diff --git a/.eslintrc.js b/.eslintrc.js index 84ba0b7..73a1d40 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -34,7 +34,11 @@ module.exports = { }, overrides: [ { - files: ['playwright/**/*.ts', 'playwright.config.ts'], + files: [ + 'playwright/**/*.ts', + 'playwright.config.ts', + 'projects/watchtower-lab/server/**/*.ts' + ], env: { jest: false, node: true diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index e74f21c..dc6d863 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -109,3 +109,52 @@ jobs: - name: Typecheck run: npm run typecheck + + e2e: + name: Playwright (Chromium, WebKit) + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v7 + + - name: Setup Node.js + uses: actions/setup-node@v7 + with: + node-version: 22.x + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Install browsers + run: npx playwright install --with-deps chromium webkit + + - name: Run the Watchtower Lab suite + run: npm run e2e + + - name: Upload the report + if: failure() + uses: actions/upload-artifact@v7 + with: + name: playwright-report + path: playwright-report/ + retention-days: 14 + + smoke: + name: Consumer smoke test + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v7 + + - name: Setup Node.js + uses: actions/setup-node@v7 + with: + node-version: 22.x + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Pack, install and build as a consumer + run: npm run smoke diff --git a/.gitignore b/.gitignore index dd2bf6e..1259144 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ # Node /node_modules +/projects/watchtower-consumer-smoke/node_modules npm-debug.log yarn-error.log @@ -30,6 +31,9 @@ yarn-error.log .sass-cache/ /connect.lock /coverage +/test-results +/playwright-report +/blob-report /libpeerconnection.log testem.log /typings diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..544cc7d --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,83 @@ +# Claude Code Guidelines + +## Core Principles + +- **Do only what was asked.** Make no unrequested changes. Keep the + scope of every change limited to the task. +- **Prefer simplicity over complexity.** Choose the simplest solution + that correctly addresses the problem. If a fix would introduce + noticeable complexity, stop and explain the trade-off before + proceeding. +- **Find the root cause first.** Investigate and understand the + underlying cause before changing code. Avoid speculative fixes and + symptom-only workarounds. +- **Never hide failing tests.** Do not delete, skip, weaken, disable, + or otherwise suppress a failing test just to make the test suite + pass. +- **Establish behavior before refactoring.** Add or verify + characterization tests before refactoring so existing behavior is + protected. For bug fixes, first create a test that reproduces the + bug and fails for the expected reason. +- **Write useful comments.** Comments should explain intent, + rationale, constraints, or context that cannot be made clear by the + code itself. Do not write comments that merely restate what the code + does. +- **Be transparent about verification.** Clearly report what was + tested or otherwise verified, what passed or failed, and what was + not verified. Never imply that checks were run when they were not. +- **Keep refactoring separate from behavior changes.** Do not combine + structural cleanup with changes to application behavior. Keep + changes focused and make behavior changes explicit. + +## Working Workflow + +1. **Understand the request.** Identify the requested outcome and the + scope. Do not expand the task without asking. +2. **Inspect before editing.** Read the relevant code and tests, trace + the behavior, and identify the root cause. +3. **Plan the smallest viable change.** Prefer a focused fix over broad + redesigns, new abstractions, or unrelated cleanup. +4. **Protect existing behavior.** Before refactoring, establish the + relevant behavior with characterization tests. Before fixing a bug, + add a regression test that demonstrates it. +5. **Make the change.** Avoid unrelated formatting, renaming, + dependency changes, or file moves. +6. **Run relevant checks.** Execute the most relevant tests and checks + available. Do not alter tests merely to obtain a green result. +7. **Report accurately.** Summarize the change and list verification + results, including anything not run and any remaining uncertainty. + +## When a Fix Adds Complexity + +If the simplest correct fix appears to require a noticeable increase in +complexity: + +- Stop before implementing the more complex approach. +- Explain why the complexity is necessary. +- Describe the main trade-offs and, where practical, a simpler + alternative. +- Ask for direction before proceeding. + +## Testing Rules + +- A bug fix should have a regression test that fails before the fix + and passes after it, whenever practical. +- A refactor should be protected by characterization tests that + capture the existing behavior. +- Preserve the intent and meaningful assertions of existing tests. +- If a test fails, investigate the failure; do not suppress it or + weaken its assertions to make the suite pass. +- Distinguish failures caused by the change from pre-existing or + environment-related failures. + +## Final Response + +At the end of the task, state: + +- **Changed:** What was changed and why. +- **Verified:** Which tests, commands, or checks were actually run and + their results. +- **Not verified:** Relevant checks that were not run, could not be + run, or remain inconclusive. + +Do not claim success beyond what the evidence supports. diff --git a/README.md b/README.md index a17a61b..3e6e8e0 100644 --- a/README.md +++ b/README.md @@ -20,11 +20,17 @@ How it is designed: [projects/ngx-ui-watchtower/DESIGN.md](projects/ngx-ui-watch ```text projects/ngx-ui-watchtower/ - src/ main entry point — @absaoss-cps/ngx-ui-watchtower - rum/ secondary entry point — @absaoss-cps/ngx-ui-watchtower/rum - README.md usage guide, published to npm - DESIGN.md full design - package.json the published package's manifest + src/ main entry point — @absaoss-cps/ngx-ui-watchtower + rum/ secondary entry point — @absaoss-cps/ngx-ui-watchtower/rum + event-inspector/ secondary entry point — @absaoss-cps/ngx-ui-watchtower/event-inspector + README.md usage guide, published to npm + DESIGN.md full design + package.json the published package's manifest +projects/watchtower-lab/ the Watchtower Lab: a testing app, never published + server/ its server: mock backend, fragment gateway, SSR +projects/watchtower-lab-fragment/ the lab's embedded application: a second app it embeds +projects/watchtower-consumer-smoke/ a minimal app that installs the packed library +playwright/ the lab's end-to-end suite (Playwright) ``` ## Getting started @@ -41,16 +47,154 @@ The package is built to `dist/ngx-ui-watchtower`. ## Scripts -| Script | What it does | -| ----------------------- | ---------------------------------------------------- | -| `npm run build` | Builds the package (production, partial compilation) | -| `npm run watch` | Rebuilds on change (development) | -| `npm run test` | Runs the unit tests (Jest) | -| `npm run test:coverage` | Runs the unit tests with coverage, as CI does | -| `npm run typecheck` | Type-checks every library and spec tsconfig | -| `npm run lint` | ESLint, no warnings allowed | -| `npm run format` | Formats with Prettier | -| `npm run format:check` | Checks formatting without writing | +| Script | What it does | +| ----------------------- | ------------------------------------------------------ | +| `npm run build` | Builds the package (production, partial compilation) | +| `npm run watch` | Rebuilds on change (development) | +| `npm run test` | Runs the unit tests (Jest) | +| `npm run test:coverage` | Runs the unit tests with coverage, as CI does | +| `npm run typecheck` | Type-checks every library, spec and suite tsconfig | +| `npm run lint` | ESLint, no warnings allowed | +| `npm run format` | Formats with Prettier | +| `npm run format:check` | Checks formatting without writing | +| `npm run lab` | Starts the Watchtower Lab on http://localhost:4280 | +| `npm run lab:build` | Builds the lab's fragment and shell | +| `npm run e2e` | Builds the lab and runs the Playwright suite | +| `npm run e2e:ui` | Builds the lab, and opens the suite in Playwright's UI | +| `npm run smoke` | Packs the library and builds an app from the tarball | + +## Watchtower Lab + +A testing app for the library, built from its source, so a change shows up +on save. `npm run lab` starts it on http://localhost:4280. Its shell is +rendered on the server, then hydrated. Its server (`server/server.ts`, which +`ng serve` runs too) also has a mock backend, which stands in for everything +the library talks to: the real `aws-rum-web` runs against a mock RUM broker +and dataplane, fully offline, and logs go to a mock log backend. + +One page per concern, in three groups. **Library**: **Scenarios**, **BI +events**, **Logging**, **Identity**, **Privacy** and **Lifecycle**, each with +a **Results** column beside its buttons — the page's own readout, and what +it sent, ticked once the mock backend has it. **Embedded apps**: **Iframe** +and **Web Fragment** (the same second app in a plain iframe, and +as a [Web Fragment](https://web-fragments.dev) through the server's gateway, +each forwarding through the shell over the tab's own channel). **Tools**: +**Event Inspector** (every dock, showing the one it is in, event generators, +and the `--uwt-inspector-inset-*` opt-in on the lab's fixed header and status +bar) and **On the wire**: what the mock backend actually received. + +**Settings**, at the foot of the nav, sets up the run every page shares: a +page per section, listed beside it by area, each saying how it applies. +**Library**: App identity, Delivery, Scenarios, BI events, Logging, Privacy & +redaction and Event Inspector. **Mock backend**: RUM broker and App monitor. +**Diagnostics**: Debug flags, which apply at once. **Advanced**: Configuration +as JSON, and the Effective configuration. A change waits, from whichever +section, in a bar listing them all, until they are applied together with one +reload, or discarded. A setting says what it does, from the library's +documentation (and aws-rum-web's, for the app monitor), and where it stands: +**Not applied**, with Undo, or **Modified** from the lab's default, with the +default and a Reset that waits to be applied too (a debug flag resets at +once); the list counts each section's modified settings. The **Overview** +lists both, to undo or reset each, or to reset all of them at once. +**Find a setting**, or `/`, finds one by its name, its name in the JSON or +what it does, and shows it in its section. On a narrow screen the sections +fold into a picker. + +The header's chips show how this load runs, each a link to its setting; +where the broker is set differently for the next one, its chip says so: +`broker: ok → null after reload`. On a narrow screen, or beside a docked +pane, the chips fold behind **Run settings**, and the page list behind +**Pages**. +The header's **Signed in** or **Signed out** only shows the state: +**Identity** and the fragments both sign in and out. + +Options, read once at startup like an app's configuration, and kept in the +address from the start page and through the page links, so a reload keeps +them too: + +| Query | Effect | +| ----------------------- | --------------------------------------------------------------------------------------------- | +| `?destination=noop` | The noop destination instead of RUM | +| `?destination=custom` | The lab's own sink, through `provideUwtTelemetryDestination` | +| `?telemetry=off` | Telemetry off, through `withTelemetryEnabled`'s factory form | +| `?channel=shared` | The shell on the default broadcast channel, shared by every tab: their hosts elect one leader | +| `?inspector=off` | The Event Inspector disabled | +| `?inspector=noshortcut` | The Event Inspector without its shortcut | +| `?config=` | Library settings laid over the lab's own | + +`?config=` takes `identity` (`eventNamespace`), `scenarios`, `logging`, `bi`, +`redaction` and `inspector`, each as its `with…` function or +`provideUwtEventInspector` takes it, in JSON: a pattern is a `RegExp` source, +and the one value transform is `mask-order-ids`. One more, `lab`, is the +lab's own: `{ "logBatching": true }` makes its log provider batch, and send +when the library flushes it. For example +`?config={"scenarios":{"emitLifecycleEvents":true}}`. **Settings** edits it, +with a control for each setting — switches, numbers, lists to pick from, lists +of patterns, and editors for logger levels and the inspector's shortcuts — and +the JSON itself, and refuses a section, setting or type the library doesn't +have. + +The mock RUM broker's answer is set in **Settings** too, under **RUM +broker**: + +| Mode | The broker | +| --------------------------- | ------------------------------------------------------------ | +| `ok` | Answers normally | +| `null` | Turns RUM off for the page | +| `fail` | Errors; the app carries on | +| `short-expiry` | Gives credentials that are refreshed within seconds | +| `flaky` | Fails twice, then answers: the sink retries, then catches up | +| `invalid` | Sends a pattern as a string: the sink doesn't start RUM | +| `null-on-refresh` | Turns RUM off at the first refresh | +| `no-credentials-on-refresh` | Refreshes without credentials: the sink keeps the old ones | + +It also sets RUM's own `telemetries` (the library's default, or any of +`errors`, `performance`, `interaction`, `http` and `replay`, down to none), +and, under **App monitor**, the broker's app monitor config: controls for the +settings most worth trying (`sessionSampleRate`, `sessionEventLimit`, +`dispatchInterval`, `allowCookies`, `enableXRay`, `disableAutoPageView`, +`recordResourceUrl` and the pages to include or exclude), and a JSON object +laid over it for the rest, such as `{ "sessionSampleRate": 0 }`. + +### Automated tests + +`npm run e2e` builds the lab and runs the suite in `playwright/` on Chromium +and WebKit, checking what the mock backend received against what each page +sends. It covers every control on every lab page: the whole scenario API and +`traceScenario`, BI events, logging and log queries, identity, redaction, +flushing, server rendering and hydration, both fragment pages, two tabs on +their own channels or one shared, a custom destination, every broker mode +and RUM's start-up and refresh, RUM's telemetries, the library's settings +through `?config=`, the debug flags, and the Event Inspector's docks, +shortcut, pause, kinds, search, filters, export, paging, resizing, links +between an error and its log event, and events kept from before opening — +and the lab's own frame: results beside the buttons, the header and nav +folding when narrow, and no page wider than a phone. It also runs axe-core's +WCAG 2.2 A and AA rules on every lab page and on the Event Inspector with its +Export menu and filter help open, measures the contrast of its coloured +text, and checks its states under forced colours. Internal edge cases — +timers longer than 24 days, giving up after five minutes, no Web Locks or +storage, message validation, start-up errors — are left to the unit tests. + +It runs the built server on http://localhost:4390, beside `npm run lab`, +starting its own each time: one already on that port stops it, rather than +serving an older build. The first time, download the browsers with +`npx playwright install chromium webkit`. + +## Consumer smoke test + +Everything above uses the library from source. `npm run smoke` uses it as +published: it packs the built package, then sets up +`projects/watchtower-consumer-smoke` on its own, in a temporary folder +outside the workspace, with its own `package.json` (the workspace's +versions) and `angular.json`, and installs it with `npm install`, peer +checks and all. Nothing comes from the workspace, so a peer the package +leaves out or mis-ranges fails as it would for an app. It builds that app +for the browser and, prerendering, for the server, and checks the tarball's +files, that all three entry points resolve with their typings, that the page +renders on the server, and that an app which never imports `/rum` has no +`aws-rum-web` in its bundle. It needs the npm registry, and keeps the folder +when a check fails. ## Before you push @@ -62,6 +206,8 @@ npm run typecheck npm run lint npm run format:check npm run build +npm run e2e +npm run smoke ``` CI runs the same checks on every pull request and every push to `main`. diff --git a/angular.json b/angular.json index 9cbcf28..30174b0 100644 --- a/angular.json +++ b/angular.json @@ -35,6 +35,114 @@ } } } + }, + "watchtower-lab": { + "projectType": "application", + "root": "projects/watchtower-lab", + "sourceRoot": "projects/watchtower-lab/src", + "prefix": "lab", + "architect": { + "build": { + "builder": "@angular/build:application", + "options": { + "outputPath": "dist/watchtower-lab", + "index": "projects/watchtower-lab/src/index.html", + "browser": "projects/watchtower-lab/src/main.ts", + "server": "projects/watchtower-lab/src/main.server.ts", + "polyfills": ["zone.js"], + "tsConfig": "projects/watchtower-lab/tsconfig.app.json", + "inlineStyleLanguage": "scss", + "styles": ["projects/watchtower-lab/src/styles.scss"], + "assets": [ + { + "glob": "icons.svg", + "input": "node_modules/cps-ui-kit/assets", + "output": "assets" + }, + { + "glob": "logo-mark.svg", + "input": "assets/logo", + "output": "assets/logo" + } + ], + "optimization": false, + "sourceMap": true, + "extractLicenses": false, + "outputMode": "server", + "ssr": { + "entry": "projects/watchtower-lab/server/server.ts" + }, + "externalDependencies": ["htmlrewriter"], + "security": { + "allowedHosts": ["localhost"] + } + } + }, + "serve": { + "builder": "@angular/build:dev-server", + "options": { + "buildTarget": "watchtower-lab:build", + "port": 4280 + } + } + } + }, + "watchtower-lab-fragment": { + "projectType": "application", + "root": "projects/watchtower-lab-fragment", + "sourceRoot": "projects/watchtower-lab-fragment/src", + "prefix": "lab", + "architect": { + "build": { + "builder": "@angular/build:application", + "options": { + "outputPath": "dist/watchtower-lab-fragment", + "index": "projects/watchtower-lab-fragment/src/index.html", + "browser": "projects/watchtower-lab-fragment/src/main.ts", + "tsConfig": "projects/watchtower-lab-fragment/tsconfig.app.json", + "inlineStyleLanguage": "scss", + "styles": ["projects/watchtower-lab-fragment/src/styles.scss"], + "baseHref": "/_fragment/lab-fragment/", + "deployUrl": "/_fragment/lab-fragment/", + "optimization": false, + "sourceMap": true, + "extractLicenses": false + } + } + } + }, + "watchtower-consumer-smoke": { + "projectType": "application", + "root": "projects/watchtower-consumer-smoke", + "sourceRoot": "projects/watchtower-consumer-smoke/src", + "prefix": "smoke", + "architect": { + "build": { + "builder": "@angular/build:application", + "options": { + "outputPath": "dist/watchtower-consumer-smoke", + "index": "projects/watchtower-consumer-smoke/src/index.html", + "browser": "projects/watchtower-consumer-smoke/src/main.ts", + "server": "projects/watchtower-consumer-smoke/src/main.server.ts", + "outputMode": "static", + "tsConfig": "projects/watchtower-consumer-smoke/tsconfig.app.json", + "optimization": true, + "sourceMap": false, + "extractLicenses": false + }, + "configurations": { + "rum": { + "outputPath": "dist/watchtower-consumer-smoke-rum", + "fileReplacements": [ + { + "replace": "projects/watchtower-consumer-smoke/src/destination.ts", + "with": "projects/watchtower-consumer-smoke/src/destination.rum.ts" + } + ] + } + } + } + } } } } diff --git a/assets/logo/logo-mark.svg b/assets/logo/logo-mark.svg new file mode 100644 index 0000000..22a870c --- /dev/null +++ b/assets/logo/logo-mark.svg @@ -0,0 +1 @@ +Angular UI Watchtower diff --git a/jest.config.js b/jest.config.js index 3a8a9c8..c40547f 100644 --- a/jest.config.js +++ b/jest.config.js @@ -3,10 +3,13 @@ module.exports = { coverageDirectory: '/coverage', preset: 'jest-preset-angular', moduleNameMapper: { + '^lodash-es$': 'lodash', '^@absaoss-cps/ngx-ui-watchtower$': '/projects/ngx-ui-watchtower/src/public-api.ts', '^@absaoss-cps/ngx-ui-watchtower/rum$': - '/projects/ngx-ui-watchtower/rum/src/public-api.ts' + '/projects/ngx-ui-watchtower/rum/src/public-api.ts', + '^@absaoss-cps/ngx-ui-watchtower/event-inspector$': + '/projects/ngx-ui-watchtower/event-inspector/src/public-api.ts' }, transformIgnorePatterns: [ 'node_modules/(?!(.*.mjs$|@angular/common/locales/.*.js$))' diff --git a/package-lock.json b/package-lock.json index 8aaef95..45b325a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,25 +8,36 @@ "name": "ui-watchtower", "version": "0.0.0", "dependencies": { - "@angular/common": "~21.0.9", - "@angular/compiler": "~21.0.9", - "@angular/core": "~21.0.9", - "@angular/platform-browser": "~21.0.9", - "@angular/platform-browser-dynamic": "~21.0.9", + "@angular/common": "~21.2.26", + "@angular/compiler": "~21.2.26", + "@angular/core": "~21.2.26", + "@angular/platform-browser": "~21.2.26", + "@angular/platform-browser-dynamic": "~21.2.26", "rxjs": "~7.8.2", "tslib": "^2.8.1", "zone.js": "~0.16.1" }, "devDependencies": { "@angular-builders/jest": "^21.0.4", - "@angular/build": "~21.0.6", - "@angular/cli": "~21.0.6", - "@angular/compiler-cli": "~21.0.9", + "@angular/animations": "~21.2.26", + "@angular/build": "~21.2.29", + "@angular/cli": "~21.2.29", + "@angular/compiler-cli": "~21.2.26", + "@angular/forms": "~21.2.26", + "@angular/platform-server": "~21.2.26", + "@angular/router": "~21.2.26", + "@angular/ssr": "~21.2.29", + "@axe-core/playwright": "4.14.0", + "@e965/xlsx": "^0.20.3", + "@playwright/test": "^1.64.1", + "@types/express": "^5.0.6", "@types/jest": "^30.0.0", + "@types/lodash-es": "^4.17.12", "@types/node": "^22.10.10", "@typescript-eslint/eslint-plugin": "^8.67.0", "@typescript-eslint/parser": "^8.67.0", "aws-rum-web": "^3.2.1", + "cps-ui-kit": "^21.33.0", "eslint": "^8.57.1", "eslint-config-prettier": "^10.1.8", "eslint-config-standard": "^17.1.0", @@ -35,65 +46,69 @@ "eslint-plugin-prettier": "^5.2.6", "eslint-plugin-promise": "^6.6.0", "eslint-plugin-standard": "^5.0.0", + "express": "^5.3.0", "jest": "^30.3.0", "jest-environment-jsdom": "^30.3.0", "jest-preset-angular": "^17.0.0", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", "ng-packagr": "~21.0.1", "prettier": "^3.4.2", - "typescript": "~5.9.3" + "typescript": "~5.9.3", + "web-fragments": "^0.8.2" }, "engines": { "node": ">=22.0.0" } }, "node_modules/@algolia/abtesting": { - "version": "1.6.1", - "integrity": "sha512-wV/gNRkzb7sI9vs1OneG129hwe3Q5zPj7zigz3Ps7M5Lpo2hSorrOnXNodHEOV+yXE/ks4Pd+G3CDFIjFTWhMQ==", + "version": "1.14.1", + "integrity": "sha512-Dkj0BgPiLAaim9sbQ97UKDFHJE/880wgStAM18U++NaJ/2Cws34J5731ovJifr6E3Pv4T2CqvMXf8qLCC417Ew==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.40.1", - "@algolia/requester-browser-xhr": "5.40.1", - "@algolia/requester-fetch": "5.40.1", - "@algolia/requester-node-http": "5.40.1" + "@algolia/client-common": "5.48.1", + "@algolia/requester-browser-xhr": "5.48.1", + "@algolia/requester-fetch": "5.48.1", + "@algolia/requester-node-http": "5.48.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-abtesting": { - "version": "5.40.1", - "integrity": "sha512-cxKNATPY5t+Mv8XAVTI57altkaPH+DZi4uMrnexPxPHODMljhGYY+GDZyHwv9a+8CbZHcY372OkxXrDMZA4Lnw==", + "version": "5.48.1", + "integrity": "sha512-LV5qCJdj+/m9I+Aj91o+glYszrzd7CX6NgKaYdTOj4+tUYfbS62pwYgUfZprYNayhkQpVFcrW8x8ZlIHpS23Vw==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.40.1", - "@algolia/requester-browser-xhr": "5.40.1", - "@algolia/requester-fetch": "5.40.1", - "@algolia/requester-node-http": "5.40.1" + "@algolia/client-common": "5.48.1", + "@algolia/requester-browser-xhr": "5.48.1", + "@algolia/requester-fetch": "5.48.1", + "@algolia/requester-node-http": "5.48.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-analytics": { - "version": "5.40.1", - "integrity": "sha512-XP008aMffJCRGAY8/70t+hyEyvqqV7YKm502VPu0+Ji30oefrTn2al7LXkITz7CK6I4eYXWRhN6NaIUi65F1OA==", + "version": "5.48.1", + "integrity": "sha512-/AVoMqHhPm14CcHq7mwB+bUJbfCv+jrxlNvRjXAuO+TQa+V37N8k1b0ijaRBPdmSjULMd8KtJbQyUyabXOu6Kg==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.40.1", - "@algolia/requester-browser-xhr": "5.40.1", - "@algolia/requester-fetch": "5.40.1", - "@algolia/requester-node-http": "5.40.1" + "@algolia/client-common": "5.48.1", + "@algolia/requester-browser-xhr": "5.48.1", + "@algolia/requester-fetch": "5.48.1", + "@algolia/requester-node-http": "5.48.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-common": { - "version": "5.40.1", - "integrity": "sha512-gWfQuQUBtzUboJv/apVGZMoxSaB0M4Imwl1c9Ap+HpCW7V0KhjBddqF2QQt5tJZCOFsfNIgBbZDGsEPaeKUosw==", + "version": "5.48.1", + "integrity": "sha512-VXO+qu2Ep6ota28ktvBm3sG53wUHS2n7bgLWmce5jTskdlCD0/JrV4tnBm1l7qpla1CeoQb8D7ShFhad+UoSOw==", "dev": true, "license": "MIT", "engines": { @@ -101,141 +116,141 @@ } }, "node_modules/@algolia/client-insights": { - "version": "5.40.1", - "integrity": "sha512-RTLjST/t+lsLMouQ4zeLJq2Ss+UNkLGyNVu+yWHanx6kQ3LT5jv8UvPwyht9s7R6jCPnlSI77WnL80J32ZuyJg==", + "version": "5.48.1", + "integrity": "sha512-zl+Qyb0nLg+Y5YvKp1Ij+u9OaPaKg2/EPzTwKNiVyOHnQJlFxmXyUZL1EInczAZsEY8hVpPCLtNfhMhfxluXKQ==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.40.1", - "@algolia/requester-browser-xhr": "5.40.1", - "@algolia/requester-fetch": "5.40.1", - "@algolia/requester-node-http": "5.40.1" + "@algolia/client-common": "5.48.1", + "@algolia/requester-browser-xhr": "5.48.1", + "@algolia/requester-fetch": "5.48.1", + "@algolia/requester-node-http": "5.48.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-personalization": { - "version": "5.40.1", - "integrity": "sha512-2FEK6bUomBzEYkTKzD0iRs7Ljtjb45rKK/VSkyHqeJnG+77qx557IeSO0qVFE3SfzapNcoytTofnZum0BQ6r3Q==", + "version": "5.48.1", + "integrity": "sha512-r89Qf9Oo9mKWQXumRu/1LtvVJAmEDpn8mHZMc485pRfQUMAwSSrsnaw1tQ3sszqzEgAr1c7rw6fjBI+zrAXTOw==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.40.1", - "@algolia/requester-browser-xhr": "5.40.1", - "@algolia/requester-fetch": "5.40.1", - "@algolia/requester-node-http": "5.40.1" + "@algolia/client-common": "5.48.1", + "@algolia/requester-browser-xhr": "5.48.1", + "@algolia/requester-fetch": "5.48.1", + "@algolia/requester-node-http": "5.48.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-query-suggestions": { - "version": "5.40.1", - "integrity": "sha512-Nju4NtxAvXjrV2hHZNLKVJLXjOlW6jAXHef/CwNzk1b2qIrCWDO589ELi5ZHH1uiWYoYyBXDQTtHmhaOVVoyXg==", + "version": "5.48.1", + "integrity": "sha512-TPKNPKfghKG/bMSc7mQYD9HxHRUkBZA4q1PEmHgICaSeHQscGqL4wBrKkhfPlDV1uYBKW02pbFMUhsOt7p4ZpA==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.40.1", - "@algolia/requester-browser-xhr": "5.40.1", - "@algolia/requester-fetch": "5.40.1", - "@algolia/requester-node-http": "5.40.1" + "@algolia/client-common": "5.48.1", + "@algolia/requester-browser-xhr": "5.48.1", + "@algolia/requester-fetch": "5.48.1", + "@algolia/requester-node-http": "5.48.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-search": { - "version": "5.40.1", - "integrity": "sha512-Mw6pAUF121MfngQtcUb5quZVqMC68pSYYjCRZkSITC085S3zdk+h/g7i6FxnVdbSU6OztxikSDMh1r7Z+4iPlA==", + "version": "5.48.1", + "integrity": "sha512-4Fu7dnzQyQmMFknYwTiN/HxPbH4DyxvQ1m+IxpPp5oslOgz8m6PG5qhiGbqJzH4HiT1I58ecDiCAC716UyVA8Q==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.40.1", - "@algolia/requester-browser-xhr": "5.40.1", - "@algolia/requester-fetch": "5.40.1", - "@algolia/requester-node-http": "5.40.1" + "@algolia/client-common": "5.48.1", + "@algolia/requester-browser-xhr": "5.48.1", + "@algolia/requester-fetch": "5.48.1", + "@algolia/requester-node-http": "5.48.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/ingestion": { - "version": "1.40.1", - "integrity": "sha512-z+BPlhs45VURKJIxsR99NNBWpUEEqIgwt10v/fATlNxc4UlXvALdOsWzaFfe89/lbP5Bu4+mbO59nqBC87ZM/g==", + "version": "1.48.1", + "integrity": "sha512-/RFq3TqtXDUUawwic/A9xylA2P3LDMO8dNhphHAUOU51b1ZLHrmZ6YYJm3df1APz7xLY1aht6okCQf+/vmrV9w==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.40.1", - "@algolia/requester-browser-xhr": "5.40.1", - "@algolia/requester-fetch": "5.40.1", - "@algolia/requester-node-http": "5.40.1" + "@algolia/client-common": "5.48.1", + "@algolia/requester-browser-xhr": "5.48.1", + "@algolia/requester-fetch": "5.48.1", + "@algolia/requester-node-http": "5.48.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/monitoring": { - "version": "1.40.1", - "integrity": "sha512-VJMUMbO0wD8Rd2VVV/nlFtLJsOAQvjnVNGkMkspFiFhpBA7s/xJOb+fJvvqwKFUjbKTUA7DjiSi1ljSMYBasXg==", + "version": "1.48.1", + "integrity": "sha512-Of0jTeAZRyRhC7XzDSjJef0aBkgRcvRAaw0ooYRlOw57APii7lZdq+layuNdeL72BRq1snaJhoMMwkmLIpJScw==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.40.1", - "@algolia/requester-browser-xhr": "5.40.1", - "@algolia/requester-fetch": "5.40.1", - "@algolia/requester-node-http": "5.40.1" + "@algolia/client-common": "5.48.1", + "@algolia/requester-browser-xhr": "5.48.1", + "@algolia/requester-fetch": "5.48.1", + "@algolia/requester-node-http": "5.48.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/recommend": { - "version": "5.40.1", - "integrity": "sha512-ehvJLadKVwTp9Scg9NfzVSlBKH34KoWOQNTaN8i1Ac64AnO6iH2apJVSP6GOxssaghZ/s8mFQsDH3QIZoluFHA==", + "version": "5.48.1", + "integrity": "sha512-bE7JcpFXzxF5zHwj/vkl2eiCBvyR1zQ7aoUdO+GDXxGp0DGw7nI0p8Xj6u8VmRQ+RDuPcICFQcCwRIJT5tDJFw==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.40.1", - "@algolia/requester-browser-xhr": "5.40.1", - "@algolia/requester-fetch": "5.40.1", - "@algolia/requester-node-http": "5.40.1" + "@algolia/client-common": "5.48.1", + "@algolia/requester-browser-xhr": "5.48.1", + "@algolia/requester-fetch": "5.48.1", + "@algolia/requester-node-http": "5.48.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-browser-xhr": { - "version": "5.40.1", - "integrity": "sha512-PbidVsPurUSQIr6X9/7s34mgOMdJnn0i6p+N6Ab+lsNhY5eiu+S33kZEpZwkITYBCIbhzDLOvb7xZD3gDi+USA==", + "version": "5.48.1", + "integrity": "sha512-MK3wZ2koLDnvH/AmqIF1EKbJlhRS5j74OZGkLpxI4rYvNi9Jn/C7vb5DytBnQ4KUWts7QsmbdwHkxY5txQHXVw==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.40.1" + "@algolia/client-common": "5.48.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-fetch": { - "version": "5.40.1", - "integrity": "sha512-ThZ5j6uOZCF11fMw9IBkhigjOYdXGXQpj6h4k+T9UkZrF2RlKcPynFzDeRgaLdpYk8Yn3/MnFbwUmib7yxj5Lw==", + "version": "5.48.1", + "integrity": "sha512-2oDT43Y5HWRSIQMPQI4tA/W+TN/N2tjggZCUsqQV440kxzzoPGsvv9QP1GhQ4CoDa+yn6ygUsGp6Dr+a9sPPSg==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.40.1" + "@algolia/client-common": "5.48.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-node-http": { - "version": "5.40.1", - "integrity": "sha512-H1gYPojO6krWHnUXu/T44DrEun/Wl95PJzMXRcM/szstNQczSbwq6wIFJPI9nyE95tarZfUNU3rgorT+wZ6iCQ==", + "version": "5.48.1", + "integrity": "sha512-xcaCqbhupVWhuBP1nwbk1XNvwrGljozutEiLx06mvqDf3o8cHyEgQSHS4fKJM+UAggaWVnnFW+Nne5aQ8SUJXg==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.40.1" + "@algolia/client-common": "5.48.1" }, "engines": { "node": ">= 14.0.0" @@ -572,19 +587,6 @@ } } }, - "node_modules/@angular-devkit/build-angular/node_modules/@angular/build/node_modules/@vitejs/plugin-basic-ssl": { - "version": "2.1.4", - "integrity": "sha512-HXciTXN/sDBYWgeAD4V4s0DN0g72x5mlxQhHxtYu3Tt8BLa6MzcJZUyDVFCdtjNs3bfENVHVzOsmooTVuNgAAw==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "peerDependencies": { - "vite": "^6.0.0 || ^7.0.0" - } - }, "node_modules/@angular-devkit/build-angular/node_modules/@angular/build/node_modules/vite": { "version": "7.3.6", "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", @@ -660,901 +662,762 @@ } } }, - "node_modules/@angular-devkit/build-angular/node_modules/@inquirer/confirm": { - "version": "5.1.21", - "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", + "node_modules/@angular-devkit/build-angular/node_modules/undici": { + "version": "7.29.1", + "integrity": "sha512-RYONW2MeafgYlkVOKYKkA/Ag7BmXqgIWCa8t1m0JcxrQg9pI9lEqRhAOruOBCbAohOa/gkCF+iPi9hrgvTzu6Q==", "dev": true, "license": "MIT", "peer": true, - "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10" - }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "node": ">=20.18.1" } }, - "node_modules/@angular-devkit/build-angular/node_modules/@lmdb/lmdb-darwin-arm64": { - "version": "3.5.1", - "integrity": "sha512-tpfN4kKrrMpQ+If1l8bhmoNkECJi0iOu6AEdrTJvWVC+32sLxTARX5Rsu579mPImRP9YFWfWgeRQ5oav7zApQQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@angular-devkit/build-angular/node_modules/@lmdb/lmdb-darwin-x64": { - "version": "3.5.1", - "integrity": "sha512-+a2tTfc3rmWhLAolFUWRgJtpSuu+Fw/yjn4rF406NMxhfjbMuiOUTDRvRlMFV+DzyjkwnokisskHbCWkS3Ly5w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@angular-devkit/build-angular/node_modules/@lmdb/lmdb-linux-arm": { - "version": "3.5.1", - "integrity": "sha512-0EgcE6reYr8InjD7V37EgXcYrloqpxVPINy3ig1MwDSbl6LF/vXTYRH9OE1Ti1D8YZnB35ZH9aTcdfSb5lql2A==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@angular-devkit/build-angular/node_modules/@lmdb/lmdb-linux-arm64": { - "version": "3.5.1", - "integrity": "sha512-aoERa5B6ywXdyFeYGQ1gbQpkMkDbEo45qVoXE5QpIRavqjnyPwjOulMkmkypkmsbJ5z4Wi0TBztON8agCTG0Vg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@angular-devkit/build-angular/node_modules/@lmdb/lmdb-linux-x64": { - "version": "3.5.1", - "integrity": "sha512-SqNDY1+vpji7bh0sFH5wlWyFTOzjbDOl0/kB5RLLYDAFyd/uw3n7wyrmas3rYPpAW7z18lMOi1yKlTPv967E3g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@angular-devkit/build-angular/node_modules/@lmdb/lmdb-win32-arm64": { - "version": "3.5.1", - "integrity": "sha512-50v0O1Lt37cwrmR9vWZK5hRW0Aw+KEmxJJ75fge/zIYdvNKB/0bSMSVR5Uc2OV9JhosIUyklOmrEvavwNJ8D6w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@angular-devkit/build-angular/node_modules/@lmdb/lmdb-win32-x64": { - "version": "3.5.1", - "integrity": "sha512-qwosvPyl+zpUlp3gRb7UcJ3H8S28XHCzkv0Y0EgQToXjQP91ZD67EHSCDmaLjtKhe+GVIW5om1KUpzVLA0l6pg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@angular-devkit/build-angular/node_modules/@oxc-project/types": { - "version": "0.113.0", - "integrity": "sha512-Tp3XmgxwNQ9pEN9vxgJBAqdRamHibi76iowQ38O2I4PMpcvNRQNVsU2n1x1nv9yh0XoTrGFzf7cZSGxmixxrhA==", + "node_modules/@angular-devkit/build-webpack": { + "version": "0.2102.26", + "integrity": "sha512-a16LANAlLYTNeLkrMwIEXZI1xJKi+ko0oLI+JhCj8/pnmMPX6yHawZQJ2mgKXjBAub+SK783q6RPBxy+Iu2DTQ==", "dev": true, "license": "MIT", "peer": true, - "funding": { - "url": "https://github.com/sponsors/Boshen" + "dependencies": { + "@angular-devkit/architect": "0.2102.26", + "rxjs": "7.8.2" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "webpack": "^5.30.0", + "webpack-dev-server": "^5.0.2" } }, - "node_modules/@angular-devkit/build-angular/node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.4", - "integrity": "sha512-vRq9f4NzvbdZavhQbjkJBx7rRebDKYR9zHfO/Wg486+I7bSecdUapzCm5cyXoK+LHokTxgSq7A5baAXUZkIz0w==", - "cpu": [ - "arm64" - ], + "node_modules/@angular-devkit/core": { + "version": "21.2.26", + "integrity": "sha512-9X6hwij620GDWJtzuZ1awGolx42Zt0kY9IRhyTAE/UyLvIdcj5+eKbKeAv43KR9wbFIHIwWPWZ+R+8ehiOetow==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, + "dependencies": { + "ajv": "8.18.0", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.4", + "rxjs": "7.8.2", + "source-map": "0.7.6" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^5.0.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } } }, - "node_modules/@angular-devkit/build-angular/node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.4", - "integrity": "sha512-kFgEvkWLqt3YCgKB5re9RlIrx9bRsvyVUnaTakEpOPuLGzLpLapYxE9BufJNvPg8GjT6mB1alN4yN1NjzoeM8Q==", - "cpu": [ - "arm64" - ], + "node_modules/@angular-devkit/schematics": { + "version": "21.2.29", + "integrity": "sha512-Bx/my36Rxwf0ptgwngXTCfbOPHyr7FX2PL3mBuaZh99065969leFd6dFajss9hSAoNreZtJu73dw5AjOpgIjBw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, + "dependencies": { + "@angular-devkit/core": "21.2.29", + "jsonc-parser": "3.3.1", + "magic-string": "0.30.21", + "ora": "9.3.0", + "rxjs": "7.8.2" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" } }, - "node_modules/@angular-devkit/build-angular/node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.4", - "integrity": "sha512-JXmaOJGsL/+rsmMfutcDjxWM2fTaVgCHGoXS7nE8Z3c9NAYjGqHvXrAhMUZvMpHS/k7Mg+X7n/MVKb7NYWKKww==", - "cpu": [ - "x64" - ], + "node_modules/@angular-devkit/schematics/node_modules/@angular-devkit/core": { + "version": "21.2.29", + "integrity": "sha512-807H6zbmVxoTZEZymJM/YKcn9BcHQsgCQg9Yki4jDAQIMockyIN9F7jscxHqBtLt2rXHiiDVyWiJhMEyot1OXQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, + "dependencies": { + "ajv": "8.18.0", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.4", + "rxjs": "7.8.2", + "source-map": "0.7.6" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^5.0.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } } }, - "node_modules/@angular-devkit/build-angular/node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.4", - "integrity": "sha512-ep3Catd6sPnHTM0P4hNEvIv5arnDvk01PfyJIJ+J3wVCG1eEaPo09tvFqdtcaTrkwQy0VWR24uz+cb4IsK53Qw==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@angular/animations": { + "version": "21.2.26", + "integrity": "sha512-4aE0nz6nfq3YDGTFaVwfG8/JDoOUQcAxYLxQFXeKGkP6ZH7/DAr64vswDq2ctDXTiSy6HvIrwoIeOg1nNxuMMg==", + "devOptional": true, "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, + "dependencies": { + "tslib": "^2.3.0" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/core": "21.2.26" } }, - "node_modules/@angular-devkit/build-angular/node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.4", - "integrity": "sha512-LwA5ayKIpnsgXJEwWc3h8wPiS33NMIHd9BhsV92T8VetVAbGe2qXlJwNVDGHN5cOQ22R9uYvbrQir2AB+ntT2w==", - "cpu": [ - "arm" - ], + "node_modules/@angular/build": { + "version": "21.2.29", + "integrity": "sha512-aahiMmyvY10nYaduYqrJPgHY3k4FpVqWYzaruxLWF74vRhz8x3OOX1rdro1OHn2p2C2/Ga80kAZ3hlsx3OyGFQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, + "dependencies": { + "@ampproject/remapping": "2.3.0", + "@angular-devkit/architect": "0.2102.29", + "@babel/core": "7.29.7", + "@babel/helper-annotate-as-pure": "7.27.3", + "@babel/helper-split-export-declaration": "7.24.7", + "@inquirer/confirm": "5.1.21", + "@vitejs/plugin-basic-ssl": "2.1.4", + "beasties": "0.4.1", + "browserslist": "^4.26.0", + "esbuild": "0.28.1", + "https-proxy-agent": "7.0.6", + "istanbul-lib-instrument": "6.0.3", + "jsonc-parser": "3.3.1", + "listr2": "9.0.5", + "magic-string": "0.30.21", + "mrmime": "2.0.1", + "parse5-html-rewriting-stream": "8.0.0", + "picomatch": "4.0.4", + "piscina": "5.3.2", + "rolldown": "1.0.0-rc.4", + "sass": "1.97.3", + "semver": "7.7.4", + "source-map-support": "0.5.21", + "tinyglobby": "0.2.15", + "undici": "7.30.1", + "vite": "7.3.7", + "watchpack": "2.5.1" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "optionalDependencies": { + "lmdb": "3.5.1" + }, + "peerDependencies": { + "@angular/compiler": "^21.0.0", + "@angular/compiler-cli": "^21.0.0", + "@angular/core": "^21.0.0", + "@angular/localize": "^21.0.0", + "@angular/platform-browser": "^21.0.0", + "@angular/platform-server": "^21.0.0", + "@angular/service-worker": "^21.0.0", + "@angular/ssr": "^21.2.29", + "karma": "^6.4.0", + "less": "^4.2.0", + "ng-packagr": "^21.0.0", + "postcss": "^8.4.0", + "tailwindcss": "^2.0.0 || ^3.0.0 || ^4.0.0", + "tslib": "^2.3.0", + "typescript": ">=5.9 <6.0", + "vitest": "^4.0.8" + }, + "peerDependenciesMeta": { + "@angular/core": { + "optional": true + }, + "@angular/localize": { + "optional": true + }, + "@angular/platform-browser": { + "optional": true + }, + "@angular/platform-server": { + "optional": true + }, + "@angular/service-worker": { + "optional": true + }, + "@angular/ssr": { + "optional": true + }, + "karma": { + "optional": true + }, + "less": { + "optional": true + }, + "ng-packagr": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tailwindcss": { + "optional": true + }, + "vitest": { + "optional": true + } } }, - "node_modules/@angular-devkit/build-angular/node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.4", - "integrity": "sha512-AC1WsGdlV1MtGay/OQ4J9T7GRadVnpYRzTcygV1hKnypbYN20Yh4t6O1Sa2qRBMqv1etulUknqXjc3CTIsBu6A==", - "cpu": [ - "arm64" - ], + "node_modules/@angular/build/node_modules/@angular-devkit/architect": { + "version": "0.2102.29", + "integrity": "sha512-o7dg7tlGbVr/ddXSAD9B/ozNwETfI7hzaBAe4v00+L+Y7Mz85hIy3R9/B0elY/SyoEDkW/HHTRsLubHMWg2AZw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, + "dependencies": { + "@angular-devkit/core": "21.2.29", + "rxjs": "7.8.2" + }, + "bin": { + "architect": "bin/cli.js" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" } }, - "node_modules/@angular-devkit/build-angular/node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.4", - "integrity": "sha512-lU+6rgXXViO61B4EudxtVMXSOfiZONR29Sys5VGSetUY7X8mg9FCKIIjcPPj8xNDeYzKl+H8F/qSKOBVFJChCQ==", - "cpu": [ - "arm64" - ], + "node_modules/@angular/build/node_modules/@angular-devkit/core": { + "version": "21.2.29", + "integrity": "sha512-807H6zbmVxoTZEZymJM/YKcn9BcHQsgCQg9Yki4jDAQIMockyIN9F7jscxHqBtLt2rXHiiDVyWiJhMEyot1OXQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, + "dependencies": { + "ajv": "8.18.0", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.4", + "rxjs": "7.8.2", + "source-map": "0.7.6" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^5.0.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } } }, - "node_modules/@angular-devkit/build-angular/node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.4", - "integrity": "sha512-DZaN1f0PGp/bSvKhtw50pPsnln4T13ycDq1FrDWRiHmWt1JeW+UtYg9touPFf8yt993p8tS2QjybpzKNTxYEwg==", - "cpu": [ - "x64" - ], + "node_modules/@angular/cdk": { + "version": "21.2.14", + "integrity": "sha512-806REq/CLf37nEhmmd8Q+ILN8z/RVG2vk2n8YZ/4TdHpcBCi5ux4AxLbpMmduLwGPOzPagJ6ggRzE5fnX0rmcQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "peer": true, - "engines": { - "node": "^20.19.0 || >=22.12.0" + "dependencies": { + "parse5": "^8.0.0", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": "^21.0.0 || ^22.0.0", + "@angular/core": "^21.0.0 || ^22.0.0", + "@angular/platform-browser": "^21.0.0 || ^22.0.0", + "rxjs": "^6.5.3 || ^7.4.0" } }, - "node_modules/@angular-devkit/build-angular/node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.4", - "integrity": "sha512-RnGxwZLN7fhMMAItnD6dZ7lvy+TI7ba+2V54UF4dhaWa/p8I/ys1E73KO6HmPmgz92ZkfD8TXS1IMV8+uhbR9g==", - "cpu": [ - "x64" - ], + "node_modules/@angular/cdk/node_modules/entities": { + "version": "9.0.0", + "integrity": "sha512-oJFNDJg3kTPwAD1cNn2+WmUq4wFoo09npc3Ydf1D7ED4smRiWrsKnaLxykrTWgyGEcusc87P5dxlAleol9kpkg==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "license": "BSD-2-Clause", "peer": true, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/@angular-devkit/build-angular/node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.4", - "integrity": "sha512-6lcI79+X8klGiGd8yHuTgQRjuuJYNggmEml+RsyN596P23l/zf9FVmJ7K0KVKkFAeYEdg0iMUKyIxiV5vebDNQ==", - "cpu": [ - "arm64" - ], + "node_modules/@angular/cdk/node_modules/parse5": { + "version": "8.1.0", + "integrity": "sha512-sq3qBFpee5jeocbFbO48VHlGr8sT7mbXS0vxlKN6sottBtJzrOG3998sr0llSZcQAGgfpdmEYAIZE0P9z0dE8w==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], "peer": true, - "engines": { - "node": "^20.19.0 || >=22.12.0" + "dependencies": { + "entities": "^9.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/@angular-devkit/build-angular/node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.4", - "integrity": "sha512-wz7ohsKCAIWy91blZ/1FlpPdqrsm1xpcEOQVveWoL6+aSPKL4VUcoYmmzuLTssyZxRpEwzuIxL/GDsvpjaBtOw==", - "cpu": [ - "wasm32" - ], + "node_modules/@angular/cli": { + "version": "21.2.29", + "integrity": "sha512-RGi3JzLgrCb5tZVqqCPjbb1w/1XFwFnno9iIHMs1wcecxcEhiXLq/klLj4wmHk4iWkoCYaltYs+h9fCmkvrVzA==", "dev": true, "license": "MIT", - "optional": true, - "peer": true, "dependencies": { - "@napi-rs/wasm-runtime": "^1.1.1" + "@angular-devkit/architect": "0.2102.29", + "@angular-devkit/core": "21.2.29", + "@angular-devkit/schematics": "21.2.29", + "@inquirer/prompts": "7.10.1", + "@listr2/prompt-adapter-inquirer": "3.0.5", + "@modelcontextprotocol/sdk": "1.32.1", + "@schematics/angular": "21.2.29", + "@yarnpkg/lockfile": "1.1.0", + "algoliasearch": "5.48.1", + "ini": "6.0.0", + "jsonc-parser": "3.3.1", + "listr2": "9.0.5", + "npm-package-arg": "13.0.2", + "pacote": "21.5.1", + "parse5-html-rewriting-stream": "8.0.0", + "semver": "7.7.4", + "yargs": "18.0.0", + "zod": "4.6.6" + }, + "bin": { + "ng": "bin/ng.js" }, "engines": { - "node": ">=14.0.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" } }, - "node_modules/@angular-devkit/build-angular/node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.4", - "integrity": "sha512-cfiMrfuWCIgsFmcVG0IPuO6qTRHvF7NuG3wngX1RZzc6dU8FuBFb+J3MIR5WrdTNozlumfgL4cvz+R4ozBCvsQ==", - "cpu": [ - "arm64" - ], + "node_modules/@angular/cli/node_modules/@angular-devkit/architect": { + "version": "0.2102.29", + "integrity": "sha512-o7dg7tlGbVr/ddXSAD9B/ozNwETfI7hzaBAe4v00+L+Y7Mz85hIy3R9/B0elY/SyoEDkW/HHTRsLubHMWg2AZw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, + "dependencies": { + "@angular-devkit/core": "21.2.29", + "rxjs": "7.8.2" + }, + "bin": { + "architect": "bin/cli.js" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" } }, - "node_modules/@angular-devkit/build-angular/node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.4", - "integrity": "sha512-p6UeR9y7ht82AH57qwGuFYn69S6CZ7LLKdCKy/8T3zS9VTrJei2/CGsTUV45Da4Z9Rbhc7G4gyWQ/Ioamqn09g==", - "cpu": [ - "x64" - ], + "node_modules/@angular/cli/node_modules/@angular-devkit/core": { + "version": "21.2.29", + "integrity": "sha512-807H6zbmVxoTZEZymJM/YKcn9BcHQsgCQg9Yki4jDAQIMockyIN9F7jscxHqBtLt2rXHiiDVyWiJhMEyot1OXQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, + "dependencies": { + "ajv": "8.18.0", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.4", + "rxjs": "7.8.2", + "source-map": "0.7.6" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^5.0.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } } }, - "node_modules/@angular-devkit/build-angular/node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.4", - "integrity": "sha512-1BrrmTu0TWfOP1riA8uakjFc9bpIUGzVKETsOtzY39pPga8zELGDl8eu1Dx7/gjM5CAz14UknsUMpBO8L+YntQ==", - "dev": true, + "node_modules/@angular/common": { + "version": "21.2.26", + "integrity": "sha512-2lnNdImIHaeTmYqW+lEKeU9vPauNLz8NgM2MoZs7kV9D+CfPMi90F5VwRg0q16J/UiKS8uiYIYmUeaoo2wu4Pg==", "license": "MIT", - "peer": true - }, - "node_modules/@angular-devkit/build-angular/node_modules/beasties": { - "version": "0.4.1", - "integrity": "sha512-2Imdcw3LznDuxAbJM26RHniOLAzE6WgrK8OuvVXCQtNBS8rsnD9zsSEa3fHl4hHpUY7BYTlrpvtPVbvu9G6neg==", - "dev": true, - "license": "Apache-2.0", - "peer": true, "dependencies": { - "css-select": "^6.0.0", - "css-what": "^7.0.0", - "dom-serializer": "^2.0.0", - "domhandler": "^5.0.3", - "htmlparser2": "^10.0.0", - "picocolors": "^1.1.1", - "postcss": "^8.4.49", - "postcss-media-query-parser": "^0.2.3", - "postcss-safe-parser": "^7.0.1" + "tslib": "^2.3.0" }, "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/beasties/node_modules/postcss-safe-parser": { - "version": "7.1.0", - "integrity": "sha512-1WzZxRLaAFwEh6Do+zyGpjWV3nGJNxxhuh7Ubu/q1ICImMgZJnLwhoaEpRbG4pJppp2Y1ncL9ffA2f+LhrefQg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "peer": true, - "engines": { - "node": ">=18.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "@angular/core": "21.2.26", + "rxjs": "^6.5.3 || ^7.4.0" } }, - "node_modules/@angular-devkit/build-angular/node_modules/lmdb": { - "version": "3.5.1", - "integrity": "sha512-NYHA0MRPjvNX+vSw8Xxg6FLKxzAG+e7Pt8RqAQA/EehzHVXq9SxDqJIN3JL1hK0dweb884y8kIh6rkWvPyg9Wg==", - "dev": true, - "hasInstallScript": true, + "node_modules/@angular/compiler": { + "version": "21.2.26", + "integrity": "sha512-awrZjb8ok4TE871RVCvVkPfO63vmW0whqslXQ1s5sSFzlYoYDGxQznVbp0PoDiYU+QpoxMK50Lec/J2l0+0fjw==", "license": "MIT", - "optional": true, - "peer": true, "dependencies": { - "@harperfast/extended-iterable": "^1.0.3", - "msgpackr": "^1.11.2", - "node-addon-api": "^6.1.0", - "node-gyp-build-optional-packages": "5.2.2", - "ordered-binary": "^1.5.3", - "weak-lru-cache": "^1.2.2" - }, - "bin": { - "download-lmdb-prebuilds": "bin/download-prebuilds.js" + "tslib": "^2.3.0" }, - "optionalDependencies": { - "@lmdb/lmdb-darwin-arm64": "3.5.1", - "@lmdb/lmdb-darwin-x64": "3.5.1", - "@lmdb/lmdb-linux-arm": "3.5.1", - "@lmdb/lmdb-linux-arm64": "3.5.1", - "@lmdb/lmdb-linux-x64": "3.5.1", - "@lmdb/lmdb-win32-arm64": "3.5.1", - "@lmdb/lmdb-win32-x64": "3.5.1" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/magic-string": { - "version": "0.30.21", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, - "node_modules/@angular-devkit/build-angular/node_modules/node-addon-api": { - "version": "6.1.0", - "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, - "node_modules/@angular-devkit/build-angular/node_modules/rolldown": { - "version": "1.0.0-rc.4", - "integrity": "sha512-V2tPDUrY3WSevrvU2E41ijZlpF+5PbZu4giH+VpNraaadsJGHa4fR6IFwsocVwEXDoAdIv5qgPPxgrvKAOIPtA==", + "node_modules/@angular/compiler-cli": { + "version": "21.2.26", + "integrity": "sha512-W/S9tImJg9IqEQmkP1yPY2LD4D6/2uSMvg2eHZuTg8zOq3YrhzJINlCKpRy/wp9oWsKxlDKef6tddMDiNa32Hw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@oxc-project/types": "=0.113.0", - "@rolldown/pluginutils": "1.0.0-rc.4" + "@babel/core": "7.29.7", + "@jridgewell/sourcemap-codec": "^1.4.14", + "chokidar": "^5.0.0", + "convert-source-map": "^1.5.1", + "reflect-metadata": "^0.2.0", + "semver": "^7.0.0", + "tslib": "^2.3.0", + "yargs": "^18.0.0" }, "bin": { - "rolldown": "bin/cli.mjs" + "ng-xi18n": "bundles/src/bin/ng_xi18n.js", + "ngc": "bundles/src/bin/ngc.js" }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, - "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.4", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.4", - "@rolldown/binding-darwin-x64": "1.0.0-rc.4", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.4", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.4", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.4", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.4", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.4", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.4", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.4", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.4", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.4", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.4" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/undici": { - "version": "7.29.1", - "integrity": "sha512-RYONW2MeafgYlkVOKYKkA/Ag7BmXqgIWCa8t1m0JcxrQg9pI9lEqRhAOruOBCbAohOa/gkCF+iPi9hrgvTzu6Q==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=20.18.1" + "peerDependencies": { + "@angular/compiler": "21.2.26", + "typescript": ">=5.9 <6.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@angular-devkit/build-angular/node_modules/watchpack": { - "version": "2.5.1", - "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", - "dev": true, + "node_modules/@angular/core": { + "version": "21.2.26", + "integrity": "sha512-EXALAnHRhhBj9WAYend5IIpFlpqPq+HM3OrHxBiWNQoqxOfFKnmRKGERLlX3FEDSzWQt+iZcZHpd2569Xo0pMA==", "license": "MIT", - "peer": true, "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" + "tslib": "^2.3.0" }, "engines": { - "node": ">=10.13.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/compiler": "21.2.26", + "rxjs": "^6.5.3 || ^7.4.0", + "zone.js": "~0.15.0 || ~0.16.0" + }, + "peerDependenciesMeta": { + "@angular/compiler": { + "optional": true + }, + "zone.js": { + "optional": true + } } }, - "node_modules/@angular-devkit/build-webpack": { - "version": "0.2102.26", - "integrity": "sha512-a16LANAlLYTNeLkrMwIEXZI1xJKi+ko0oLI+JhCj8/pnmMPX6yHawZQJ2mgKXjBAub+SK783q6RPBxy+Iu2DTQ==", + "node_modules/@angular/forms": { + "version": "21.2.26", + "integrity": "sha512-FnQ8LQu5liO5afYaCeRz0TMJW82oiEWo9h7g/KrvjCsgCHW9kY7uLFc3WxFCppVycl0kZLJtYXprkkRIJfMsbw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@angular-devkit/architect": "0.2102.26", - "rxjs": "7.8.2" + "@standard-schema/spec": "^1.0.0", + "tslib": "^2.3.0" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "webpack": "^5.30.0", - "webpack-dev-server": "^5.0.2" + "@angular/common": "21.2.26", + "@angular/core": "21.2.26", + "@angular/platform-browser": "21.2.26", + "rxjs": "^6.5.3 || ^7.4.0" } }, - "node_modules/@angular-devkit/core": { + "node_modules/@angular/platform-browser": { "version": "21.2.26", - "integrity": "sha512-9X6hwij620GDWJtzuZ1awGolx42Zt0kY9IRhyTAE/UyLvIdcj5+eKbKeAv43KR9wbFIHIwWPWZ+R+8ehiOetow==", - "dev": true, + "integrity": "sha512-0yJN5FZX88SqdAnsmZC5E0MGpkQyG4CXjdWN3wTX3vFMsnetvm9MqyZKalxNFneucXV0KLrb8oRma5San3Hmeg==", "license": "MIT", "dependencies": { - "ajv": "8.18.0", - "ajv-formats": "3.0.1", - "jsonc-parser": "3.3.1", - "picomatch": "4.0.4", - "rxjs": "7.8.2", - "source-map": "0.7.6" + "tslib": "^2.3.0" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "chokidar": "^5.0.0" + "@angular/animations": "21.2.26", + "@angular/common": "21.2.26", + "@angular/core": "21.2.26" }, "peerDependenciesMeta": { - "chokidar": { + "@angular/animations": { "optional": true } } }, - "node_modules/@angular-devkit/schematics": { - "version": "21.0.6", - "integrity": "sha512-0Zg4IeBoAjPNtGQobZEH010l1jQThq2lMdQZfqhLj4a3XoVdhGdzo9Xcn+GcA/z0zXy+rtAiqRazsk9mW1VcRA==", - "dev": true, + "node_modules/@angular/platform-browser-dynamic": { + "version": "21.2.26", + "integrity": "sha512-YQOvZbPZZ5JuvCjk+5KCM23qrq93ftPuF8NfGNvSDUll4TX5TnKGaWldN93nl9T76oTa98y1PcRJWrCHiNe+tQ==", "license": "MIT", "dependencies": { - "@angular-devkit/core": "21.0.6", - "jsonc-parser": "3.3.1", - "magic-string": "0.30.19", - "ora": "9.0.0", - "rxjs": "7.8.2" + "tslib": "^2.3.0" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/common": "21.2.26", + "@angular/compiler": "21.2.26", + "@angular/core": "21.2.26", + "@angular/platform-browser": "21.2.26" } }, - "node_modules/@angular-devkit/schematics/node_modules/@angular-devkit/core": { - "version": "21.0.6", - "integrity": "sha512-quhQrvfbs1zzDev2T3PdyXDsTrthi9QjwegqR4HyDZKvYCWzmXLw9KjScqEWWUSKVLhG7GULrmb+tDsxRFG9lg==", + "node_modules/@angular/platform-server": { + "version": "21.2.26", + "integrity": "sha512-fxUB/Ji9Js05sbqZdYC8tHLkQ8JaKYaj6zGoFh5N6oWEcycoVlRwNSbIJGiUBddM6V9fOwUlU2ECjkjc5VJxfw==", "dev": true, "license": "MIT", "dependencies": { - "ajv": "8.17.1", - "ajv-formats": "3.0.1", - "jsonc-parser": "3.3.1", - "picomatch": "4.0.3", - "rxjs": "7.8.2", - "source-map": "0.7.6" + "tslib": "^2.3.0", + "xhr2": "^0.2.0" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "chokidar": "^4.0.0" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } + "@angular/common": "21.2.26", + "@angular/compiler": "21.2.26", + "@angular/core": "21.2.26", + "@angular/platform-browser": "21.2.26", + "rxjs": "^6.5.3 || ^7.4.0" } }, - "node_modules/@angular-devkit/schematics/node_modules/ajv": { - "version": "8.17.1", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "node_modules/@angular/router": { + "version": "21.2.26", + "integrity": "sha512-GSaSVW1y6SxKVAseszFzzREhxRnFbVtjE4w3acQpBg1SjgKflFrJ87U1vh39GagSfi8n0N8sswdZym4jHxet9w==", "dev": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" + "tslib": "^2.3.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/common": "21.2.26", + "@angular/core": "21.2.26", + "@angular/platform-browser": "21.2.26", + "rxjs": "^6.5.3 || ^7.4.0" } }, - "node_modules/@angular-devkit/schematics/node_modules/ansi-regex": { - "version": "6.4.0", - "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", + "node_modules/@angular/ssr": { + "version": "21.2.29", + "integrity": "sha512-PDHioohS6K5MSic9AwJhDXuDEmt7ZOfhrSdtiTa2zM9nPDsVlny8Nc9uYyIcWicc7V+2BA5Td6u34IWBmAoeeg==", "dev": true, "license": "MIT", - "engines": { - "node": ">=12" + "dependencies": { + "tslib": "^2.3.0" }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "peerDependencies": { + "@angular/common": "^21.0.0", + "@angular/core": "^21.0.0", + "@angular/platform-server": "^21.0.0", + "@angular/router": "^21.0.0" + }, + "peerDependenciesMeta": { + "@angular/platform-server": { + "optional": true + } } }, - "node_modules/@angular-devkit/schematics/node_modules/chalk": { - "version": "5.6.2", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "node_modules/@asamuzakjp/css-color": { + "version": "7.1.5", + "integrity": "sha512-jCpAvj8apa7SKHZYogk0rbXB3Kb0hLFrpkWF+Ap+vBA5rirItnpv9NKyAyEuDuFizvl0qwnj6JWR70RGJ0rOlw==", "dev": true, "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" + "peer": true, + "dependencies": { + "@csstools/css-calc": "^3.4.3", + "@csstools/css-color-parser": "^4.2.6", + "@csstools/css-parser-algorithms": "^4.0.2", + "@csstools/css-tokenizer": "^4.0.2", + "lru-cache": "^11.5.3" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" } }, - "node_modules/@angular-devkit/schematics/node_modules/chokidar": { - "version": "4.0.3", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "11.5.3", + "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", "dev": true, - "license": "MIT", - "optional": true, + "license": "BlueOak-1.0.0", "peer": true, - "dependencies": { - "readdirp": "^4.0.1" - }, "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" + "node": "20 || >=22" } }, - "node_modules/@angular-devkit/schematics/node_modules/ora": { - "version": "9.0.0", - "integrity": "sha512-m0pg2zscbYgWbqRR6ABga5c3sZdEon7bSgjnlXC64kxtxLOyjRcbbUkLj7HFyy/FTD+P2xdBWu8snGhYI0jc4A==", + "node_modules/@asamuzakjp/dom-selector": { + "version": "9.2.4", + "integrity": "sha512-YnVzxDxLaqL0t7Q4wfcgHZjg55Wm6y5EaO7Bhu3q9J7wq/8iw3PhsnWiOQhYekx4VexnbkgWpTT6n+QIV7kczw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "chalk": "^5.6.2", - "cli-cursor": "^5.0.0", - "cli-spinners": "^3.2.0", - "is-interactive": "^2.0.0", - "is-unicode-supported": "^2.1.0", - "log-symbols": "^7.0.1", - "stdin-discarder": "^0.2.2", - "string-width": "^8.1.0", - "strip-ansi": "^7.1.2" + "bidi-js": "^1.1.0", + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.5.3" }, "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" } }, - "node_modules/@angular-devkit/schematics/node_modules/picomatch": { - "version": "4.0.3", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { + "version": "11.5.3", + "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", "dev": true, - "license": "MIT", + "license": "BlueOak-1.0.0", + "peer": true, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "node": "20 || >=22" } }, - "node_modules/@angular-devkit/schematics/node_modules/readdirp": { - "version": "4.1.2", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 14.18.0" + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@angular-devkit/schematics/node_modules/stdin-discarder": { - "version": "0.2.2", - "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" } }, - "node_modules/@angular-devkit/schematics/node_modules/strip-ansi": { - "version": "7.2.0", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "node_modules/@aws-rum/web-core": { + "version": "3.2.1", + "integrity": "sha512-i9wZrQYAaJNueBbHJsH04eFn7q+umtOmsm21i9kur2YYCsvGhOFADifbBW1vpv0TfO1nKfHjPMdbvb9AeiKVOA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "@aws-crypto/sha256-js": "^5.2.0", + "@rrweb/record": "2.0.0-alpha.20", + "@smithy/fetch-http-handler": "^5.0.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/querystring-builder": "^4.0.0", + "@smithy/signature-v4": "^5.0.0", + "@smithy/util-hex-encoding": "^3.0.0", + "rrweb": "2.0.0-alpha.4", + "shimmer": "^1.2.1", + "uuid": "^9.0.0", + "web-vitals": "^4.0.0" } }, - "node_modules/@angular/build": { - "version": "21.0.6", - "integrity": "sha512-kn5c5LERVuY8x9eNveeN2m+hUw2doXobqxjFGB1M1YCDNHGT3DBX7DNG8aG0PoFGV6BSbb534ypRny4mhuFh2A==", + "node_modules/@aws-rum/web-slim": { + "version": "3.2.1", + "integrity": "sha512-CbNAuqWFvQ2VlA6m/aWvPylkT0TR+vBIP8RtP1/nnxinJah42vgIb4EbwFX8tAkzWkOIOpAqj0EeqTs9RopsbA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.2100.6", - "@babel/core": "7.28.4", - "@babel/helper-annotate-as-pure": "7.27.3", - "@babel/helper-split-export-declaration": "7.24.7", - "@inquirer/confirm": "5.1.19", - "@vitejs/plugin-basic-ssl": "2.1.0", - "beasties": "0.3.5", - "browserslist": "^4.26.0", - "esbuild": "0.26.0", - "https-proxy-agent": "7.0.6", - "istanbul-lib-instrument": "6.0.3", - "jsonc-parser": "3.3.1", - "listr2": "9.0.5", - "magic-string": "0.30.19", - "mrmime": "2.0.1", - "parse5-html-rewriting-stream": "8.0.0", - "picomatch": "4.0.3", - "piscina": "5.1.3", - "rolldown": "1.0.0-beta.47", - "sass": "1.93.2", - "semver": "7.7.3", - "source-map-support": "0.5.21", - "tinyglobby": "0.2.15", - "undici": "7.16.0", - "vite": "7.2.2", - "watchpack": "2.4.4" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "optionalDependencies": { - "lmdb": "3.4.3" - }, - "peerDependencies": { - "@angular/compiler": "^21.0.0", - "@angular/compiler-cli": "^21.0.0", - "@angular/core": "^21.0.0", - "@angular/localize": "^21.0.0", - "@angular/platform-browser": "^21.0.0", - "@angular/platform-server": "^21.0.0", - "@angular/service-worker": "^21.0.0", - "@angular/ssr": "^21.0.6", - "karma": "^6.4.0", - "less": "^4.2.0", - "ng-packagr": "^21.0.0", - "postcss": "^8.4.0", - "tailwindcss": "^2.0.0 || ^3.0.0 || ^4.0.0", - "tslib": "^2.3.0", - "typescript": ">=5.9 <6.0", - "vitest": "^4.0.8" - }, - "peerDependenciesMeta": { - "@angular/core": { - "optional": true - }, - "@angular/localize": { - "optional": true - }, - "@angular/platform-browser": { - "optional": true - }, - "@angular/platform-server": { - "optional": true - }, - "@angular/service-worker": { - "optional": true - }, - "@angular/ssr": { - "optional": true - }, - "karma": { - "optional": true - }, - "less": { - "optional": true - }, - "ng-packagr": { - "optional": true - }, - "postcss": { - "optional": true - }, - "tailwindcss": { - "optional": true - }, - "vitest": { - "optional": true - } + "@aws-rum/web-core": "3.2.1" } }, - "node_modules/@angular/build/node_modules/@angular-devkit/architect": { - "version": "0.2100.6", - "integrity": "sha512-RsCXULyyNNElRHDObVeEBhEOaLK8QaKu+DaYVhfkOOKvVeDoEI0i+0JomzPhGDjazXPPYG8BPOTgSOvhlEeWSA==", + "node_modules/@aws-sdk/types": { + "version": "3.974.6", + "integrity": "sha512-v/clNZzZnDxGyvpHMOGpJKVXFAExJzUNAAjaWGdcx8QAcXLGwTaOkw33p5SHAi0YAioK32xB3hWwOekRVfmfKg==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@angular-devkit/core": "21.0.6", - "rxjs": "7.8.2" + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" + "node": ">=20.0.0" } }, - "node_modules/@angular/build/node_modules/@angular-devkit/core": { - "version": "21.0.6", - "integrity": "sha512-quhQrvfbs1zzDev2T3PdyXDsTrthi9QjwegqR4HyDZKvYCWzmXLw9KjScqEWWUSKVLhG7GULrmb+tDsxRFG9lg==", + "node_modules/@axe-core/playwright": { + "version": "4.14.0", + "integrity": "sha512-SGUhG5PIHd2eMdpgBF2PyQLOhX+qyDR2PT4cmoS4Qk2ak+jX/rrSvst9l0PGv5WTHolKpUMvVmoHpr4cZgf1RQ==", "dev": true, - "license": "MIT", + "license": "MPL-2.0", "dependencies": { - "ajv": "8.17.1", - "ajv-formats": "3.0.1", - "jsonc-parser": "3.3.1", - "picomatch": "4.0.3", - "rxjs": "7.8.2", - "source-map": "0.7.6" + "axe-core": "~4.14.0" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" + "node": ">=22.12.0" }, "peerDependencies": { - "chokidar": "^4.0.0" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } + "playwright-core": ">= 1.0.0" } }, - "node_modules/@angular/build/node_modules/@babel/core": { - "version": "7.28.4", - "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.4", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.4", - "@babel/types": "^7.28.4", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" }, @@ -1566,7 +1429,29 @@ "url": "https://opencollective.com/babel" } }, - "node_modules/@angular/build/node_modules/@babel/core/node_modules/semver": { + "node_modules/@babel/core/node_modules/@babel/generator": { + "version": "7.29.8", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/core/node_modules/semver": { "version": "6.3.1", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, @@ -1575,1387 +1460,1213 @@ "semver": "bin/semver.js" } }, - "node_modules/@angular/build/node_modules/@esbuild/aix-ppc64": { - "version": "0.26.0", - "integrity": "sha512-hj0sKNCQOOo2fgyII3clmJXP28VhgDfU5iy3GNHlWO76KG6N7x4D9ezH5lJtQTG+1J6MFDAJXC1qsI+W+LvZoA==", - "cpu": [ - "ppc64" - ], + "node_modules/@babel/generator": { + "version": "7.29.1", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "aix" - ], + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@angular/build/node_modules/@esbuild/android-arm": { - "version": "0.26.0", - "integrity": "sha512-C0hkDsYNHZkBtPxxDx177JN90/1MiCpvBNjz1f5yWJo1+5+c5zr8apjastpEG+wtPjo9FFtGG7owSsAxyKiHxA==", - "cpu": [ - "arm" - ], + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "dependencies": { + "@babel/types": "^7.27.3" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@angular/build/node_modules/@esbuild/android-arm64": { - "version": "0.26.0", - "integrity": "sha512-DDnoJ5eoa13L8zPh87PUlRd/IyFaIKOlRbxiwcSbeumcJ7UZKdtuMCHa1Q27LWQggug6W4m28i4/O2qiQQ5NZQ==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@angular/build/node_modules/@esbuild/android-x64": { - "version": "0.26.0", - "integrity": "sha512-bKDkGXGZnj0T70cRpgmv549x38Vr2O3UWLbjT2qmIkdIWcmlg8yebcFWoT9Dku7b5OV3UqPEuNKRzlNhjwUJ9A==", - "cpu": [ - "x64" - ], + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@angular/build/node_modules/@esbuild/darwin-arm64": { - "version": "0.26.0", - "integrity": "sha512-6Z3naJgOuAIB0RLlJkYc81An3rTlQ/IeRdrU3dOea8h/PvZSgitZV+thNuIccw0MuK1GmIAnAmd5TrMZad8FTQ==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.29.7", + "integrity": "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "peer": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/traverse": "^7.29.7", + "semver": "^6.3.1" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@angular/build/node_modules/@esbuild/darwin-x64": { - "version": "0.26.0", - "integrity": "sha512-OPnYj0zpYW0tHusMefyaMvNYQX5pNQuSsHFTHUBNp3vVXupwqpxofcjVsUx11CQhGVkGeXjC3WLjh91hgBG2xw==", - "cpu": [ - "x64" - ], + "node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.29.7", + "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "peer": true, + "dependencies": { + "@babel/types": "^7.29.7" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@angular/build/node_modules/@esbuild/freebsd-arm64": { - "version": "0.26.0", - "integrity": "sha512-jix2fa6GQeZhO1sCKNaNMjfj5hbOvoL2F5t+w6gEPxALumkpOV/wq7oUBMHBn2hY2dOm+mEV/K+xfZy3mrsxNQ==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@angular/build/node_modules/@esbuild/freebsd-x64": { - "version": "0.26.0", - "integrity": "sha512-tccJaH5xHJD/239LjbVvJwf6T4kSzbk6wPFerF0uwWlkw/u7HL+wnAzAH5GB2irGhYemDgiNTp8wJzhAHQ64oA==", - "cpu": [ - "x64" - ], + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.29.10", + "integrity": "sha512-/YY9soQuWa8TURtHXHT+GbklpbAA+dZErxFGHIr6qgDg96xuGWrTAs1+MfjgVJPF0YLQsOVsOYs8lhgsPbOinQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], + "peer": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "regexpu-core": "^6.5.3", + "semver": "^6.3.1" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@angular/build/node_modules/@esbuild/linux-arm": { - "version": "0.26.0", - "integrity": "sha512-JY8NyU31SyRmRpuc5W8PQarAx4TvuYbyxbPIpHAZdr/0g4iBr8KwQBS4kiiamGl2f42BBecHusYCsyxi7Kn8UQ==", - "cpu": [ - "arm" - ], + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.29.7", + "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "peer": true, + "dependencies": { + "@babel/types": "^7.29.7" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@angular/build/node_modules/@esbuild/linux-arm64": { - "version": "0.26.0", - "integrity": "sha512-IMJYN7FSkLttYyTbsbme0Ra14cBO5z47kpamo16IwggzzATFY2lcZAwkbcNkWiAduKrTgFJP7fW5cBI7FzcuNQ==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { + "version": "6.3.1", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@angular/build/node_modules/@esbuild/linux-ia32": { - "version": "0.26.0", - "integrity": "sha512-XITaGqGVLgk8WOHw8We9Z1L0lbLFip8LyQzKYFKO4zFo1PFaaSKsbNjvkb7O8kEXytmSGRkYpE8LLVpPJpsSlw==", - "cpu": [ - "ia32" - ], + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.8", + "integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@angular/build/node_modules/@esbuild/linux-loong64": { - "version": "0.26.0", - "integrity": "sha512-MkggfbDIczStUJwq9wU7gQ7kO33d8j9lWuOCDifN9t47+PeI+9m2QVh51EI/zZQ1spZtFMC1nzBJ+qNGCjJnsg==", - "cpu": [ - "loong64" - ], + "peer": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "debug": "^4.4.3", + "lodash.debounce": "^4.0.8", + "resolve": "^1.22.11" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@angular/build/node_modules/@esbuild/linux-mips64el": { - "version": "0.26.0", - "integrity": "sha512-fUYup12HZWAeccNLhQ5HwNBPr4zXCPgUWzEq2Rfw7UwqwfQrFZ0SR/JljaURR8xIh9t+o1lNUFTECUTmaP7yKA==", - "cpu": [ - "mips64el" - ], + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.29.7", + "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "peer": true, + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@angular/build/node_modules/@esbuild/linux-ppc64": { - "version": "0.26.0", - "integrity": "sha512-MzRKhM0Ip+//VYwC8tialCiwUQ4G65WfALtJEFyU0GKJzfTYoPBw5XNWf0SLbCUYQbxTKamlVwPmcw4DgZzFxg==", - "cpu": [ - "ppc64" - ], + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@angular/build/node_modules/@esbuild/linux-riscv64": { - "version": "0.26.0", - "integrity": "sha512-QhCc32CwI1I4Jrg1enCv292sm3YJprW8WHHlyxJhae/dVs+KRWkbvz2Nynl5HmZDW/m9ZxrXayHzjzVNvQMGQA==", - "cpu": [ - "riscv64" - ], + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@angular/build/node_modules/@esbuild/linux-s390x": { - "version": "0.26.0", - "integrity": "sha512-1D6vi6lfI18aNT1aTf2HV+RIlm6fxtlAp8eOJ4mmnbYmZ4boz8zYDar86sIYNh0wmiLJEbW/EocaKAX6Yso2fw==", - "cpu": [ - "s390x" - ], + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.29.7", + "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "peer": true, + "dependencies": { + "@babel/types": "^7.29.7" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@angular/build/node_modules/@esbuild/linux-x64": { - "version": "0.26.0", - "integrity": "sha512-rnDcepj7LjrKFvZkx+WrBv6wECeYACcFjdNPvVPojCPJD8nHpb3pv3AuR9CXgdnjH1O23btICj0rsp0L9wAnHA==", - "cpu": [ - "x64" - ], + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@angular/build/node_modules/@esbuild/netbsd-arm64": { - "version": "0.26.0", - "integrity": "sha512-FSWmgGp0mDNjEXXFcsf12BmVrb+sZBBBlyh3LwB/B9ac3Kkc8x5D2WimYW9N7SUkolui8JzVnVlWh7ZmjCpnxw==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.29.7", + "integrity": "sha512-16AMiW26DbXWBbr3B8wNozKM0ydMLB892vaOaJW/fPJdnT8vJk5sdkQcU/isqUxyCE0cEoa8wZOcbgDuC4b6Og==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], + "peer": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-wrap-function": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@angular/build/node_modules/@esbuild/netbsd-x64": { - "version": "0.26.0", - "integrity": "sha512-0QfciUDFryD39QoSPUDshj4uNEjQhp73+3pbSAaxjV2qGOEDsM67P7KbJq7LzHoVl46oqhIhJ1S+skKGR7lMXA==", - "cpu": [ - "x64" - ], + "node_modules/@babel/helper-remap-async-to-generator/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.29.7", + "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], + "peer": true, + "dependencies": { + "@babel/types": "^7.29.7" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@angular/build/node_modules/@esbuild/openbsd-arm64": { - "version": "0.26.0", - "integrity": "sha512-vmAK+nHhIZWImwJ3RNw9hX3fU4UGN/OqbSE0imqljNbUQC3GvVJ1jpwYoTfD6mmXmQaxdJY6Hn4jQbLGJKg5Yw==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/helper-replace-supers": { + "version": "7.29.7", + "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], + "peer": true, + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@angular/build/node_modules/@esbuild/openbsd-x64": { - "version": "0.26.0", - "integrity": "sha512-GPXF7RMkJ7o9bTyUsnyNtrFMqgM3X+uM/LWw4CeHIjqc32fm0Ir6jKDnWHpj8xHFstgWDUYseSABK9KCkHGnpg==", - "cpu": [ - "x64" - ], + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.29.7", + "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], + "peer": true, + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@angular/build/node_modules/@esbuild/openharmony-arm64": { - "version": "0.26.0", - "integrity": "sha512-nUHZ5jEYqbBthbiBksbmHTlbb5eElyVfs/s1iHQ8rLBq1eWsd5maOnDpCocw1OM8kFK747d1Xms8dXJHtduxSw==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.24.7", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], + "dependencies": { + "@babel/types": "^7.24.7" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@angular/build/node_modules/@esbuild/sunos-x64": { - "version": "0.26.0", - "integrity": "sha512-TMg3KCTCYYaVO+R6P5mSORhcNDDlemUVnUbb8QkboUtOhb5JWKAzd5uMIMECJQOxHZ/R+N8HHtDF5ylzLfMiLw==", - "cpu": [ - "x64" - ], + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@angular/build/node_modules/@esbuild/win32-arm64": { - "version": "0.26.0", - "integrity": "sha512-apqYgoAUd6ZCb9Phcs8zN32q6l0ZQzQBdVXOofa6WvHDlSOhwCWgSfVQabGViThS40Y1NA4SCvQickgZMFZRlA==", - "cpu": [ - "arm64" - ], + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@angular/build/node_modules/@esbuild/win32-ia32": { - "version": "0.26.0", - "integrity": "sha512-FGJAcImbJNZzLWu7U6WB0iKHl4RuY4TsXEwxJPl9UZLS47agIZuILZEX3Pagfw7I4J3ddflomt9f0apfaJSbaw==", - "cpu": [ - "ia32" - ], + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@angular/build/node_modules/@esbuild/win32-x64": { - "version": "0.26.0", - "integrity": "sha512-WAckBKaVnmFqbEhbymrPK7M086DQMpL1XoRbpmN0iW8k5JSXjDRQBhcZNa0VweItknLq9eAeCL34jK7/CDcw7A==", - "cpu": [ - "x64" - ], + "node_modules/@babel/helper-wrap-function": { + "version": "7.29.7", + "integrity": "sha512-iES0Skag9ERIF68aXadpO6dbXa03mNWK3sEqJaMnLNs/eC3l0lkImdfoy6Y09/SfkpawdAB4RjQ7PVA7TcVGdw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "peer": true, + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@angular/build/node_modules/ajv": { - "version": "8.17.1", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "node_modules/@babel/helpers": { + "version": "7.29.10", + "integrity": "sha512-EMCx3AGZ3WfAl6ht7AyQoPm2Lh+BGYEMdc4Q6Hf7uSCDOZFuLq44qNwL+iaim8VlyYhRAo4X43+weadK8ZB4Yg==", "dev": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@angular/build/node_modules/chokidar": { - "version": "4.0.3", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "node_modules/@babel/parser": { + "version": "7.29.9", + "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==", "dev": true, "license": "MIT", "dependencies": { - "readdirp": "^4.0.1" + "@babel/types": "^7.29.8" }, - "engines": { - "node": ">= 14.16.0" + "bin": { + "parser": "bin/babel-parser.js" }, - "funding": { - "url": "https://paulmillr.com/funding/" + "engines": { + "node": ">=6.0.0" } }, - "node_modules/@angular/build/node_modules/convert-source-map": { - "version": "2.0.0", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@angular/build/node_modules/esbuild": { - "version": "0.26.0", - "integrity": "sha512-3Hq7jri+tRrVWha+ZeIVhl4qJRha/XjRNSopvTsOaCvfPHrflTYTcUFcEjMKdxofsXXsdc4zjg5NOTnL4Gl57Q==", + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.29.7", + "integrity": "sha512-j8SrR0zLZrRsC09DlszEx8FpMiwukKffYXMK0d5LmOglO7vGG6sz/BR/20yHqWH+Lnn31JTt2PE3hIWNgM2J6w==", "dev": true, - "hasInstallScript": true, "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { - "node": ">=18" + "node": ">=6.9.0" }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.26.0", - "@esbuild/android-arm": "0.26.0", - "@esbuild/android-arm64": "0.26.0", - "@esbuild/android-x64": "0.26.0", - "@esbuild/darwin-arm64": "0.26.0", - "@esbuild/darwin-x64": "0.26.0", - "@esbuild/freebsd-arm64": "0.26.0", - "@esbuild/freebsd-x64": "0.26.0", - "@esbuild/linux-arm": "0.26.0", - "@esbuild/linux-arm64": "0.26.0", - "@esbuild/linux-ia32": "0.26.0", - "@esbuild/linux-loong64": "0.26.0", - "@esbuild/linux-mips64el": "0.26.0", - "@esbuild/linux-ppc64": "0.26.0", - "@esbuild/linux-riscv64": "0.26.0", - "@esbuild/linux-s390x": "0.26.0", - "@esbuild/linux-x64": "0.26.0", - "@esbuild/netbsd-arm64": "0.26.0", - "@esbuild/netbsd-x64": "0.26.0", - "@esbuild/openbsd-arm64": "0.26.0", - "@esbuild/openbsd-x64": "0.26.0", - "@esbuild/openharmony-arm64": "0.26.0", - "@esbuild/sunos-x64": "0.26.0", - "@esbuild/win32-arm64": "0.26.0", - "@esbuild/win32-ia32": "0.26.0", - "@esbuild/win32-x64": "0.26.0" - } - }, - "node_modules/@angular/build/node_modules/picomatch": { - "version": "4.0.3", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.29.7", + "integrity": "sha512-r8j8escF+U2FUHo0KOhPUdMzUO+jp9fInva6+ACVAF3Y97Ev+5iNZwiqTghmzNeWwDkOPlYuTcfb1vDaoZKmAQ==", "dev": true, "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@angular/build/node_modules/piscina": { - "version": "5.1.3", - "integrity": "sha512-0u3N7H4+hbr40KjuVn2uNhOcthu/9usKhnw5vT3J7ply79v3D3M8naI00el9Klcy16x557VsEkkUQaHCWFXC/g==", + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.29.7", + "integrity": "sha512-GE1TFSiuFeGsCxmYXZl8HwoPrVlwe4rHPFE8weieGKZqnDORK+Ar3vgWMgW+AOxQ6/2TgLSKx9p6W7O4rC6qgQ==", "dev": true, "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, "engines": { - "node": ">=20.x" + "node": ">=6.9.0" }, - "optionalDependencies": { - "@napi-rs/nice": "^1.0.4" + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@angular/build/node_modules/readdirp": { - "version": "4.1.2", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.29.7", + "integrity": "sha512-QQt9qKHZ2sg/kivaLr7lnQr8HVrQDdBNSfCsTjiDxRuX/K5ORyKq+Bu8Xr0cDE3Dfkv0cw28Ve0EKyKMvulkOw==", "dev": true, "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/plugin-transform-optional-chaining": "^7.29.7" + }, "engines": { - "node": ">= 14.18.0" + "node": ">=6.9.0" }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" + "peerDependencies": { + "@babel/core": "^7.13.0" } }, - "node_modules/@angular/build/node_modules/sass": { - "version": "1.93.2", - "integrity": "sha512-t+YPtOQHpGW1QWsh1CHQ5cPIr9lbbGZLZnbihP/D/qZj/yuV68m8qarcV17nvkOX81BCrvzAlq2klCQFZghyTg==", + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.29.7", + "integrity": "sha512-pn6QacGLgvCcwc+syUhKE/qSjV2D1IHDB84RNxWYSt1mW3K/SCtjinZ2p0cETJxAWBjPy3K/1lHwG5BjjPxNlw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "chokidar": "^4.0.0", - "immutable": "^5.0.2", - "source-map-js": ">=0.6.2 <2.0.0" - }, - "bin": { - "sass": "sass.js" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { - "node": ">=14.0.0" + "node": ">=6.9.0" }, - "optionalDependencies": { - "@parcel/watcher": "^2.4.1" + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@angular/build/node_modules/semver": { - "version": "7.7.3", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, + "license": "MIT", + "peer": true, "engines": { - "node": ">=10" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@angular/cli": { - "version": "21.0.6", - "integrity": "sha512-nBdSDwOCKB3T5STs4EPVsft73x4Ap9rZPQRJuGk5r5seS26nBsKFE/eeUShTIIqqM9tA/NRLSc080ugPKuHr2Q==", + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/architect": "0.2100.6", - "@angular-devkit/core": "21.0.6", - "@angular-devkit/schematics": "21.0.6", - "@inquirer/prompts": "7.9.0", - "@listr2/prompt-adapter-inquirer": "3.0.5", - "@modelcontextprotocol/sdk": "1.25.2", - "@schematics/angular": "21.0.6", - "@yarnpkg/lockfile": "1.1.0", - "algoliasearch": "5.40.1", - "ini": "5.0.0", - "jsonc-parser": "3.3.1", - "listr2": "9.0.5", - "npm-package-arg": "13.0.1", - "pacote": "21.0.3", - "parse5-html-rewriting-stream": "8.0.0", - "resolve": "1.22.11", - "semver": "7.7.3", - "yargs": "18.0.0", - "zod": "4.1.13" - }, - "bin": { - "ng": "bin/ng.js" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@angular/cli/node_modules/@angular-devkit/architect": { - "version": "0.2100.6", - "integrity": "sha512-RsCXULyyNNElRHDObVeEBhEOaLK8QaKu+DaYVhfkOOKvVeDoEI0i+0JomzPhGDjazXPPYG8BPOTgSOvhlEeWSA==", + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "21.0.6", - "rxjs": "7.8.2" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@angular/cli/node_modules/@angular-devkit/core": { - "version": "21.0.6", - "integrity": "sha512-quhQrvfbs1zzDev2T3PdyXDsTrthi9QjwegqR4HyDZKvYCWzmXLw9KjScqEWWUSKVLhG7GULrmb+tDsxRFG9lg==", + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", "dev": true, "license": "MIT", "dependencies": { - "ajv": "8.17.1", - "ajv-formats": "3.0.1", - "jsonc-parser": "3.3.1", - "picomatch": "4.0.3", - "rxjs": "7.8.2", - "source-map": "0.7.6" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" + "@babel/helper-plugin-utils": "^7.12.13" }, "peerDependencies": { - "chokidar": "^4.0.0" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } + "@babel/core": "^7.0.0-0" } }, - "node_modules/@angular/cli/node_modules/ajv": { - "version": "8.17.1", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", "dev": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" + "@babel/helper-plugin-utils": "^7.14.5" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@angular/cli/node_modules/chokidar": { - "version": "4.0.3", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.29.7", + "integrity": "sha512-/An1OCBN93thpBAGyfsK2pcf0jvju1SAtKkL2Ny++B5Sy6sqgzXDQH1cZxWbF96Wuk+bn41MDA9bLd4VVAw6rw==", "dev": true, "license": "MIT", - "optional": true, "peer": true, "dependencies": { - "readdirp": "^4.0.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">= 14.16.0" + "node": ">=6.9.0" }, - "funding": { - "url": "https://paulmillr.com/funding/" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@angular/cli/node_modules/picomatch": { - "version": "4.0.3", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.29.7", + "integrity": "sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, "engines": { - "node": ">=12" + "node": ">=6.9.0" }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@angular/cli/node_modules/readdirp": { - "version": "4.1.2", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", "dev": true, "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 14.18.0" + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@angular/cli/node_modules/semver": { - "version": "7.7.3", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=10" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@angular/common": { - "version": "21.0.9", - "integrity": "sha512-yU9D5qgZGGhlhaM9p0YZEcFSMq84sZHKUqOjL5a2+bxpT01gO1ZhoOGeyLccClgjaNALH23J9hCvmv05ufhXlw==", + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.29.7", + "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", + "dev": true, "license": "MIT", "dependencies": { - "tslib": "^2.3.0" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": ">=6.9.0" }, "peerDependencies": { - "@angular/core": "21.0.9", - "rxjs": "^6.5.3 || ^7.4.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@angular/compiler": { - "version": "21.0.9", - "integrity": "sha512-wQVaNWZM/iyIggJ6lExxnJG8ProqNp4fDNCinejnJbjiQVH7oLU57uAD153nqe6lHhz9oHVMtHx4qpPh1h/ptQ==", + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, "license": "MIT", "dependencies": { - "tslib": "^2.3.0" + "@babel/helper-plugin-utils": "^7.10.4" }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@angular/compiler-cli": { - "version": "21.0.9", - "integrity": "sha512-yjXdY6PAw9HR3YcWGiGSwPw7sJmnW8ziCqP+5DKJkHDIAHmTWFyxffmWys+cZRDjRuk6GvBmyWemvEjNyfMVMw==", + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "7.28.4", - "@jridgewell/sourcemap-codec": "^1.4.14", - "chokidar": "^4.0.0", - "convert-source-map": "^1.5.1", - "reflect-metadata": "^0.2.0", - "semver": "^7.0.0", - "tslib": "^2.3.0", - "yargs": "^18.0.0" - }, - "bin": { - "ng-xi18n": "bundles/src/bin/ng_xi18n.js", - "ngc": "bundles/src/bin/ngc.js" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { - "@angular/compiler": "21.0.9", - "typescript": ">=5.9 <6.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "@babel/core": "^7.0.0-0" } }, - "node_modules/@angular/compiler-cli/node_modules/@babel/core": { - "version": "7.28.4", - "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.4", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.4", - "@babel/types": "^7.28.4", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" + "@babel/helper-plugin-utils": "^7.10.4" }, - "engines": { - "node": ">=6.9.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@angular/compiler-cli/node_modules/@babel/core/node_modules/convert-source-map": { - "version": "2.0.0", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "node_modules/@angular/compiler-cli/node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@angular/compiler-cli/node_modules/chokidar": { - "version": "4.0.3", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", "dev": true, "license": "MIT", "dependencies": { - "readdirp": "^4.0.1" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { - "node": ">= 14.16.0" + "node": ">=6.9.0" }, - "funding": { - "url": "https://paulmillr.com/funding/" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@angular/compiler-cli/node_modules/readdirp": { - "version": "4.1.2", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, "engines": { - "node": ">= 14.18.0" + "node": ">=6.9.0" }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@angular/core": { - "version": "21.0.9", - "integrity": "sha512-3RAkc0esb5fuerAJcjgv+rdsaOQfjkWIRckyvDFecAFyeouYWn8x3pAp9rDI02PTMnup+qSgZOqPN1mG3BAjgg==", + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.29.7", + "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", + "dev": true, "license": "MIT", "dependencies": { - "tslib": "^2.3.0" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": ">=6.9.0" }, "peerDependencies": { - "@angular/compiler": "21.0.9", - "rxjs": "^6.5.3 || ^7.4.0", - "zone.js": "~0.15.0 || ~0.16.0" - }, - "peerDependenciesMeta": { - "@angular/compiler": { - "optional": true - }, - "zone.js": { - "optional": true - } + "@babel/core": "^7.0.0-0" } }, - "node_modules/@angular/platform-browser": { - "version": "21.0.9", - "integrity": "sha512-NXOiDAlGXex8O1D6ZsHinZsloA/ngvSqcg/g2IzVUtxM/PRKYIyk6DeRnWXx1QCQmUVh96WbcAnFoF2PUj2Z8A==", + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "tslib": "^2.3.0" + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": ">=6.9.0" }, "peerDependencies": { - "@angular/animations": "21.0.9", - "@angular/common": "21.0.9", - "@angular/core": "21.0.9" - }, - "peerDependenciesMeta": { - "@angular/animations": { - "optional": true - } + "@babel/core": "^7.0.0" } }, - "node_modules/@angular/platform-browser-dynamic": { - "version": "21.0.9", - "integrity": "sha512-rWHFG753jqKHED/AvsTGzKevqtWQVfxDKMqSYu/kUVysld1l9mti+kR2+tJ2mYM9xRXYXFoZHXVMXlbNVmnAPg==", - "deprecated": "@angular/platform-browser-dynamic is deprecated. Use `@angular/platform-browser` instead.", + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.29.7", + "integrity": "sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ==", + "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "tslib": "^2.3.0" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": ">=6.9.0" }, "peerDependencies": { - "@angular/common": "21.0.9", - "@angular/compiler": "21.0.9", - "@angular/core": "21.0.9", - "@angular/platform-browser": "21.0.9" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@asamuzakjp/css-color": { - "version": "7.1.5", - "integrity": "sha512-jCpAvj8apa7SKHZYogk0rbXB3Kb0hLFrpkWF+Ap+vBA5rirItnpv9NKyAyEuDuFizvl0qwnj6JWR70RGJ0rOlw==", + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.29.0", + "integrity": "sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@csstools/css-calc": "^3.4.3", - "@csstools/css-color-parser": "^4.2.6", - "@csstools/css-parser-algorithms": "^4.0.2", - "@csstools/css-tokenizer": "^4.0.2", - "lru-cache": "^11.5.3" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.29.0" }, "engines": { - "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { - "version": "11.5.3", - "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.28.6", + "integrity": "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "MIT", "peer": true, + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-remap-async-to-generator": "^7.27.1" + }, "engines": { - "node": "20 || >=22" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@asamuzakjp/dom-selector": { - "version": "9.2.4", - "integrity": "sha512-YnVzxDxLaqL0t7Q4wfcgHZjg55Wm6y5EaO7Bhu3q9J7wq/8iw3PhsnWiOQhYekx4VexnbkgWpTT6n+QIV7kczw==", + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.29.7", + "integrity": "sha512-cUSmjh72N+rN4PrkFlN1dJwNCwjVp5d38/CQrEsFggkD10UiFlBFgdH3tv5dNsLuHY+3S8db2xCHjhZcv5WgvA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "bidi-js": "^1.1.0", - "css-tree": "^3.2.1", - "is-potential-custom-element-name": "^1.0.1", - "lru-cache": "^11.5.3" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { - "version": "11.5.3", - "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.29.7", + "integrity": "sha512-ONyr4+AZhKh8yKWInVxU9AXA9EbsyeLcL6V0dJy6M2/62vuvpGm29zzuymbTpdc451GEpDIdAyPLP3r+P61yKQ==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "MIT", "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, "engines": { - "node": "20 || >=22" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-crypto/sha256-js": { - "version": "5.2.0", - "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.29.7", + "integrity": "sha512-GtcpjFvanPfzNQi3eTitsCqtRRmmqzpy/A+yhTR1HaZo1Ly3EA8ZXxlPyHdR8/IuRMYc3E4wdGBewB2QKQjAaA==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "peer": true, "dependencies": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^2.6.2" + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">=16.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-crypto/util": { - "version": "5.2.0", - "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.222.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-rum/web-core": { - "version": "3.2.1", - "integrity": "sha512-i9wZrQYAaJNueBbHJsH04eFn7q+umtOmsm21i9kur2YYCsvGhOFADifbBW1vpv0TfO1nKfHjPMdbvb9AeiKVOA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-js": "^5.2.0", - "@rrweb/record": "2.0.0-alpha.20", - "@smithy/fetch-http-handler": "^5.0.0", - "@smithy/protocol-http": "^5.0.0", - "@smithy/querystring-builder": "^4.0.0", - "@smithy/signature-v4": "^5.0.0", - "@smithy/util-hex-encoding": "^3.0.0", - "rrweb": "2.0.0-alpha.4", - "shimmer": "^1.2.1", - "uuid": "^9.0.0", - "web-vitals": "^4.0.0" - } - }, - "node_modules/@aws-rum/web-slim": { - "version": "3.2.1", - "integrity": "sha512-CbNAuqWFvQ2VlA6m/aWvPylkT0TR+vBIP8RtP1/nnxinJah42vgIb4EbwFX8tAkzWkOIOpAqj0EeqTs9RopsbA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-rum/web-core": "3.2.1" - } - }, - "node_modules/@aws-sdk/types": { - "version": "3.974.6", - "integrity": "sha512-v/clNZzZnDxGyvpHMOGpJKVXFAExJzUNAAjaWGdcx8QAcXLGwTaOkw33p5SHAi0YAioK32xB3hWwOekRVfmfKg==", + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.29.7", + "integrity": "sha512-kibJgmEdX2iMwsHY2tSZNDgj8PwIlCQz7FK9KuGKO8zsuoUwSEhoNnNVp/emKWrbY4HeO6kkXfdMqRKKKXBm2A==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "peer": true, "dependencies": { - "@smithy/types": "^4.19.0", - "tslib": "^2.6.2" + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" } }, - "node_modules/@babel/code-frame": { + "node_modules/@babel/plugin-transform-classes": { "version": "7.29.7", - "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "integrity": "sha512-qV0OGGBVacduzQHE649JyCneOFI/maT+YKsO+K4Yi3xv2wTPNjM/W2o2gdzMwEAZz7fXNTHAe0NcSg30bIN69g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.29.7", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/compat-data": { + "node_modules/@babel/plugin-transform-classes/node_modules/@babel/helper-annotate-as-pure": { "version": "7.29.7", - "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", "dev": true, "license": "MIT", + "peer": true, + "dependencies": { + "@babel/types": "^7.29.7" + }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/core": { + "node_modules/@babel/plugin-transform-computed-properties": { "version": "7.29.7", - "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "integrity": "sha512-RK7/IyU5phpuCdBAuig5VkzG/EnbDaui5SQGdU9BFrHdV+mV4cUjLMQ9lJDjLNtWHsqtiefpGZUXQP2BiTYMsA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.7", - "@babel/helper-compilation-targets": "^7.29.7", - "@babel/helper-module-transforms": "^7.29.7", - "@babel/helpers": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/template": "^7.29.7", - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/template": "^7.29.7" }, "engines": { "node": ">=6.9.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/core/node_modules/@babel/generator": { - "version": "7.29.8", - "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.29.7", + "integrity": "sha512-iPX8aD6H9zV5s7ZsqTdNocPN/MGQ5sSMnElKrktxjJRMnB2jN/1p2+R7GkfD6CAYoVFqy5A4XnSIUeGgJzIWpg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@babel/parser": "^7.29.8", - "@babel/types": "^7.29.8", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/core/node_modules/convert-source-map": { - "version": "2.0.0", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/generator": { - "version": "7.29.1", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.29.7", + "integrity": "sha512-3qc18hsD2RdZiyJNDNc7HQpv6xbncwh8FYtxNFFzclSyh/trPD9KkVR9BDECUjDLvb7yJVF15GfYUuC+LMkkiQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.27.3", - "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.29.7", + "integrity": "sha512-6IvRRriEMqnBwD6chtxdLpMYCHWEzN+oL5cyQtjykya19UgzbmKhxmhZgKC/LHxS2nYr9Q/qYPZ5Lr6jOL9+yQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@babel/types": "^7.27.3" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-compilation-targets": { + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { "version": "7.29.7", - "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "integrity": "sha512-2wiIyo2BjtgU7HufSeDnL9L2O7zr8jmhFKuSr65VpRkUiRKRNpb0mdlk56+XPPKoIrfHqzbMuglDvZun0RISsA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@babel/compat-data": "^7.29.7", - "@babel/helper-validator-option": "^7.29.7", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-create-class-features-plugin": { + "node_modules/@babel/plugin-transform-dynamic-import": { "version": "7.29.7", - "integrity": "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==", + "integrity": "sha512-giOlEm/EFjfjr+te9NsdjkUo2v4f8rS/SXPumRVHAtbNcyNlvtREkU1dZzaIDclNpnaVhlCqRdFKhJBjBikzLg==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.29.7", - "@babel/helper-member-expression-to-functions": "^7.29.7", - "@babel/helper-optimise-call-expression": "^7.29.7", - "@babel/helper-replace-supers": "^7.29.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", - "@babel/traverse": "^7.29.7", - "semver": "^6.3.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/helper-annotate-as-pure": { + "node_modules/@babel/plugin-transform-explicit-resource-management": { "version": "7.29.7", - "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", + "integrity": "sha512-Rstj7coNz8sE+7Ju7ihpHLI564lsK5pUpNNlvptCIC/16E/S5hbl6n3kESPKdNRmqEWlpn5xpS5Q2dvXBsySLw==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@babel/types": "^7.29.7" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-transform-destructuring": "^7.29.7" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { - "version": "6.3.1", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.29.7", + "integrity": "sha512-zFpMOTLZBdW5LfObqcSbL6kefg4R4eLdmvS0wbN9M6D5Mym/sKm9toOoWyVOa+xDjvCnuWcHls2YonXwHvH3CQ==", "dev": true, - "license": "ISC", + "license": "MIT", "peer": true, - "bin": { - "semver": "bin/semver.js" + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.29.10", - "integrity": "sha512-/YY9soQuWa8TURtHXHT+GbklpbAA+dZErxFGHIr6qgDg96xuGWrTAs1+MfjgVJPF0YLQsOVsOYs8lhgsPbOinQ==", + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.29.7", + "integrity": "sha512-24B2nOy2TeJSMheqwPD4DDQOV/elLSIlKxjZt4i05H5AgdPdWR3n18HnNrcJ+j76WJd9gbwb9jPjNYUy6RautA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.29.7", - "regexpu-core": "^6.5.3", - "semver": "^6.3.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/@babel/helper-annotate-as-pure": { + "node_modules/@babel/plugin-transform-for-of": { "version": "7.29.7", - "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", + "integrity": "sha512-zeSIHh0+E1Um1WJRXCFlHQYu2ieJNdivLLjlBEp+dIBu3S51n+SZZmIXjxnItw6pz56Cn+KvK68BIBVsxq2JiQ==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@babel/types": "^7.29.7" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { - "version": "6.3.1", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "peer": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.8", - "integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==", + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.29.7", + "integrity": "sha512-otRWaHXE6fbAGkePvaj/kvs3HsqXfPhlnzwSOlnFgbqCPMd975dW+4wZ00WFBt+/YlBGcJwNrARQTOJOb4ZrIg==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "debug": "^4.4.3", - "lodash.debounce": "^4.0.8", - "resolve": "^1.22.11" + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-globals": { + "node_modules/@babel/plugin-transform-json-strings": { "version": "7.29.7", - "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "integrity": "sha512-RRnE2+eon1rJAq8MnoF1b5kTpY1vU88twHcvcKMrsqP/jxIRqDVs9iJB5fqPuqyeFAW0wJo4MlUIPpQCq/aRsg==", "dev": true, "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-member-expression-to-functions": { + "node_modules/@babel/plugin-transform-literals": { "version": "7.29.7", - "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==", + "integrity": "sha512-DZ/oLP21ZuWx1vKqnoNv6/tvEK48AQOBRai40CX9dTjGluvT/YZCyY3rryDtyUqCEoyNroy5KKPwX2iQCiRvyw==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-module-imports": { + "node_modules/@babel/plugin-transform-logical-assignment-operators": { "version": "7.29.7", - "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "integrity": "sha512-A0H91hh6W8MFRkp5TqJmMr39jzGD1A1E1Ysiv2O06Sfbhkapm+XyIzxWCEh5kqwOZ1/8QZ0dY3SeQ7XBqfJd5Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-module-transforms": { + "node_modules/@babel/plugin-transform-member-expression-literals": { "version": "7.29.7", - "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "integrity": "sha512-hl1kwFZCCiDyfH25Xmco9jTrkPgnS9pmOzSG7W5I4SaGbLeqKv417hcU2RKmaxoPEgsoJh7ZPOrnPGq99bHoUg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-module-imports": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7", - "@babel/traverse": "^7.29.7" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-optimise-call-expression": { + "node_modules/@babel/plugin-transform-modules-amd": { "version": "7.29.7", - "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==", + "integrity": "sha512-fxtQoH3m5ywUSIfaH0FGCzWu4McsYon5bD3K4XnskC7f+OyQMj7rsOMi4NvvmJ83WwBAg4UCe+ov4VZlqEvyew==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@babel/types": "^7.29.7" + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-plugin-utils": { + "node_modules/@babel/plugin-transform-modules-commonjs": { "version": "7.29.7", - "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "integrity": "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==", "dev": true, "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.29.7", - "integrity": "sha512-16AMiW26DbXWBbr3B8wNozKM0ydMLB892vaOaJW/fPJdnT8vJk5sdkQcU/isqUxyCE0cEoa8wZOcbgDuC4b6Og==", + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.29.8", + "integrity": "sha512-6iSnEK0zlkLKU4heofK/AdmRD4e2SHVpJMtrwnTCzhnaM98ria4rTrOXBBi45BTTYnJtO8txnPsX4fChYXkmeA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.29.7", - "@babel/helper-wrap-function": "^7.29.7", - "@babel/traverse": "^7.29.7" + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.8" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-remap-async-to-generator/node_modules/@babel/helper-annotate-as-pure": { + "node_modules/@babel/plugin-transform-modules-umd": { "version": "7.29.7", - "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", + "integrity": "sha512-B4UkaTK3QpgCwJnrxKfMPKdo92CN7OKXAlpAAnM3UPu0Q0lCCk57ylA9AJbRy2v8dDKOPAAWcoR6CMyeoHwRCA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@babel/types": "^7.29.7" + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-replace-supers": { + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { "version": "7.29.7", - "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==", + "integrity": "sha512-vuFoLwr4qnv2xbZ16SQd6uPcH5FNrLHhk/Jzo++0XJFcaDsr4gjJVg6j398oMHiC+83k/GiBzviwF5KBJkPUtQ==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.29.7", - "@babel/helper-optimise-call-expression": "^7.29.7", - "@babel/traverse": "^7.29.7" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -2964,192 +2675,197 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "node_modules/@babel/plugin-transform-new-target": { "version": "7.29.7", - "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", + "integrity": "sha512-fEo41GmsOUhOBlw8ioo6zvjX5Xc2Lqkzlyfqbpsk3eB6TReV18uhxZ0esfEokVbY2+PVJAQHNKxER6lGrzNd3A==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.7", - "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.29.7", + "integrity": "sha512-idmp1dFaekP9GbcMvG24Kvw2BfhFZjHnNJCkV4WuIY4PskJzwI3f1N5OdgYke38T7rftO6ERulFRn2cFeZwRkg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@babel/types": "^7.24.7" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-string-parser": { + "node_modules/@babel/plugin-transform-numeric-separator": { "version": "7.29.7", - "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "integrity": "sha512-zR7fv/z14OjgHl4AgRtkDBvBMhIzCxqV/qN/2BCRC7LjFwvuzjYe7gDWxC4Wl/SNsLM6SE1IWvRPYMgSJaUvNw==", "dev": true, "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-validator-identifier": { + "node_modules/@babel/plugin-transform-object-rest-spread": { "version": "7.29.7", - "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "integrity": "sha512-Ld98jn4c0smUywL57m7SgsHq3OpThOa6LqZJif3G6jYOovPleoFhVrBJ1WegRApSFB2wu4+RelAj9AC9G08Z4A==", "dev": true, "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-transform-destructuring": "^7.29.7", + "@babel/plugin-transform-parameters": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-validator-option": { + "node_modules/@babel/plugin-transform-object-super": { "version": "7.29.7", - "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "integrity": "sha512-Ea/diGcw0twB5IlZPO5sgET6fJsLJqPABqTuFWIR+iMPGPZJkATEIWx0wa+aEQ5UY1CBQyP/gkAiLEqn1vBiQA==", "dev": true, "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7" + }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-wrap-function": { + "node_modules/@babel/plugin-transform-optional-catch-binding": { "version": "7.29.7", - "integrity": "sha512-iES0Skag9ERIF68aXadpO6dbXa03mNWK3sEqJaMnLNs/eC3l0lkImdfoy6Y09/SfkpawdAB4RjQ7PVA7TcVGdw==", + "integrity": "sha512-sLsyndxK2VwX6yNUOakMb7Sh553ZTe/vVM1XJ+9Z5aW1ytsc8xOIwmyk05NNjN60vkc5/KqoTH6hB4V41LJhng==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@babel/template": "^7.29.7", - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helpers": { - "version": "7.29.10", - "integrity": "sha512-EMCx3AGZ3WfAl6ht7AyQoPm2Lh+BGYEMdc4Q6Hf7uSCDOZFuLq44qNwL+iaim8VlyYhRAo4X43+weadK8ZB4Yg==", + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.29.7", + "integrity": "sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.8" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "engines": { "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.9", - "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.8" - }, - "bin": { - "parser": "bin/babel-parser.js" }, - "engines": { - "node": ">=6.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "node_modules/@babel/plugin-transform-parameters": { "version": "7.29.7", - "integrity": "sha512-j8SrR0zLZrRsC09DlszEx8FpMiwukKffYXMK0d5LmOglO7vGG6sz/BR/20yHqWH+Lnn31JTt2PE3hIWNgM2J6w==", + "integrity": "sha512-ZDOBqV/qLYJI0YElr8DcENEyARsFQeESqWXH6gZlghYXuPPjvweuDhP4VyEi4BlUBlLRFZVjxoZDMjxhLW766g==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/traverse": "^7.29.7" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "node_modules/@babel/plugin-transform-private-methods": { "version": "7.29.7", - "integrity": "sha512-r8j8escF+U2FUHo0KOhPUdMzUO+jp9fInva6+ACVAF3Y97Ev+5iNZwiqTghmzNeWwDkOPlYuTcfb1vDaoZKmAQ==", + "integrity": "sha512-/6Rz4DK1ETDEM/bWHsPHcaEe7ZaT1EqSXjtSP/L0DijOYuaUhiRiOKcwpZ8P7zR4xXEHc2ITdiCgBm9Tpyv9ug==", "dev": true, "license": "MIT", "peer": true, "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "node_modules/@babel/plugin-transform-private-property-in-object": { "version": "7.29.7", - "integrity": "sha512-GE1TFSiuFeGsCxmYXZl8HwoPrVlwe4rHPFE8weieGKZqnDORK+Ar3vgWMgW+AOxQ6/2TgLSKx9p6W7O4rC6qgQ==", + "integrity": "sha512-+BNo06dnrzdNNqCm1X6YUaVv0DKk8Q+JYcoZfOkLhYWNCXzlwTSRq8zGWayT1csjcpNXV9CQTBRRbmTLZac5cA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-create-class-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "node_modules/@babel/plugin-transform-private-property-in-object/node_modules/@babel/helper-annotate-as-pure": { "version": "7.29.7", - "integrity": "sha512-QQt9qKHZ2sg/kivaLr7lnQr8HVrQDdBNSfCsTjiDxRuX/K5ORyKq+Bu8Xr0cDE3Dfkv0cw28Ve0EKyKMvulkOw==", + "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", - "@babel/plugin-transform-optional-chaining": "^7.29.7" + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" } }, - "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "node_modules/@babel/plugin-transform-property-literals": { "version": "7.29.7", - "integrity": "sha512-pn6QacGLgvCcwc+syUhKE/qSjV2D1IHDB84RNxWYSt1mW3K/SCtjinZ2p0cETJxAWBjPy3K/1lHwG5BjjPxNlw==", + "integrity": "sha512-bOMRLQuI0A5ZqHq3OWJ89/rXpJ/NJrbVhXiP4zwPGMs6kpcVsuTUNjwoE30K0Qm3mf48a/TnRYYD6vPNqcg6jA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/traverse": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.29.7" }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", - "dev": true, - "license": "MIT", - "peer": true, "engines": { "node": ">=6.9.0" }, @@ -3157,49 +2873,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.29.8", + "integrity": "sha512-0UpIXPtdDtMXfnV2OJAVMLpj3H/92vmkA6lpSRakmycJvj3VUy6Xs1dM8tXRugupykr5WB+LpiVl0J8LMVg2mg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -3208,27 +2889,29 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-import-assertions": { + "node_modules/@babel/plugin-transform-regexp-modifiers": { "version": "7.29.7", - "integrity": "sha512-/An1OCBN93thpBAGyfsK2pcf0jvju1SAtKkL2Ny++B5Sy6sqgzXDQH1cZxWbF96Wuk+bn41MDA9bLd4VVAw6rw==", + "integrity": "sha512-mB5Fs0VWrJ42ZCmc8114v60qetdaUVNkj9PmSZRmanCZM3S9hm0CFRLjRmYIsuXav14l2jvZ+4T8iiCGnhj3nQ==", "dev": true, "license": "MIT", "peer": true, "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-syntax-import-attributes": { + "node_modules/@babel/plugin-transform-reserved-words": { "version": "7.29.7", - "integrity": "sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg==", + "integrity": "sha512-5+YhdpVgmfSmwZyLMftfaiffLRMHjzIRHFHHLdibcSyJm2pasMrKHrO3Ptrt2DRshjvpgjEJJ1zVW14WPq/6QA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, @@ -3239,35 +2922,43 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.29.0", + "integrity": "sha512-jlaRT5dJtMaMCV6fAuLbsQMSwz/QkvaHOHOSXRitGGwSpR1blCY4KUKoyP2tYO8vJcqYe8cEj96cqSztv3uF9w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@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-syntax-json-strings": { - "version": "7.8.3", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { + "version": "6.3.1", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@babel/plugin-syntax-jsx": { + "node_modules/@babel/plugin-transform-shorthand-properties": { "version": "7.29.7", - "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", + "integrity": "sha512-I+WYbGBAiCn7nA6xBrlgPH+MB7HWb4u8pv5S0Pv7OtwNvIFvCCb24YlttKEeUFVurfBCEaOTnuhlqsb7f0Z5Dg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, @@ -3278,85 +2969,47 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "node_modules/@babel/plugin-transform-spread": { + "version": "7.29.8", + "integrity": "sha512-4S9ksMGVWUshvgK0mKfvZky7leuG5/uoFVwMpAomJ8bMoDJiNHRVmc1EglwW/CmGVSqqWpEbXm9FmbRit22qoA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.29.7", + "integrity": "sha512-BCHzNYJGe9l7EpwwDBN/ztlL2NYFFq8hp9ddjtUEM9f2O7S7kKV/lL6Fwo7IF7NSkYhPK2vO+86nIGltA90MsA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "^7.29.7" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.29.7", + "integrity": "sha512-NCSEJ4sLFU2gqAub45HYh4fus2yQ36rr6ei6vpU7NdoJqCpxvEG8E6eJpscGyXP3VHD2Ny+fSXr04k1hoUrFqA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -3365,13 +3018,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.29.7", + "integrity": "sha512-223mNGoTkBiTEWFoK+Q6Go3tueMRclO8vxxxxquNCYuNI4jWOofFKJRRDu6SDrB8Sgo1UEGW9T4GAQ8ZyRso1A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -3380,11 +3034,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-typescript": { + "node_modules/@babel/plugin-transform-unicode-escapes": { "version": "7.29.7", - "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", + "integrity": "sha512-jCfXxSjf94lf4E0hKE0AByxF6F3/pVFqRdUUNkDJhsY0m1ZKjnN6ZYyMeHNpzflxb/0q5b7t3p+BE+SLF1WOtA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, @@ -3395,30 +3050,31 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-unicode-sets-regex": { - "version": "7.18.6", - "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.29.7", + "integrity": "sha512-OgZ+zoAJgZLUCunsTRQ5LAjOywDv5zzZ2/hQ5aMw1pGXyY2rtE8/chXYUmu3AlVHKpm10KEdG9aMwbI/K76ZGw==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-arrow-functions": { + "node_modules/@babel/plugin-transform-unicode-regex": { "version": "7.29.7", - "integrity": "sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ==", + "integrity": "sha512-7D/x/23/d/3VqZ0QA+LGbZMlGwZjztBygSWWWsfTPoQ1oQ6Q1P6Mr3d0kk42XabyUVw+fha3LqdRsFqeKqvCyA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { @@ -3428,50 +3084,100 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.29.0", - "integrity": "sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==", + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.29.7", + "integrity": "sha512-BLOhLht9DOJwIxlmp91wHvkXv1lguuHS3/FwUO8HL1H0u8s4hR1gASVFyilu9iGtcTRYqjTZmlsFFeQletntEg==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-remap-async-to-generator": "^7.27.1", - "@babel/traverse": "^7.29.0" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.28.6", - "integrity": "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==", + "node_modules/@babel/preset-env": { + "version": "7.29.2", + "integrity": "sha512-DYD23veRYGvBFhcTY1iUvJnDNpuqNd/BzBwCvzOTKUnJjKg5kpUBh3/u9585Agdkgj+QuygG7jLfOPWMa2KVNw==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-module-imports": "^7.28.6", + "@babel/compat-data": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-remap-async-to-generator": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.29.7", - "integrity": "sha512-cUSmjh72N+rN4PrkFlN1dJwNCwjVp5d38/CQrEsFggkD10UiFlBFgdH3tv5dNsLuHY+3S8db2xCHjhZcv5WgvA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.28.6", + "@babel/plugin-syntax-import-attributes": "^7.28.6", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.29.0", + "@babel/plugin-transform-async-to-generator": "^7.28.6", + "@babel/plugin-transform-block-scoped-functions": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.28.6", + "@babel/plugin-transform-class-properties": "^7.28.6", + "@babel/plugin-transform-class-static-block": "^7.28.6", + "@babel/plugin-transform-classes": "^7.28.6", + "@babel/plugin-transform-computed-properties": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-dotall-regex": "^7.28.6", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.0", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-explicit-resource-management": "^7.28.6", + "@babel/plugin-transform-exponentiation-operator": "^7.28.6", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-function-name": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.28.6", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.28.6", + "@babel/plugin-transform-modules-systemjs": "^7.29.0", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", + "@babel/plugin-transform-numeric-separator": "^7.28.6", + "@babel/plugin-transform-object-rest-spread": "^7.28.6", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.28.6", + "@babel/plugin-transform-optional-chaining": "^7.28.6", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/plugin-transform-private-methods": "^7.28.6", + "@babel/plugin-transform-private-property-in-object": "^7.28.6", + "@babel/plugin-transform-property-literals": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.29.0", + "@babel/plugin-transform-regexp-modifiers": "^7.28.6", + "@babel/plugin-transform-reserved-words": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-spread": "^7.28.6", + "@babel/plugin-transform-sticky-regex": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-typeof-symbol": "^7.27.1", + "@babel/plugin-transform-unicode-escapes": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.28.6", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.15", + "babel-plugin-polyfill-corejs3": "^0.14.0", + "babel-plugin-polyfill-regenerator": "^0.6.6", + "core-js-compat": "^3.48.0", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -3480,3365 +3186,3298 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.29.7", - "integrity": "sha512-ONyr4+AZhKh8yKWInVxU9AXA9EbsyeLcL6V0dJy6M2/62vuvpGm29zzuymbTpdc451GEpDIdAyPLP3r+P61yKQ==", + "node_modules/@babel/preset-env/node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.14.2", + "integrity": "sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-define-polyfill-provider": "^0.6.8", + "core-js-compat": "^3.48.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.29.7", - "integrity": "sha512-GtcpjFvanPfzNQi3eTitsCqtRRmmqzpy/A+yhTR1HaZo1Ly3EA8ZXxlPyHdR8/IuRMYc3E4wdGBewB2QKQjAaA==", + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.1", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "MIT", + "license": "ISC", "peer": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.29.7", - "integrity": "sha512-kibJgmEdX2iMwsHY2tSZNDgj8PwIlCQz7FK9KuGKO8zsuoUwSEhoNnNVp/emKWrbY4HeO6kkXfdMqRKKKXBm2A==", + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" }, "peerDependencies": { - "@babel/core": "^7.12.0" + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.29.7", - "integrity": "sha512-qV0OGGBVacduzQHE649JyCneOFI/maT+YKsO+K4Yi3xv2wTPNjM/W2o2gdzMwEAZz7fXNTHAe0NcSg30bIN69g==", + "node_modules/@babel/runtime": { + "version": "7.29.2", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", "dev": true, "license": "MIT", "peer": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.29.7", - "@babel/helper-compilation-targets": "^7.29.7", - "@babel/helper-globals": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-replace-supers": "^7.29.7", - "@babel/traverse": "^7.29.7" - }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-classes/node_modules/@babel/helper-annotate-as-pure": { + "node_modules/@babel/template": { "version": "7.29.7", - "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.29.7", - "integrity": "sha512-RK7/IyU5phpuCdBAuig5VkzG/EnbDaui5SQGdU9BFrHdV+mV4cUjLMQ9lJDjLNtWHsqtiefpGZUXQP2BiTYMsA==", + "node_modules/@babel/traverse": { + "version": "7.29.10", + "integrity": "sha512-B3lYAs2nkKskbgQ1nfy8Ma9ao9Q1rntTAQHrzzyDuj+TSO+X3le6PfWwqDvodsb+OumE10eMFfuMCTQSQ5iuIQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/template": "^7.29.7" + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.9", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.29.7", - "integrity": "sha512-iPX8aD6H9zV5s7ZsqTdNocPN/MGQ5sSMnElKrktxjJRMnB2jN/1p2+R7GkfD6CAYoVFqy5A4XnSIUeGgJzIWpg==", + "node_modules/@babel/traverse/node_modules/@babel/generator": { + "version": "7.29.8", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/traverse": "^7.29.7" + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.29.7", - "integrity": "sha512-3qc18hsD2RdZiyJNDNc7HQpv6xbncwh8FYtxNFFzclSyh/trPD9KkVR9BDECUjDLvb7yJVF15GfYUuC+LMkkiQ==", + "node_modules/@babel/types": { + "version": "7.29.8", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.29.7", - "integrity": "sha512-6IvRRriEMqnBwD6chtxdLpMYCHWEzN+oL5cyQtjykya19UgzbmKhxmhZgKC/LHxS2nYr9Q/qYPZ5Lr6jOL9+yQ==", + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@bramus/specificity": { + "version": "2.4.3", + "integrity": "sha512-0GEz8qJWokkUVYQYRukmkSHIFNoBH8QTreeHrAFbvz1XFYc3Y+puhMs8PQ5x8GNugiC2l5jvDBXKLbBIByeSBw==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" + "css-tree": "^3.2.1" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "bin": { + "specificity": "bin/cli.js" } }, - "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.29.7", - "integrity": "sha512-2wiIyo2BjtgU7HufSeDnL9L2O7zr8jmhFKuSr65VpRkUiRKRNpb0mdlk56+XPPKoIrfHqzbMuglDvZun0RISsA==", + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" + "@jridgewell/trace-mapping": "0.3.9" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=12" } }, - "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.29.7", - "integrity": "sha512-giOlEm/EFjfjr+te9NsdjkUo2v4f8rS/SXPumRVHAtbNcyNlvtREkU1dZzaIDclNpnaVhlCqRdFKhJBjBikzLg==", + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@babel/plugin-transform-explicit-resource-management": { - "version": "7.29.7", - "integrity": "sha512-Rstj7coNz8sE+7Ju7ihpHLI564lsK5pUpNNlvptCIC/16E/S5hbl6n3kESPKdNRmqEWlpn5xpS5Q2dvXBsySLw==", + "node_modules/@csstools/color-helpers": { + "version": "6.1.2", + "integrity": "sha512-grhRy3OKmniaAEKXMjua5z/EODX0MSqBGjunw8+j/3HQjOnahs2AGhvEOIYVUWcU6ScApbhLhVrQTX8XqrMrow==", "dev": true, - "license": "MIT", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/plugin-transform-destructuring": "^7.29.7" - }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=20.19.0" } }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.29.7", - "integrity": "sha512-zFpMOTLZBdW5LfObqcSbL6kefg4R4eLdmvS0wbN9M6D5Mym/sKm9toOoWyVOa+xDjvCnuWcHls2YonXwHvH3CQ==", + "node_modules/@csstools/css-calc": { + "version": "3.4.3", + "integrity": "sha512-iex20d8CHVkyvg6B7UKV7uHnI2Bqo9g+EFfT9E0y+GvTvhZ/DwONJ+9aKb1dlqm0ZiGsL5RXjp0fCoJYnkeDjA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "license": "MIT", "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, "engines": { - "node": ">=6.9.0" + "node": ">=20.19.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@csstools/css-parser-algorithms": "^4.0.2", + "@csstools/css-tokenizer": "^4.0.2" } }, - "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.29.7", - "integrity": "sha512-24B2nOy2TeJSMheqwPD4DDQOV/elLSIlKxjZt4i05H5AgdPdWR3n18HnNrcJ+j76WJd9gbwb9jPjNYUy6RautA==", + "node_modules/@csstools/css-color-parser": { + "version": "4.2.6", + "integrity": "sha512-iiPQ3iRWwnJkeEn6RIu6SJPr7hYrLz6XZ9s/QZl+2/LI5KQVjpl2fdmDSZKuD4xP6GMmMPgHFFXg6k1Wkz0Trg==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" + "@csstools/color-helpers": "^6.1.2", + "@csstools/css-calc": "^3.4.3" }, "engines": { - "node": ">=6.9.0" + "node": ">=20.19.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@csstools/css-parser-algorithms": "^4.0.2", + "@csstools/css-tokenizer": "^4.0.2" } }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.29.7", - "integrity": "sha512-zeSIHh0+E1Um1WJRXCFlHQYu2ieJNdivLLjlBEp+dIBu3S51n+SZZmIXjxnItw6pz56Cn+KvK68BIBVsxq2JiQ==", + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.2", + "integrity": "sha512-40cSKyMvK+tq4qz6Awrlye2WGuOKt3FwPgtGg6KTfbHOWNw+Rk1rzbAtZnZ6IBhsY491HLRnDXwoyBAijmmILA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "license": "MIT", "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" - }, "engines": { - "node": ">=6.9.0" + "node": ">=20.19.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@csstools/css-tokenizer": "^4.0.2" } }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.29.7", - "integrity": "sha512-otRWaHXE6fbAGkePvaj/kvs3HsqXfPhlnzwSOlnFgbqCPMd975dW+4wZ00WFBt+/YlBGcJwNrARQTOJOb4ZrIg==", + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.15", + "integrity": "sha512-J0u7HkVl2nzSlhsiTOp4AmwcUQ3D+mGEEKfBy/7To5/y7F2OHwyLrXfrhR0SMgr4p5Lo+eaMVSeai24zUcBIxA==", "dev": true, - "license": "MIT", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "peer": true, - "dependencies": { - "@babel/helper-compilation-targets": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/traverse": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } } }, - "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.29.7", - "integrity": "sha512-RRnE2+eon1rJAq8MnoF1b5kTpY1vU88twHcvcKMrsqP/jxIRqDVs9iJB5fqPuqyeFAW0wJo4MlUIPpQCq/aRsg==", + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.2", + "integrity": "sha512-OoKoR0f76dCY666JlcbhmVTs2drYj1GUXZTYTcbUgJjh9Nv41aFfZ21bPQTERm5+L5cBDo466NltB2lplS5GBw==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "license": "MIT", "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=20.19.0" } }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.29.7", - "integrity": "sha512-DZ/oLP21ZuWx1vKqnoNv6/tvEK48AQOBRai40CX9dTjGluvT/YZCyY3rryDtyUqCEoyNroy5KKPwX2iQCiRvyw==", + "node_modules/@discoveryjs/json-ext": { + "version": "0.6.3", + "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==", "dev": true, "license": "MIT", "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=14.17.0" } }, - "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.29.7", - "integrity": "sha512-A0H91hh6W8MFRkp5TqJmMr39jzGD1A1E1Ysiv2O06Sfbhkapm+XyIzxWCEh5kqwOZ1/8QZ0dY3SeQ7XBqfJd5Q==", + "node_modules/@dual-bundle/import-meta-resolve": { + "version": "4.2.1", + "integrity": "sha512-id+7YRUgoUX6CgV0DtuhirQWodeeA7Lf4i2x71JS/vtA5pRb/hIGWlw+G6MeXvsM+MXrz0VAydTGElX1rAfgPg==", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/JounQin" } }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.29.7", - "integrity": "sha512-hl1kwFZCCiDyfH25Xmco9jTrkPgnS9pmOzSG7W5I4SaGbLeqKv417hcU2RKmaxoPEgsoJh7ZPOrnPGq99bHoUg==", + "node_modules/@e965/xlsx": { + "version": "0.20.3", + "integrity": "sha512-703RN/3OdsRD5mtse2HBX7Um7xwaP9tlswEG6svOtjqokXoX7rJdQj7DyabD2I+xk22RgaIIU+R6BHgkpZGB/w==", "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" + "license": "Apache-2.0", + "bin": { + "xlsx": "bin/xlsx.njs" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=0.8" } }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.29.7", - "integrity": "sha512-fxtQoH3m5ywUSIfaH0FGCzWu4McsYon5bD3K4XnskC7f+OyQMj7rsOMi4NvvmJ83WwBAg4UCe+ov4VZlqEvyew==", + "node_modules/@emnapi/core": { + "version": "1.11.3", + "integrity": "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==", "dev": true, "license": "MIT", + "optional": true, "peer": true, "dependencies": { - "@babel/helper-module-transforms": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@emnapi/wasi-threads": "1.2.3", + "tslib": "^2.4.0" } }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.29.7", - "integrity": "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==", + "node_modules/@emnapi/runtime": { + "version": "1.11.3", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", "dev": true, "license": "MIT", + "optional": true, "peer": true, "dependencies": { - "@babel/helper-module-transforms": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "tslib": "^2.4.0" } }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.29.8", - "integrity": "sha512-6iSnEK0zlkLKU4heofK/AdmRD4e2SHVpJMtrwnTCzhnaM98ria4rTrOXBBi45BTTYnJtO8txnPsX4fChYXkmeA==", + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.3", + "integrity": "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==", "dev": true, "license": "MIT", + "optional": true, "peer": true, "dependencies": { - "@babel/helper-module-transforms": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7", - "@babel/traverse": "^7.29.8" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "tslib": "^2.4.0" } }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.29.7", - "integrity": "sha512-B4UkaTK3QpgCwJnrxKfMPKdo92CN7OKXAlpAAnM3UPu0Q0lCCk57ylA9AJbRy2v8dDKOPAAWcoR6CMyeoHwRCA==", + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "aix" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.29.7", - "integrity": "sha512-vuFoLwr4qnv2xbZ16SQd6uPcH5FNrLHhk/Jzo++0XJFcaDsr4gjJVg6j398oMHiC+83k/GiBzviwF5KBJkPUtQ==", + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.29.7", - "integrity": "sha512-fEo41GmsOUhOBlw8ioo6zvjX5Xc2Lqkzlyfqbpsk3eB6TReV18uhxZ0esfEokVbY2+PVJAQHNKxER6lGrzNd3A==", + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.29.7", - "integrity": "sha512-idmp1dFaekP9GbcMvG24Kvw2BfhFZjHnNJCkV4WuIY4PskJzwI3f1N5OdgYke38T7rftO6ERulFRn2cFeZwRkg==", + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.29.7", - "integrity": "sha512-zR7fv/z14OjgHl4AgRtkDBvBMhIzCxqV/qN/2BCRC7LjFwvuzjYe7gDWxC4Wl/SNsLM6SE1IWvRPYMgSJaUvNw==", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.29.7", - "integrity": "sha512-Ld98jn4c0smUywL57m7SgsHq3OpThOa6LqZJif3G6jYOovPleoFhVrBJ1WegRApSFB2wu4+RelAj9AC9G08Z4A==", + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-compilation-targets": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/plugin-transform-destructuring": "^7.29.7", - "@babel/plugin-transform-parameters": "^7.29.7", - "@babel/traverse": "^7.29.7" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.29.7", - "integrity": "sha512-Ea/diGcw0twB5IlZPO5sgET6fJsLJqPABqTuFWIR+iMPGPZJkATEIWx0wa+aEQ5UY1CBQyP/gkAiLEqn1vBiQA==", + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-replace-supers": "^7.29.7" - }, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.29.7", - "integrity": "sha512-sLsyndxK2VwX6yNUOakMb7Sh553ZTe/vVM1XJ+9Z5aW1ytsc8xOIwmyk05NNjN60vkc5/KqoTH6hB4V41LJhng==", + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.29.7", - "integrity": "sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ==", + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.29.7", - "integrity": "sha512-ZDOBqV/qLYJI0YElr8DcENEyARsFQeESqWXH6gZlghYXuPPjvweuDhP4VyEi4BlUBlLRFZVjxoZDMjxhLW766g==", + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.29.7", - "integrity": "sha512-/6Rz4DK1ETDEM/bWHsPHcaEe7ZaT1EqSXjtSP/L0DijOYuaUhiRiOKcwpZ8P7zR4xXEHc2ITdiCgBm9Tpyv9ug==", + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.29.7", - "integrity": "sha512-+BNo06dnrzdNNqCm1X6YUaVv0DKk8Q+JYcoZfOkLhYWNCXzlwTSRq8zGWayT1csjcpNXV9CQTBRRbmTLZac5cA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.29.7", - "@babel/helper-create-class-features-plugin": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-property-in-object/node_modules/@babel/helper-annotate-as-pure": { - "version": "7.29.7", - "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@babel/types": "^7.29.7" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.29.7", - "integrity": "sha512-bOMRLQuI0A5ZqHq3OWJ89/rXpJ/NJrbVhXiP4zwPGMs6kpcVsuTUNjwoE30K0Qm3mf48a/TnRYYD6vPNqcg6jA==", + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.29.8", - "integrity": "sha512-0UpIXPtdDtMXfnV2OJAVMLpj3H/92vmkA6lpSRakmycJvj3VUy6Xs1dM8tXRugupykr5WB+LpiVl0J8LMVg2mg==", + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-regexp-modifiers": { - "version": "7.29.7", - "integrity": "sha512-mB5Fs0VWrJ42ZCmc8114v60qetdaUVNkj9PmSZRmanCZM3S9hm0CFRLjRmYIsuXav14l2jvZ+4T8iiCGnhj3nQ==", + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.29.7", - "integrity": "sha512-5+YhdpVgmfSmwZyLMftfaiffLRMHjzIRHFHHLdibcSyJm2pasMrKHrO3Ptrt2DRshjvpgjEJJ1zVW14WPq/6QA==", + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-runtime": { - "version": "7.29.0", - "integrity": "sha512-jlaRT5dJtMaMCV6fAuLbsQMSwz/QkvaHOHOSXRitGGwSpR1blCY4KUKoyP2tYO8vJcqYe8cEj96cqSztv3uF9w==", + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "peer": true, - "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" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { - "version": "6.3.1", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "ISC", - "peer": true, - "bin": { - "semver": "bin/semver.js" + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.29.7", - "integrity": "sha512-I+WYbGBAiCn7nA6xBrlgPH+MB7HWb4u8pv5S0Pv7OtwNvIFvCCb24YlttKEeUFVurfBCEaOTnuhlqsb7f0Z5Dg==", + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.29.8", - "integrity": "sha512-4S9ksMGVWUshvgK0mKfvZky7leuG5/uoFVwMpAomJ8bMoDJiNHRVmc1EglwW/CmGVSqqWpEbXm9FmbRit22qoA==", + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" - }, + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.29.7", - "integrity": "sha512-BCHzNYJGe9l7EpwwDBN/ztlL2NYFFq8hp9ddjtUEM9f2O7S7kKV/lL6Fwo7IF7NSkYhPK2vO+86nIGltA90MsA==", + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.29.7", - "integrity": "sha512-NCSEJ4sLFU2gqAub45HYh4fus2yQ36rr6ei6vpU7NdoJqCpxvEG8E6eJpscGyXP3VHD2Ny+fSXr04k1hoUrFqA==", + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "openharmony" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.29.7", - "integrity": "sha512-223mNGoTkBiTEWFoK+Q6Go3tueMRclO8vxxxxquNCYuNI4jWOofFKJRRDu6SDrB8Sgo1UEGW9T4GAQ8ZyRso1A==", + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "sunos" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.29.7", - "integrity": "sha512-jCfXxSjf94lf4E0hKE0AByxF6F3/pVFqRdUUNkDJhsY0m1ZKjnN6ZYyMeHNpzflxb/0q5b7t3p+BE+SLF1WOtA==", + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.29.7", - "integrity": "sha512-OgZ+zoAJgZLUCunsTRQ5LAjOywDv5zzZ2/hQ5aMw1pGXyY2rtE8/chXYUmu3AlVHKpm10KEdG9aMwbI/K76ZGw==", + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.29.7", - "integrity": "sha512-7D/x/23/d/3VqZ0QA+LGbZMlGwZjztBygSWWWsfTPoQ1oQ6Q1P6Mr3d0kk42XabyUVw+fha3LqdRsFqeKqvCyA==", + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.29.7", - "integrity": "sha512-BLOhLht9DOJwIxlmp91wHvkXv1lguuHS3/FwUO8HL1H0u8s4hR1gASVFyilu9iGtcTRYqjTZmlsFFeQletntEg==", + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": ">=6.9.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@babel/preset-env": { - "version": "7.29.2", - "integrity": "sha512-DYD23veRYGvBFhcTY1iUvJnDNpuqNd/BzBwCvzOTKUnJjKg5kpUBh3/u9585Agdkgj+QuygG7jLfOPWMa2KVNw==", + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/compat-data": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", - "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-import-assertions": "^7.28.6", - "@babel/plugin-syntax-import-attributes": "^7.28.6", - "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.27.1", - "@babel/plugin-transform-async-generator-functions": "^7.29.0", - "@babel/plugin-transform-async-to-generator": "^7.28.6", - "@babel/plugin-transform-block-scoped-functions": "^7.27.1", - "@babel/plugin-transform-block-scoping": "^7.28.6", - "@babel/plugin-transform-class-properties": "^7.28.6", - "@babel/plugin-transform-class-static-block": "^7.28.6", - "@babel/plugin-transform-classes": "^7.28.6", - "@babel/plugin-transform-computed-properties": "^7.28.6", - "@babel/plugin-transform-destructuring": "^7.28.5", - "@babel/plugin-transform-dotall-regex": "^7.28.6", - "@babel/plugin-transform-duplicate-keys": "^7.27.1", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.0", - "@babel/plugin-transform-dynamic-import": "^7.27.1", - "@babel/plugin-transform-explicit-resource-management": "^7.28.6", - "@babel/plugin-transform-exponentiation-operator": "^7.28.6", - "@babel/plugin-transform-export-namespace-from": "^7.27.1", - "@babel/plugin-transform-for-of": "^7.27.1", - "@babel/plugin-transform-function-name": "^7.27.1", - "@babel/plugin-transform-json-strings": "^7.28.6", - "@babel/plugin-transform-literals": "^7.27.1", - "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", - "@babel/plugin-transform-member-expression-literals": "^7.27.1", - "@babel/plugin-transform-modules-amd": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.28.6", - "@babel/plugin-transform-modules-systemjs": "^7.29.0", - "@babel/plugin-transform-modules-umd": "^7.27.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0", - "@babel/plugin-transform-new-target": "^7.27.1", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", - "@babel/plugin-transform-numeric-separator": "^7.28.6", - "@babel/plugin-transform-object-rest-spread": "^7.28.6", - "@babel/plugin-transform-object-super": "^7.27.1", - "@babel/plugin-transform-optional-catch-binding": "^7.28.6", - "@babel/plugin-transform-optional-chaining": "^7.28.6", - "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/plugin-transform-private-methods": "^7.28.6", - "@babel/plugin-transform-private-property-in-object": "^7.28.6", - "@babel/plugin-transform-property-literals": "^7.27.1", - "@babel/plugin-transform-regenerator": "^7.29.0", - "@babel/plugin-transform-regexp-modifiers": "^7.28.6", - "@babel/plugin-transform-reserved-words": "^7.27.1", - "@babel/plugin-transform-shorthand-properties": "^7.27.1", - "@babel/plugin-transform-spread": "^7.28.6", - "@babel/plugin-transform-sticky-regex": "^7.27.1", - "@babel/plugin-transform-template-literals": "^7.27.1", - "@babel/plugin-transform-typeof-symbol": "^7.27.1", - "@babel/plugin-transform-unicode-escapes": "^7.27.1", - "@babel/plugin-transform-unicode-property-regex": "^7.28.6", - "@babel/plugin-transform-unicode-regex": "^7.27.1", - "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", - "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.15", - "babel-plugin-polyfill-corejs3": "^0.14.0", - "babel-plugin-polyfill-regenerator": "^0.6.6", - "core-js-compat": "^3.48.0", - "semver": "^6.3.1" + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" }, "engines": { - "node": ">=6.9.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@babel/preset-env/node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.14.2", - "integrity": "sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==", + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.15.0", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.8", - "core-js-compat": "^3.48.0" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@babel/preset-env/node_modules/semver": { - "version": "6.3.1", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/@eslint/eslintrc/node_modules/balanced-match": { + "version": "1.0.2", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, - "license": "ISC", - "peer": true, - "bin": { - "semver": "bin/semver.js" - } + "license": "MIT" }, - "node_modules/@babel/preset-modules": { - "version": "0.1.6-no-external-plugins", - "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.21", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/@babel/runtime": { - "version": "7.29.2", - "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", - "dev": true, - "license": "MIT", - "peer": true, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=6.9.0" + "node": ">= 4" } }, - "node_modules/@babel/template": { - "version": "7.29.7", - "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true, - "license": "MIT", + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=6.9.0" + "node": "*" } }, - "node_modules/@babel/traverse": { - "version": "7.29.10", - "integrity": "sha512-B3lYAs2nkKskbgQ1nfy8Ma9ao9Q1rntTAQHrzzyDuj+TSO+X3le6PfWwqDvodsb+OumE10eMFfuMCTQSQ5iuIQ==", + "node_modules/@eslint/js": { + "version": "8.57.1", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.8", - "@babel/helper-globals": "^7.29.7", - "@babel/parser": "^7.29.9", - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.8", - "debug": "^4.3.1" - }, "engines": { - "node": ">=6.9.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@babel/traverse/node_modules/@babel/generator": { - "version": "7.29.8", - "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "node_modules/@exodus/bytes": { + "version": "1.16.0", + "integrity": "sha512-IcpW84uEn3N7ETtNZMlxKhfl6Pec8rUNGOTBtWbK1FKhJxIFAptZyVrvVRVBimAJxJCgc3PxepxkdWWG4DVzfA==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.8", - "@babel/types": "^7.29.8", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" + "peer": true, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, + "node_modules/@gar/promise-retry": { + "version": "1.0.3", + "integrity": "sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=6.9.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@babel/types": { - "version": "7.29.8", - "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "node_modules/@harperfast/extended-iterable": { + "version": "1.0.3", + "integrity": "sha512-sSAYhQca3rDWtQUHSAPeO7axFIUJOI6hn1gjRC5APVE1a90tuyT8f5WIgRsFhhWA7htNkju2veB9eWL6YHi/Lw==", + "dev": true, + "license": "Apache-2.0", + "optional": true + }, + "node_modules/@hono/node-server": { + "version": "1.19.17", + "integrity": "sha512-dSneS5qhiauZWGDCeK4o695Xd9nUNjviSZCMQrj10eetr8Uln1ucn6bbphOM6UynAMMtNIzZNSpL9vnASJwrPQ==", "dev": true, "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@babel/helper-string-parser": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7" + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" }, "engines": { - "node": ">=6.9.0" + "node": ">=10.10.0" } }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "node_modules/@humanwhocodes/config-array/node_modules/balanced-match": { + "version": "1.0.2", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, "license": "MIT" }, - "node_modules/@bramus/specificity": { - "version": "2.4.3", - "integrity": "sha512-0GEz8qJWokkUVYQYRukmkSHIFNoBH8QTreeHrAFbvz1XFYc3Y+puhMs8PQ5x8GNugiC2l5jvDBXKLbBIByeSBw==", + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.21", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "css-tree": "^3.2.1" - }, - "bin": { - "specificity": "bin/cli.js" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.5", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=12" + "node": "*" } }, - "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@csstools/color-helpers": { - "version": "6.1.2", - "integrity": "sha512-grhRy3OKmniaAEKXMjua5z/EODX0MSqBGjunw8+j/3HQjOnahs2AGhvEOIYVUWcU6ScApbhLhVrQTX8XqrMrow==", + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "peer": true, + "license": "BSD-3-Clause" + }, + "node_modules/@inquirer/ansi": { + "version": "1.0.2", + "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=18" } }, - "node_modules/@csstools/css-calc": { - "version": "3.4.3", - "integrity": "sha512-iex20d8CHVkyvg6B7UKV7uHnI2Bqo9g+EFfT9E0y+GvTvhZ/DwONJ+9aKb1dlqm0ZiGsL5RXjp0fCoJYnkeDjA==", + "node_modules/@inquirer/checkbox": { + "version": "4.3.2", + "integrity": "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], "license": "MIT", - "peer": true, + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, "engines": { - "node": ">=20.19.0" + "node": ">=18" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^4.0.2", - "@csstools/css-tokenizer": "^4.0.2" + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@csstools/css-color-parser": { - "version": "4.2.6", - "integrity": "sha512-iiPQ3iRWwnJkeEn6RIu6SJPr7hYrLz6XZ9s/QZl+2/LI5KQVjpl2fdmDSZKuD4xP6GMmMPgHFFXg6k1Wkz0Trg==", + "node_modules/@inquirer/confirm": { + "version": "5.1.21", + "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], "license": "MIT", - "peer": true, "dependencies": { - "@csstools/color-helpers": "^6.1.2", - "@csstools/css-calc": "^3.4.3" + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" }, "engines": { - "node": ">=20.19.0" + "node": ">=18" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^4.0.2", - "@csstools/css-tokenizer": "^4.0.2" + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@csstools/css-parser-algorithms": { - "version": "4.0.2", - "integrity": "sha512-40cSKyMvK+tq4qz6Awrlye2WGuOKt3FwPgtGg6KTfbHOWNw+Rk1rzbAtZnZ6IBhsY491HLRnDXwoyBAijmmILA==", + "node_modules/@inquirer/core": { + "version": "10.3.2", + "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], "license": "MIT", - "peer": true, + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.3" + }, "engines": { - "node": ">=20.19.0" + "node": ">=18" }, "peerDependencies": { - "@csstools/css-tokenizer": "^4.0.2" + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@csstools/css-syntax-patches-for-csstree": { - "version": "1.1.15", - "integrity": "sha512-J0u7HkVl2nzSlhsiTOp4AmwcUQ3D+mGEEKfBy/7To5/y7F2OHwyLrXfrhR0SMgr4p5Lo+eaMVSeai24zUcBIxA==", + "node_modules/@inquirer/editor": { + "version": "4.2.23", + "integrity": "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "peer": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/external-editor": "^1.0.3", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, "peerDependencies": { - "css-tree": "^3.2.1" + "@types/node": ">=18" }, "peerDependenciesMeta": { - "css-tree": { + "@types/node": { "optional": true } } }, - "node_modules/@csstools/css-tokenizer": { - "version": "4.0.2", - "integrity": "sha512-OoKoR0f76dCY666JlcbhmVTs2drYj1GUXZTYTcbUgJjh9Nv41aFfZ21bPQTERm5+L5cBDo466NltB2lplS5GBw==", + "node_modules/@inquirer/expand": { + "version": "4.0.23", + "integrity": "sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], "license": "MIT", - "peer": true, + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, "engines": { - "node": ">=20.19.0" + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.6.3", - "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==", + "node_modules/@inquirer/external-editor": { + "version": "1.0.3", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", "dev": true, "license": "MIT", - "peer": true, + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" + }, "engines": { - "node": ">=14.17.0" + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@dual-bundle/import-meta-resolve": { - "version": "4.2.1", - "integrity": "sha512-id+7YRUgoUX6CgV0DtuhirQWodeeA7Lf4i2x71JS/vtA5pRb/hIGWlw+G6MeXvsM+MXrz0VAydTGElX1rAfgPg==", + "node_modules/@inquirer/figures": { + "version": "1.0.15", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", "dev": true, "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/JounQin" + "engines": { + "node": ">=18" } }, - "node_modules/@emnapi/core": { - "version": "1.11.3", - "integrity": "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==", + "node_modules/@inquirer/input": { + "version": "4.3.1", + "integrity": "sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==", "dev": true, "license": "MIT", - "optional": true, - "peer": true, "dependencies": { - "@emnapi/wasi-threads": "1.2.3", - "tslib": "^2.4.0" + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@emnapi/runtime": { - "version": "1.11.3", - "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", + "node_modules/@inquirer/number": { + "version": "3.0.23", + "integrity": "sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==", "dev": true, "license": "MIT", - "optional": true, - "peer": true, "dependencies": { - "tslib": "^2.4.0" + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.3", - "integrity": "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==", + "node_modules/@inquirer/password": { + "version": "4.0.23", + "integrity": "sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==", "dev": true, "license": "MIT", - "optional": true, - "peer": true, "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.28.1", - "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@esbuild/android-arm": { - "version": "0.28.1", - "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", - "cpu": [ - "arm" - ], + "node_modules/@inquirer/prompts": { + "version": "7.10.1", + "integrity": "sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "dependencies": { + "@inquirer/checkbox": "^4.3.2", + "@inquirer/confirm": "^5.1.21", + "@inquirer/editor": "^4.2.23", + "@inquirer/expand": "^4.0.23", + "@inquirer/input": "^4.3.1", + "@inquirer/number": "^3.0.23", + "@inquirer/password": "^4.0.23", + "@inquirer/rawlist": "^4.1.11", + "@inquirer/search": "^3.2.2", + "@inquirer/select": "^4.4.2" + }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.28.1", - "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", - "cpu": [ - "arm64" - ], + "node_modules/@inquirer/rawlist": { + "version": "4.1.11", + "integrity": "sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@esbuild/android-x64": { - "version": "0.28.1", - "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", - "cpu": [ - "x64" - ], + "node_modules/@inquirer/search": { + "version": "3.2.2", + "integrity": "sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.28.1", - "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", - "cpu": [ - "arm64" - ], + "node_modules/@inquirer/select": { + "version": "4.4.2", + "integrity": "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.28.1", - "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", - "cpu": [ - "x64" - ], + "node_modules/@inquirer/type": { + "version": "3.0.10", + "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], "engines": { "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.1", - "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", - "cpu": [ - "arm64" - ], + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, "engines": { - "node": ">=18" + "node": ">=12" } }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.28.1", - "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", - "cpu": [ - "x64" - ], + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.4.0", + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], "engines": { - "node": ">=18" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/@esbuild/linux-arm": { - "version": "0.28.1", - "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", - "cpu": [ - "arm" - ], + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">=18" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.28.1", - "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", - "cpu": [ - "arm64" - ], + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, "engines": { - "node": ">=18" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.28.1", - "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", - "cpu": [ - "ia32" - ], + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.2.0", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "ansi-regex": "^6.2.2" + }, "engines": { - "node": ">=18" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.28.1", - "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", - "cpu": [ - "loong64" - ], + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, "engines": { - "node": ">=18" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.28.1", - "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", - "cpu": [ - "mips64el" - ], + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.28.1", - "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", - "cpu": [ - "ppc64" - ], + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, "engines": { - "node": ">=18" + "node": ">=8" } }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.28.1", - "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", - "cpu": [ - "riscv64" - ], + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "dependencies": { + "sprintf-js": "~1.0.2" } }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.28.1", - "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", - "cpu": [ - "s390x" - ], + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, "engines": { - "node": ">=18" + "node": ">=8" } }, - "node_modules/@esbuild/linux-x64": { - "version": "0.28.1", - "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", - "cpu": [ - "x64" - ], + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.15.2", + "integrity": "sha512-6EuL879VkRA+1Cz578mKMiKvjPNEuk6+r1JaFzoSWejZmtf7xWbIyw1e3KkxlkzTIt9Taw6JBhEppG7utc1P+w==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.1", - "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", - "cpu": [ - "arm64" - ], + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], + "dependencies": { + "p-locate": "^4.1.0" + }, "engines": { - "node": ">=18" + "node": ">=8" } }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.28.1", - "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", - "cpu": [ - "x64" - ], + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], + "dependencies": { + "p-try": "^2.0.0" + }, "engines": { - "node": ">=18" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.1", - "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", - "cpu": [ - "arm64" - ], + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], + "dependencies": { + "p-limit": "^2.2.0" + }, "engines": { - "node": ">=18" + "node": ">=8" } }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.1", - "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", - "cpu": [ - "x64" - ], + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], "engines": { - "node": ">=18" + "node": ">=8" } }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.1", - "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", - "cpu": [ - "arm64" - ], + "node_modules/@istanbuljs/schema": { + "version": "0.1.6", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], "engines": { - "node": ">=18" + "node": ">=8" } }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.28.1", - "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", - "cpu": [ - "x64" - ], + "node_modules/@jest/console": { + "version": "30.5.2", + "integrity": "sha512-e8sQC4pCMHPBrPX7Hk8TJr+DM5dkUnImSTNag8SEWFAFAF6xIz4AJvCKapnrdHnZxep4bxw4PhG+ZbimYBMyKw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], + "dependencies": { + "@jest/types": "30.5.1", + "@types/node": "*", + "chalk": "^4.1.2", + "jest-message-util": "30.5.1", + "jest-util": "30.5.1", + "slash": "^3.0.0" + }, "engines": { - "node": ">=18" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.28.1", - "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", - "cpu": [ - "arm64" - ], + "node_modules/@jest/core": { + "version": "30.5.2", + "integrity": "sha512-/jqvFWoJF7LV1a6AUpYMbMm+2yIYsHJfR474H0SCzr9k8tnO/jl9wRg6zSG6lxgf6EozNlCG4amFzWsbZSvvZA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "dependencies": { + "@jest/console": "30.5.2", + "@jest/pattern": "30.5.0", + "@jest/reporters": "30.5.2", + "@jest/test-result": "30.5.2", + "@jest/transform": "30.5.2", + "@jest/types": "30.5.1", + "@types/node": "*", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "exit-x": "^0.2.2", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.11", + "jest-changed-files": "30.5.1", + "jest-config": "30.5.2", + "jest-haste-map": "30.5.1", + "jest-message-util": "30.5.1", + "jest-regex-util": "30.5.0", + "jest-resolve": "30.5.1", + "jest-resolve-dependencies": "30.5.2", + "jest-runner": "30.5.2", + "jest-runtime": "30.5.2", + "jest-snapshot": "30.5.2", + "jest-util": "30.5.1", + "jest-validate": "30.5.1", + "jest-watcher": "30.5.2", + "pretty-format": "30.5.1", + "slash": "^3.0.0" + }, "engines": { - "node": ">=18" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.28.1", - "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", - "cpu": [ - "ia32" - ], + "node_modules/@jest/diff-sequences": { + "version": "30.5.0", + "integrity": "sha512-OsqBjHXCn8cadasoAZBP6nWYvMsRhpMzGXTpxJ5aO04NlbdhIz+FVe3q49l0AwVhsz/cEmIpBes6gAFl1/dWQg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], "engines": { - "node": ">=18" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@esbuild/win32-x64": { - "version": "0.28.1", - "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", - "cpu": [ - "x64" - ], + "node_modules/@jest/environment": { + "version": "30.5.2", + "integrity": "sha512-nwFPOUMbmsjNNRCpnJpB9HbSZu4C07wDDds++5j+j1MezzPLxMPDdbdUVjr2o4RULDOValnDd70taPFI1EtxAg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "dependencies": { + "@jest/fake-timers": "30.5.2", + "@jest/types": "30.5.1", + "@types/node": "*", + "jest-mock": "30.5.2" + }, "engines": { - "node": ">=18" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.10.1", - "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "node_modules/@jest/environment-jsdom-abstract": { + "version": "30.5.2", + "integrity": "sha512-8/P+8hKv7L4mhFoB6gpYLj/fuzXk8PWrde7TDHh8tJz5pE1ktlMbg0y6hDY/V8MfAnispFlPl/vPifowLy3JGQ==", "dev": true, "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^3.4.3" + "@jest/environment": "30.5.2", + "@jest/fake-timers": "30.5.2", + "@jest/types": "30.5.1", + "@types/node": "*", + "jest-mock": "30.5.2", + "jest-util": "30.5.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "@types/jsdom": "*", + "canvas": "^3.0.0", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } } }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "node_modules/@jest/expect": { + "version": "30.5.2", + "integrity": "sha512-2E+1Pg6jJxbRjLNLpciC71iri/3qsKJL222/aXJEFwC0VdUSvfS+JZQQLEVjUYYc8t8pQu2MGE3cF2+bNTYRsg==", "dev": true, "license": "MIT", + "dependencies": { + "expect": "30.5.2", + "jest-snapshot": "30.5.2" + }, "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "node_modules/@jest/expect-utils": { + "version": "30.5.2", + "integrity": "sha512-d/HOLSrJUlBIl6n3LzOa7cjW3xdePm5H5gQw2lixZ/0h157l9sw3mLblzFqnPcTbhDzhRQdBOX3A3KcG73nm5Q==", "dev": true, "license": "MIT", "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" + "@jest/get-type": "30.5.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.15.0", - "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "node_modules/@jest/fake-timers": { + "version": "30.5.2", + "integrity": "sha512-GKk0h0tKT5SpDPxDhPg3r9mm5s8/YCBzVNMOGdRiQXwUiMsjU10mVPlMaQstH6/cu50vOb8N9oQUMETHuwwxKw==", "dev": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "@jest/types": "30.5.1", + "@sinonjs/fake-timers": "^15.4.0", + "@types/node": "*", + "jest-message-util": "30.5.1", + "jest-mock": "30.5.2", + "jest-util": "30.5.1" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@eslint/eslintrc/node_modules/balanced-match": { - "version": "1.0.2", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.21", - "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "node_modules/@jest/get-type": { + "version": "30.5.0", + "integrity": "sha512-9/2VUPitAjmBzbvDvqrxmvB7BzWsBW0WmkkojX1ODuxX1NLGxx9gfaZpHB0z8DtJ9uhGNmZG/VXBhf8uO0OV8Q==", "dev": true, "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@eslint/eslintrc/node_modules/ignore": { - "version": "5.3.2", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "node_modules/@jest/globals": { + "version": "30.5.2", + "integrity": "sha512-62zfS+dL+M5aTNHXCNLyqD+j4oqxTIzDYrZbpqMP8Yn+gvlAGyzC6BSPNb1ZmFOakV1RtFYX/O3FCQXSWmQdgg==", "dev": true, "license": "MIT", + "dependencies": { + "@jest/environment": "30.5.2", + "@jest/expect": "30.5.2", + "@jest/types": "30.5.1", + "jest-mock": "30.5.2" + }, "engines": { - "node": ">= 4" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { - "version": "0.4.1", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.5", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "node_modules/@jest/pattern": { + "version": "30.5.0", + "integrity": "sha512-HdNQYSdRTEBNrginaqzQtTjG0HRMfrra/z6Ok7uL3S87vSlarIVohEsJsSj5edu3MiHoHjAkvPROz5ZjoKai+w==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "@types/node": "*", + "jest-regex-util": "30.5.0" }, "engines": { - "node": "*" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@eslint/js": { - "version": "8.57.1", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "node_modules/@jest/react-is-18": { + "name": "react-is", + "version": "18.3.1", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } + "license": "MIT" }, - "node_modules/@exodus/bytes": { - "version": "1.16.0", - "integrity": "sha512-IcpW84uEn3N7ETtNZMlxKhfl6Pec8rUNGOTBtWbK1FKhJxIFAptZyVrvVRVBimAJxJCgc3PxepxkdWWG4DVzfA==", + "node_modules/@jest/react-is-19": { + "name": "react-is", + "version": "19.3.0", + "integrity": "sha512-UpMYezM4v5/18F28aC66AEsjXIgE02kyEMH6yLdgLXu/UTfa1Ntwck/nNLrbqJsEXW7gPb0coNO9FQse9WTovA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jest/reporters": { + "version": "30.5.2", + "integrity": "sha512-ev6E9iG73twQcfAfoTnviuYkr3yOrYbzAtMFCimv2fxHXPIp9PBt2u1wNMt0aYWgl8FPwIcS8oqRJg6alnht4Q==", "dev": true, "license": "MIT", - "peer": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "30.5.2", + "@jest/test-result": "30.5.2", + "@jest/transform": "30.5.2", + "@jest/types": "30.5.1", + "@jridgewell/trace-mapping": "^0.3.31", + "@types/node": "*", + "chalk": "^4.1.2", + "collect-v8-coverage": "^1.0.2", + "exit-x": "^0.2.2", + "glob": "^13.0.6", + "graceful-fs": "^4.2.11", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^5.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "30.5.1", + "jest-util": "30.5.1", + "jest-worker": "30.5.1", + "slash": "^3.0.0", + "string-length": "^4.0.2", + "v8-to-istanbul": "^9.0.1" + }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "@noble/hashes": "^1.8.0 || ^2.0.0" + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "peerDependenciesMeta": { - "@noble/hashes": { + "node-notifier": { "optional": true } } }, - "node_modules/@gar/promise-retry": { - "version": "1.0.3", - "integrity": "sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA==", + "node_modules/@jest/schemas": { + "version": "30.5.0", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", "dev": true, "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@harperfast/extended-iterable": { - "version": "1.0.3", - "integrity": "sha512-sSAYhQca3rDWtQUHSAPeO7axFIUJOI6hn1gjRC5APVE1a90tuyT8f5WIgRsFhhWA7htNkju2veB9eWL6YHi/Lw==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "peer": true - }, - "node_modules/@hono/node-server": { - "version": "1.19.17", - "integrity": "sha512-dSneS5qhiauZWGDCeK4o695Xd9nUNjviSZCMQrj10eetr8Uln1ucn6bbphOM6UynAMMtNIzZNSpL9vnASJwrPQ==", + "node_modules/@jest/snapshot-utils": { + "version": "30.5.1", + "integrity": "sha512-V3wnxNtiVmw5PPVg433Cn3VdXnsOeu/ofLw3KC04Bn2y1wIlU5kozXQn48rhrgj/02LrCVtntTEF1yBha0XSUw==", "dev": true, "license": "MIT", - "engines": { - "node": ">=18.14.1" + "dependencies": { + "@jest/types": "30.5.1", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "natural-compare": "^1.4.0" }, - "peerDependencies": { - "hono": "^4" + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "deprecated": "Use @eslint/config-array instead", + "node_modules/@jest/source-map": { + "version": "30.5.2", + "integrity": "sha512-c5CehvkbfHX6yyNg7bMn2ylqlp9AHXuZ/3hPH/Lh5bxD/vefd/lV9HBjMqXef9yQNqkdwyZvkLSVSwB38hy8Qw==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" + "@jridgewell/trace-mapping": "^0.3.31", + "callsites": "^3.1.0", + "convert-source-map": "^2.0.0", + "graceful-fs": "^4.2.11" }, "engines": { - "node": ">=10.10.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@humanwhocodes/config-array/node_modules/balanced-match": { - "version": "1.0.2", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "node_modules/@jest/source-map/node_modules/convert-source-map": { + "version": "2.0.0", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, "license": "MIT" }, - "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.21", - "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "node_modules/@jest/test-result": { + "version": "30.5.2", + "integrity": "sha512-eO6YU5rsLfs60ne694e0IAmRgeBnoA8mu0nlxXDbl/1j5km0o2vO9/VbbUIKIH+WRKKEI+ELgwraRGn2Lhep8g==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.5", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" + "@jest/console": "30.5.2", + "@jest/types": "30.5.1", + "@types/istanbul-lib-coverage": "^2.0.6", + "collect-v8-coverage": "^1.0.2" }, "engines": { - "node": "*" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@inquirer/ansi": { - "version": "1.0.2", - "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", + "node_modules/@jest/test-sequencer": { + "version": "30.5.2", + "integrity": "sha512-bmij8jZyYAC47ExT2GeP7PcrlwSNS+Bp3KeRuBH88gpcxqDv6fljN8PemS4J/lLZ79xb6mpaennQTTq0zUAVVg==", "dev": true, "license": "MIT", + "dependencies": { + "@jest/test-result": "30.5.2", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.5.1", + "slash": "^3.0.0" + }, "engines": { - "node": ">=18" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@inquirer/checkbox": { - "version": "4.3.2", - "integrity": "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==", + "node_modules/@jest/transform": { + "version": "30.5.2", + "integrity": "sha512-LBGwmaE886C41jDZ65eqR1YbXM7642MK3ZhTowZd5+xDlrDLuf4ePuDdDmVSvcr8NTVJ3MqxLkMbUCzuigUaoQ==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/ansi": "^1.0.2", - "@inquirer/core": "^10.3.2", - "@inquirer/figures": "^1.0.15", - "@inquirer/type": "^3.0.10", - "yoctocolors-cjs": "^2.1.3" + "@babel/core": "^7.27.4", + "@jest/types": "30.5.1", + "@jridgewell/trace-mapping": "^0.3.31", + "babel-plugin-istanbul": "^8.0.0", + "chalk": "^4.1.2", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.5.1", + "jest-regex-util": "30.5.0", + "jest-util": "30.5.1", + "pirates": "^4.0.7", + "slash": "^3.0.0", + "write-file-atomic": "^5.0.1" }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@inquirer/confirm": { - "version": "5.1.19", - "integrity": "sha512-wQNz9cfcxrtEnUyG5PndC8g3gZ7lGDBzmWiXZkX8ot3vfZ+/BLjR8EvyGX4YzQLeVqtAlY/YScZpW7CW8qMoDQ==", + "node_modules/@jest/transform/node_modules/convert-source-map": { + "version": "2.0.0", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jest/types": { + "version": "30.5.1", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.3.0", - "@inquirer/type": "^3.0.9" + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@inquirer/core": { - "version": "10.3.2", - "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/ansi": "^1.0.2", - "@inquirer/figures": "^1.0.15", - "@inquirer/type": "^3.0.10", - "cli-width": "^4.1.0", - "mute-stream": "^2.0.0", - "signal-exit": "^4.1.0", - "wrap-ansi": "^6.2.0", - "yoctocolors-cjs": "^2.1.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@inquirer/editor": { - "version": "4.2.23", - "integrity": "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==", + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/external-editor": "^1.0.3", - "@inquirer/type": "^3.0.10" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@inquirer/expand": { - "version": "4.0.23", - "integrity": "sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==", + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10", - "yoctocolors-cjs": "^2.1.3" - }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "node": ">=6.0.0" } }, - "node_modules/@inquirer/external-editor": { - "version": "1.0.3", - "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "chardet": "^2.1.1", - "iconv-lite": "^0.7.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" } }, - "node_modules/@inquirer/figures": { - "version": "1.0.15", - "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } + "license": "MIT" }, - "node_modules/@inquirer/input": { - "version": "4.3.1", - "integrity": "sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==", + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@inquirer/number": { - "version": "3.0.23", - "integrity": "sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==", + "node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10" - }, + "license": "Apache-2.0", + "peer": true, "engines": { - "node": ">=18" + "node": ">=10.0" }, - "peerDependencies": { - "@types/node": ">=18" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@inquirer/password": { - "version": "4.0.23", - "integrity": "sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==", + "node_modules/@jsonjoy.com/buffers": { + "version": "17.67.0", + "integrity": "sha512-tfExRpYxBvi32vPs9ZHaTjSP4fHAfzSmcahOfNxtvGHcyJel+aibkPlGeBB+7AoC6hL7lXIE++8okecBxx7lcw==", "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/ansi": "^1.0.2", - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10" - }, + "license": "Apache-2.0", + "peer": true, "engines": { - "node": ">=18" + "node": ">=10.0" }, - "peerDependencies": { - "@types/node": ">=18" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@inquirer/prompts": { - "version": "7.9.0", - "integrity": "sha512-X7/+dG9SLpSzRkwgG5/xiIzW0oMrV3C0HOa7YHG1WnrLK+vCQHfte4k/T80059YBdei29RBC3s+pSMvPJDU9/A==", + "node_modules/@jsonjoy.com/codegen": { + "version": "1.0.0", + "integrity": "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==", "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/checkbox": "^4.3.0", - "@inquirer/confirm": "^5.1.19", - "@inquirer/editor": "^4.2.21", - "@inquirer/expand": "^4.0.21", - "@inquirer/input": "^4.2.5", - "@inquirer/number": "^3.0.21", - "@inquirer/password": "^4.0.21", - "@inquirer/rawlist": "^4.1.9", - "@inquirer/search": "^3.2.0", - "@inquirer/select": "^4.4.0" - }, + "license": "Apache-2.0", + "peer": true, "engines": { - "node": ">=18" + "node": ">=10.0" }, - "peerDependencies": { - "@types/node": ">=18" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@inquirer/rawlist": { - "version": "4.1.11", - "integrity": "sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==", + "node_modules/@jsonjoy.com/fs-core": { + "version": "4.80.0", + "integrity": "sha512-qMKWshnyjbyhm+NbzqGE3w5y1mckFFEMj0aDPU2/JBNh1BzIjVy9esq9lBmRnNVmhMEkxwDzLZ8Gqh5G1P5Dag==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "peer": true, "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10", - "yoctocolors-cjs": "^2.1.3" + "@jsonjoy.com/fs-node-builtins": "4.80.0", + "@jsonjoy.com/fs-node-utils": "4.80.0", + "thingies": "^2.5.0" }, "engines": { - "node": ">=18" + "node": ">=10.0" }, - "peerDependencies": { - "@types/node": ">=18" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@inquirer/search": { - "version": "3.2.2", - "integrity": "sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==", + "node_modules/@jsonjoy.com/fs-fsa": { + "version": "4.80.0", + "integrity": "sha512-ZBEKl7J6dbkRCfmrFV48Re211A/FHqniTUhsWCsaRteNSOqMgkX3qF1UreVgwB9oSPm81JLAwhIi6Lb/NH/PoA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "peer": true, "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/figures": "^1.0.15", - "@inquirer/type": "^3.0.10", - "yoctocolors-cjs": "^2.1.3" + "@jsonjoy.com/fs-core": "4.80.0", + "@jsonjoy.com/fs-node-builtins": "4.80.0", + "@jsonjoy.com/fs-node-utils": "4.80.0", + "thingies": "^2.5.0" }, "engines": { - "node": ">=18" + "node": ">=10.0" }, - "peerDependencies": { - "@types/node": ">=18" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@inquirer/select": { - "version": "4.4.2", - "integrity": "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==", + "node_modules/@jsonjoy.com/fs-node": { + "version": "4.80.0", + "integrity": "sha512-yiBlUfkMMRFFGy8CD/h2zd5rPnrM6bjlF+nx2LRAxNV6hOSiuObQIUEE7m7y2VTMisjxCRtzINH94bFGySWL7g==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "peer": true, "dependencies": { - "@inquirer/ansi": "^1.0.2", - "@inquirer/core": "^10.3.2", - "@inquirer/figures": "^1.0.15", - "@inquirer/type": "^3.0.10", - "yoctocolors-cjs": "^2.1.3" + "@jsonjoy.com/fs-core": "4.80.0", + "@jsonjoy.com/fs-node-builtins": "4.80.0", + "@jsonjoy.com/fs-node-utils": "4.80.0", + "@jsonjoy.com/fs-print": "4.80.0", + "@jsonjoy.com/fs-snapshot": "4.80.0", + "glob-to-regex.js": "^1.3.1", + "thingies": "^2.5.0" }, "engines": { - "node": ">=18" + "node": ">=10.0" }, - "peerDependencies": { - "@types/node": ">=18" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@inquirer/type": { - "version": "3.0.10", - "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", + "node_modules/@jsonjoy.com/fs-node-builtins": { + "version": "4.80.0", + "integrity": "sha512-OIOIhqaWiwUySFMny4epIAoviXsyMaQQ63PTc3zH+5HEOvarl5hpUmZIhp2m5+OfyKAh5ylxXnQIgew1OgJDVw==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "peer": true, "engines": { - "node": ">=18" + "node": ">=10.0" }, - "peerDependencies": { - "@types/node": ">=18" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "node_modules/@jsonjoy.com/fs-node-to-fsa": { + "version": "4.80.0", + "integrity": "sha512-ljeaR5xwtX1EyRmB0wvXANUMQZ3mjKcm7z8s2N8D8SLQ04CcAGtUhHFRZQXFb9mhgFyEVMydQGQtSOFS7pcrFQ==", "dev": true, - "license": "ISC", + "license": "Apache-2.0", + "peer": true, "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + "@jsonjoy.com/fs-fsa": "4.80.0", + "@jsonjoy.com/fs-node-builtins": "4.80.0", + "@jsonjoy.com/fs-node-utils": "4.80.0" }, "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.4.0", - "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" + "node": ">=10.0" }, "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.3", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" + "type": "github", + "url": "https://github.com/sponsors/streamich" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "node_modules/@jsonjoy.com/fs-node-utils": { + "version": "4.80.0", + "integrity": "sha512-xwHeVVi3f+Khg/Fmb8i5qoYVmQ08QznE5TWhQokUmIZjz4KBPzU+zt/+bahBI6G1ghtqajt27lWuAciIxApuzA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "peer": true, "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" + "@jsonjoy.com/fs-node-builtins": "4.80.0", + "glob-to-regex.js": "^1.3.1" }, "engines": { - "node": ">=12" + "node": ">=10.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.2.0", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "node_modules/@jsonjoy.com/fs-print": { + "version": "4.80.0", + "integrity": "sha512-1ocZLJw/m/xlq0moaIHO7HV9nHRVBUvdc2zMzhsJGkFOrqfwWvexfCv5cYCPPLRhP+9p3yunoi4Ep5YhRORC9Q==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "peer": true, "dependencies": { - "ansi-regex": "^6.2.2" + "@jsonjoy.com/fs-node-utils": "4.80.0", + "tree-dump": "^1.1.0" }, "engines": { - "node": ">=12" + "node": ">=10.0" }, "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "node_modules/@jsonjoy.com/fs-snapshot": { + "version": "4.80.0", + "integrity": "sha512-3AV23Q1H228KTtJKTUE0nNq8FCtxSvtENOLrEat80wkAJCKHuqQEv8yjhpS8Bei2OEFJAnluGXp9Ri2SWBlROw==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "peer": true, "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" + "@jsonjoy.com/buffers": "^17.65.0", + "@jsonjoy.com/fs-node-utils": "4.80.0", + "@jsonjoy.com/json-pack": "^17.65.0", + "@jsonjoy.com/util": "^17.65.0" }, "engines": { - "node": ">=12" + "node": ">=10.0" }, "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/base64": { + "version": "17.67.0", + "integrity": "sha512-5SEsJGsm15aP8TQGkDfJvz9axgPwAEm98S5DxOuYe8e1EbfajcDmgeXXzccEjh+mLnjqEKrkBdjHWS5vFNwDdw==", "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, + "license": "Apache-2.0", + "peer": true, "engines": { - "node": ">=18.0.0" + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/codegen": { + "version": "17.67.0", + "integrity": "sha512-idnkUplROpdBOV0HMcwhsCUS5TRUi9poagdGs70A6S4ux9+/aPuKbh8+UYRTLYQHtXvAdNfQWXDqZEx5k4Dj2Q==", "dev": true, - "license": "ISC", - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, + "license": "Apache-2.0", + "peer": true, "engines": { - "node": ">=8" + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { - "version": "1.0.10", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pack": { + "version": "17.67.0", + "integrity": "sha512-t0ejURcGaZsn1ClbJ/3kFqSOjlryd92eQY465IYrezsXmPcfHPE/av4twRSxf6WE+TkZgLY+71vCZbiIiFKA/w==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "peer": true, "dependencies": { - "sprintf-js": "~1.0.2" + "@jsonjoy.com/base64": "17.67.0", + "@jsonjoy.com/buffers": "17.67.0", + "@jsonjoy.com/codegen": "17.67.0", + "@jsonjoy.com/json-pointer": "17.67.0", + "@jsonjoy.com/util": "17.67.0", + "hyperdyperid": "^1.2.0", + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { - "version": "4.1.0", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pointer": { + "version": "17.67.0", + "integrity": "sha512-+iqOFInH+QZGmSuaybBUNdh7yvNrXvqR+h3wjXm0N/3JK1EyyFAeGJvqnmQL61d1ARLlk/wJdFKSL+LHJ1eaUA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "peer": true, "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "@jsonjoy.com/util": "17.67.0" }, "engines": { - "node": ">=8" + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.15.2", - "integrity": "sha512-6EuL879VkRA+1Cz578mKMiKvjPNEuk6+r1JaFzoSWejZmtf7xWbIyw1e3KkxlkzTIt9Taw6JBhEppG7utc1P+w==", + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/util": { + "version": "17.67.0", + "integrity": "sha512-6+8xBaz1rLSohlGh68D1pdw3AwDi9xydm8QNlAFkvnavCJYSze+pxoW2VKP8p308jtlMRLs5NTHfPlZLd4w7ew==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "peer": true, "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "@jsonjoy.com/buffers": "17.67.0", + "@jsonjoy.com/codegen": "17.67.0" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { - "version": "5.0.0", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.21.0", + "integrity": "sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "peer": true, "dependencies": { - "p-locate": "^4.1.0" + "@jsonjoy.com/base64": "^1.1.2", + "@jsonjoy.com/buffers": "^1.2.0", + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/json-pointer": "^1.0.2", + "@jsonjoy.com/util": "^1.9.0", + "hyperdyperid": "^1.2.0", + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" }, "engines": { - "node": ">=8" + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { - "version": "2.3.0", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "node_modules/@jsonjoy.com/json-pack/node_modules/@jsonjoy.com/buffers": { + "version": "1.2.1", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, + "license": "Apache-2.0", + "peer": true, "engines": { - "node": ">=6" + "node": ">=10.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { - "version": "4.1.0", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "node_modules/@jsonjoy.com/json-pointer": { + "version": "1.0.2", + "integrity": "sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "peer": true, "dependencies": { - "p-limit": "^2.2.0" + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/util": "^1.9.0" }, "engines": { - "node": ">=8" + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { - "version": "5.0.0", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "node_modules/@jsonjoy.com/util": { + "version": "1.9.0", + "integrity": "sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@jsonjoy.com/buffers": "^1.0.0", + "@jsonjoy.com/codegen": "^1.0.0" + }, "engines": { - "node": ">=8" + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.6", - "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", + "node_modules/@jsonjoy.com/util/node_modules/@jsonjoy.com/buffers": { + "version": "1.2.1", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "peer": true, "engines": { - "node": ">=8" + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@jest/console": { - "version": "30.5.2", - "integrity": "sha512-e8sQC4pCMHPBrPX7Hk8TJr+DM5dkUnImSTNag8SEWFAFAF6xIz4AJvCKapnrdHnZxep4bxw4PhG+ZbimYBMyKw==", + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/types": "30.5.1", - "@types/node": "*", - "chalk": "^4.1.2", - "jest-message-util": "30.5.1", - "jest-util": "30.5.1", - "slash": "^3.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } + "peer": true }, - "node_modules/@jest/core": { - "version": "30.5.2", - "integrity": "sha512-/jqvFWoJF7LV1a6AUpYMbMm+2yIYsHJfR474H0SCzr9k8tnO/jl9wRg6zSG6lxgf6EozNlCG4amFzWsbZSvvZA==", + "node_modules/@listr2/prompt-adapter-inquirer": { + "version": "3.0.5", + "integrity": "sha512-WELs+hj6xcilkloBXYf9XXK8tYEnKsgLj01Xl5ONUJpKjmT5hGVUzNUS5tooUxs7pGMrw+jFD/41WpqW4V3LDA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.5.2", - "@jest/pattern": "30.5.0", - "@jest/reporters": "30.5.2", - "@jest/test-result": "30.5.2", - "@jest/transform": "30.5.2", - "@jest/types": "30.5.1", - "@types/node": "*", - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "exit-x": "^0.2.2", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.11", - "jest-changed-files": "30.5.1", - "jest-config": "30.5.2", - "jest-haste-map": "30.5.1", - "jest-message-util": "30.5.1", - "jest-regex-util": "30.5.0", - "jest-resolve": "30.5.1", - "jest-resolve-dependencies": "30.5.2", - "jest-runner": "30.5.2", - "jest-runtime": "30.5.2", - "jest-snapshot": "30.5.2", - "jest-util": "30.5.1", - "jest-validate": "30.5.1", - "jest-watcher": "30.5.2", - "pretty-format": "30.5.1", - "slash": "^3.0.0" + "@inquirer/type": "^3.0.8" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=20.0.0" }, "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "@inquirer/prompts": ">= 3 < 8", + "listr2": "9.0.5" } }, - "node_modules/@jest/diff-sequences": { - "version": "30.5.0", - "integrity": "sha512-OsqBjHXCn8cadasoAZBP6nWYvMsRhpMzGXTpxJ5aO04NlbdhIz+FVe3q49l0AwVhsz/cEmIpBes6gAFl1/dWQg==", + "node_modules/@lmdb/lmdb-darwin-arm64": { + "version": "3.5.1", + "integrity": "sha512-tpfN4kKrrMpQ+If1l8bhmoNkECJi0iOu6AEdrTJvWVC+32sLxTARX5Rsu579mPImRP9YFWfWgeRQ5oav7zApQQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@jest/environment": { - "version": "30.5.2", - "integrity": "sha512-nwFPOUMbmsjNNRCpnJpB9HbSZu4C07wDDds++5j+j1MezzPLxMPDdbdUVjr2o4RULDOValnDd70taPFI1EtxAg==", + "node_modules/@lmdb/lmdb-darwin-x64": { + "version": "3.5.1", + "integrity": "sha512-+a2tTfc3rmWhLAolFUWRgJtpSuu+Fw/yjn4rF406NMxhfjbMuiOUTDRvRlMFV+DzyjkwnokisskHbCWkS3Ly5w==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jest/fake-timers": "30.5.2", - "@jest/types": "30.5.1", - "@types/node": "*", - "jest-mock": "30.5.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@jest/environment-jsdom-abstract": { - "version": "30.5.2", - "integrity": "sha512-8/P+8hKv7L4mhFoB6gpYLj/fuzXk8PWrde7TDHh8tJz5pE1ktlMbg0y6hDY/V8MfAnispFlPl/vPifowLy3JGQ==", + "node_modules/@lmdb/lmdb-linux-arm": { + "version": "3.5.1", + "integrity": "sha512-0EgcE6reYr8InjD7V37EgXcYrloqpxVPINy3ig1MwDSbl6LF/vXTYRH9OE1Ti1D8YZnB35ZH9aTcdfSb5lql2A==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jest/environment": "30.5.2", - "@jest/fake-timers": "30.5.2", - "@jest/types": "30.5.1", - "@types/node": "*", - "jest-mock": "30.5.2", - "jest-util": "30.5.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "@types/jsdom": "*", - "canvas": "^3.0.0", - "jsdom": "*" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@jest/expect": { - "version": "30.5.2", - "integrity": "sha512-2E+1Pg6jJxbRjLNLpciC71iri/3qsKJL222/aXJEFwC0VdUSvfS+JZQQLEVjUYYc8t8pQu2MGE3cF2+bNTYRsg==", + "node_modules/@lmdb/lmdb-linux-arm64": { + "version": "3.5.1", + "integrity": "sha512-aoERa5B6ywXdyFeYGQ1gbQpkMkDbEo45qVoXE5QpIRavqjnyPwjOulMkmkypkmsbJ5z4Wi0TBztON8agCTG0Vg==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "expect": "30.5.2", - "jest-snapshot": "30.5.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@jest/expect-utils": { - "version": "30.5.2", - "integrity": "sha512-d/HOLSrJUlBIl6n3LzOa7cjW3xdePm5H5gQw2lixZ/0h157l9sw3mLblzFqnPcTbhDzhRQdBOX3A3KcG73nm5Q==", + "node_modules/@lmdb/lmdb-linux-x64": { + "version": "3.5.1", + "integrity": "sha512-SqNDY1+vpji7bh0sFH5wlWyFTOzjbDOl0/kB5RLLYDAFyd/uw3n7wyrmas3rYPpAW7z18lMOi1yKlTPv967E3g==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jest/get-type": "30.5.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@jest/fake-timers": { - "version": "30.5.2", - "integrity": "sha512-GKk0h0tKT5SpDPxDhPg3r9mm5s8/YCBzVNMOGdRiQXwUiMsjU10mVPlMaQstH6/cu50vOb8N9oQUMETHuwwxKw==", + "node_modules/@lmdb/lmdb-win32-arm64": { + "version": "3.5.1", + "integrity": "sha512-50v0O1Lt37cwrmR9vWZK5hRW0Aw+KEmxJJ75fge/zIYdvNKB/0bSMSVR5Uc2OV9JhosIUyklOmrEvavwNJ8D6w==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jest/types": "30.5.1", - "@sinonjs/fake-timers": "^15.4.0", - "@types/node": "*", - "jest-message-util": "30.5.1", - "jest-mock": "30.5.2", - "jest-util": "30.5.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@jest/get-type": { - "version": "30.5.0", - "integrity": "sha512-9/2VUPitAjmBzbvDvqrxmvB7BzWsBW0WmkkojX1ODuxX1NLGxx9gfaZpHB0z8DtJ9uhGNmZG/VXBhf8uO0OV8Q==", + "node_modules/@lmdb/lmdb-win32-x64": { + "version": "3.5.1", + "integrity": "sha512-qwosvPyl+zpUlp3gRb7UcJ3H8S28XHCzkv0Y0EgQToXjQP91ZD67EHSCDmaLjtKhe+GVIW5om1KUpzVLA0l6pg==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@jest/globals": { - "version": "30.5.2", - "integrity": "sha512-62zfS+dL+M5aTNHXCNLyqD+j4oqxTIzDYrZbpqMP8Yn+gvlAGyzC6BSPNb1ZmFOakV1RtFYX/O3FCQXSWmQdgg==", + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.32.1", + "integrity": "sha512-2DdE+SJDtzLEEWzY1ZjY7Q+VcPhcV1KisD3zI4u0XZyktsjHum1mwbMI+JaulUBi2OZk+KJAi2uPXzxichPkdw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.5.2", - "@jest/expect": "30.5.2", - "@jest/types": "30.5.1", - "jest-mock": "30.5.2" + "@hono/node-server": "^1.19.9 || ^2.0.5", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } } }, - "node_modules/@jest/pattern": { - "version": "30.5.0", - "integrity": "sha512-HdNQYSdRTEBNrginaqzQtTjG0HRMfrra/z6Ok7uL3S87vSlarIVohEsJsSj5edu3MiHoHjAkvPROz5ZjoKai+w==", + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { + "version": "3.0.4", + "integrity": "sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@types/node": "*", - "jest-regex-util": "30.5.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@jest/react-is-18": { - "name": "react-is", - "version": "18.3.1", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { + "version": "3.0.4", + "integrity": "sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@jest/react-is-19": { - "name": "react-is", - "version": "19.3.0", - "integrity": "sha512-UpMYezM4v5/18F28aC66AEsjXIgE02kyEMH6yLdgLXu/UTfa1Ntwck/nNLrbqJsEXW7gPb0coNO9FQse9WTovA==", + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { + "version": "3.0.4", + "integrity": "sha512-Tg3yX65f5GbtXLkrYEHE5oibZG9epyYWas7FogTTEJeDEF9JlXJzKgXaNhT3UXlTOeA+AfZpYZYZ0uPj7Cfquw==", + "cpu": [ + "arm" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@jest/reporters": { - "version": "30.5.2", - "integrity": "sha512-ev6E9iG73twQcfAfoTnviuYkr3yOrYbzAtMFCimv2fxHXPIp9PBt2u1wNMt0aYWgl8FPwIcS8oqRJg6alnht4Q==", + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { + "version": "3.0.4", + "integrity": "sha512-dgX0P/9wGPJeHFBG+ZmhgE6bmtMt7NP5CRBGyyktpopdk/mW4POnrpQsSLtKI1dwpc+pPLuXHDh6vvskyQE/sw==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "30.5.2", - "@jest/test-result": "30.5.2", - "@jest/transform": "30.5.2", - "@jest/types": "30.5.1", - "@jridgewell/trace-mapping": "^0.3.31", - "@types/node": "*", - "chalk": "^4.1.2", - "collect-v8-coverage": "^1.0.2", - "exit-x": "^0.2.2", - "glob": "^13.0.6", - "graceful-fs": "^4.2.11", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^5.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "30.5.1", - "jest-util": "30.5.1", - "jest-worker": "30.5.1", - "slash": "^3.0.0", - "string-length": "^4.0.2", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@jest/schemas": { - "version": "30.5.0", - "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", + "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { + "version": "3.0.4", + "integrity": "sha512-8TNXMEjJc3QEy7R/x1INhgiU+XakDAFUzBhaz7+Rbrs8NH5UQeHQxxmzsSBJGyV6I1jW79undiQm8tOI+D+8FQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.34.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@jest/snapshot-utils": { - "version": "30.5.1", - "integrity": "sha512-V3wnxNtiVmw5PPVg433Cn3VdXnsOeu/ofLw3KC04Bn2y1wIlU5kozXQn48rhrgj/02LrCVtntTEF1yBha0XSUw==", + "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { + "version": "3.0.4", + "integrity": "sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jest/types": "30.5.1", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "natural-compare": "^1.4.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@jest/source-map": { - "version": "30.5.2", - "integrity": "sha512-c5CehvkbfHX6yyNg7bMn2ylqlp9AHXuZ/3hPH/Lh5bxD/vefd/lV9HBjMqXef9yQNqkdwyZvkLSVSwB38hy8Qw==", + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.31", - "callsites": "^3.1.0", - "convert-source-map": "^2.0.0", - "graceful-fs": "^4.2.11" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^22.20 || ^24.12 || >=25" } }, - "node_modules/@jest/source-map/node_modules/convert-source-map": { - "version": "2.0.0", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "node_modules/@napi-rs/nice": { + "version": "1.1.1", + "integrity": "sha512-xJIPs+bYuc9ASBl+cvGsKbGrJmS6fAKaSZCnT0lhahT5rhA2VVy9/EcIgd2JhtEuFOJNx7UHNn/qiTPTY4nrQw==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@napi-rs/nice-android-arm-eabi": "1.1.1", + "@napi-rs/nice-android-arm64": "1.1.1", + "@napi-rs/nice-darwin-arm64": "1.1.1", + "@napi-rs/nice-darwin-x64": "1.1.1", + "@napi-rs/nice-freebsd-x64": "1.1.1", + "@napi-rs/nice-linux-arm-gnueabihf": "1.1.1", + "@napi-rs/nice-linux-arm64-gnu": "1.1.1", + "@napi-rs/nice-linux-arm64-musl": "1.1.1", + "@napi-rs/nice-linux-ppc64-gnu": "1.1.1", + "@napi-rs/nice-linux-riscv64-gnu": "1.1.1", + "@napi-rs/nice-linux-s390x-gnu": "1.1.1", + "@napi-rs/nice-linux-x64-gnu": "1.1.1", + "@napi-rs/nice-linux-x64-musl": "1.1.1", + "@napi-rs/nice-openharmony-arm64": "1.1.1", + "@napi-rs/nice-win32-arm64-msvc": "1.1.1", + "@napi-rs/nice-win32-ia32-msvc": "1.1.1", + "@napi-rs/nice-win32-x64-msvc": "1.1.1" + } }, - "node_modules/@jest/test-result": { - "version": "30.5.2", - "integrity": "sha512-eO6YU5rsLfs60ne694e0IAmRgeBnoA8mu0nlxXDbl/1j5km0o2vO9/VbbUIKIH+WRKKEI+ELgwraRGn2Lhep8g==", + "node_modules/@napi-rs/nice-android-arm-eabi": { + "version": "1.1.1", + "integrity": "sha512-kjirL3N6TnRPv5iuHw36wnucNqXAO46dzK9oPb0wj076R5Xm8PfUVA9nAFB5ZNMmfJQJVKACAPd/Z2KYMppthw==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jest/console": "30.5.2", - "@jest/types": "30.5.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "collect-v8-coverage": "^1.0.2" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 10" } }, - "node_modules/@jest/test-sequencer": { - "version": "30.5.2", - "integrity": "sha512-bmij8jZyYAC47ExT2GeP7PcrlwSNS+Bp3KeRuBH88gpcxqDv6fljN8PemS4J/lLZ79xb6mpaennQTTq0zUAVVg==", + "node_modules/@napi-rs/nice-android-arm64": { + "version": "1.1.1", + "integrity": "sha512-blG0i7dXgbInN5urONoUCNf+DUEAavRffrO7fZSeoRMJc5qD+BJeNcpr54msPF6qfDD6kzs9AQJogZvT2KD5nw==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jest/test-result": "30.5.2", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.5.1", - "slash": "^3.0.0" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 10" } }, - "node_modules/@jest/transform": { - "version": "30.5.2", - "integrity": "sha512-LBGwmaE886C41jDZ65eqR1YbXM7642MK3ZhTowZd5+xDlrDLuf4ePuDdDmVSvcr8NTVJ3MqxLkMbUCzuigUaoQ==", + "node_modules/@napi-rs/nice-darwin-arm64": { + "version": "1.1.1", + "integrity": "sha512-s/E7w45NaLqTGuOjC2p96pct4jRfo61xb9bU1unM/MJ/RFkKlJyJDx7OJI/O0ll/hrfpqKopuAFDV8yo0hfT7A==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/core": "^7.27.4", - "@jest/types": "30.5.1", - "@jridgewell/trace-mapping": "^0.3.31", - "babel-plugin-istanbul": "^8.0.0", - "chalk": "^4.1.2", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.5.1", - "jest-regex-util": "30.5.0", - "jest-util": "30.5.1", - "pirates": "^4.0.7", - "slash": "^3.0.0", - "write-file-atomic": "^5.0.1" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 10" } }, - "node_modules/@jest/transform/node_modules/convert-source-map": { - "version": "2.0.0", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "node_modules/@napi-rs/nice-darwin-x64": { + "version": "1.1.1", + "integrity": "sha512-dGoEBnVpsdcC+oHHmW1LRK5eiyzLwdgNQq3BmZIav+9/5WTZwBYX7r5ZkQC07Nxd3KHOCkgbHSh4wPkH1N1LiQ==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } }, - "node_modules/@jest/types": { - "version": "30.5.1", - "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", + "node_modules/@napi-rs/nice-freebsd-x64": { + "version": "1.1.1", + "integrity": "sha512-kHv4kEHAylMYmlNwcQcDtXjklYp4FCf0b05E+0h6nDHsZ+F0bDe04U/tXNOqrx5CmIAth4vwfkjjUmp4c4JktQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jest/pattern": "30.5.0", - "@jest/schemas": "30.5.0", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - }, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 10" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "node_modules/@napi-rs/nice-linux-arm-gnueabihf": { + "version": "1.1.1", + "integrity": "sha512-E1t7K0efyKXZDoZg1LzCOLxgolxV58HCkaEkEvIYQx12ht2pa8hoBo+4OB3qh7e+QiBlp1SRf+voWUZFxyhyqg==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" } }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "node_modules/@napi-rs/nice-linux-arm64-gnu": { + "version": "1.1.1", + "integrity": "sha512-CIKLA12DTIZlmTaaKhQP88R3Xao+gyJxNWEn04wZwC2wmRapNnxCUZkVwggInMJvtVElA+D4ZzOU5sX4jV+SmQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "node_modules/@napi-rs/nice-linux-arm64-musl": { + "version": "1.1.1", + "integrity": "sha512-+2Rzdb3nTIYZ0YJF43qf2twhqOCkiSrHx2Pg6DJaCPYhhaxbLcdlV8hCRMHghQ+EtZQWGNcS2xF4KxBhSGeutg==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.0.0" + "node": ">= 10" } }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.11", - "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "node_modules/@napi-rs/nice-linux-ppc64-gnu": { + "version": "1.1.1", + "integrity": "sha512-4FS8oc0GeHpwvv4tKciKkw3Y4jKsL7FRhaOeiPei0X9T4Jd619wHNe4xCLmN2EMgZoeGg+Q7GY7BsvwKpL22Tg==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.6.0", - "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "node_modules/@napi-rs/nice-linux-riscv64-gnu": { + "version": "1.1.1", + "integrity": "sha512-HU0nw9uD4FO/oGCCk409tCi5IzIZpH2agE6nN4fqpwVlCn5BOq0MS1dXGjXaG17JaAvrlpV5ZeyZwSon10XOXw==", + "cpu": [ + "riscv64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "node_modules/@napi-rs/nice-linux-s390x-gnu": { + "version": "1.1.1", + "integrity": "sha512-2YqKJWWl24EwrX0DzCQgPLKQBxYDdBxOHot1KWEq7aY2uYeX+Uvtv4I8xFVVygJDgf6/92h9N3Y43WPx8+PAgQ==", + "cpu": [ + "s390x" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" } }, - "node_modules/@jsonjoy.com/base64": { - "version": "1.1.2", - "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "node_modules/@napi-rs/nice-linux-x64-gnu": { + "version": "1.1.1", + "integrity": "sha512-/gaNz3R92t+dcrfCw/96pDopcmec7oCcAQ3l/M+Zxr82KT4DljD37CpgrnXV+pJC263JkW572pdbP3hP+KjcIg==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", - "peer": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" + "node": ">= 10" } }, - "node_modules/@jsonjoy.com/buffers": { - "version": "17.67.0", - "integrity": "sha512-tfExRpYxBvi32vPs9ZHaTjSP4fHAfzSmcahOfNxtvGHcyJel+aibkPlGeBB+7AoC6hL7lXIE++8okecBxx7lcw==", + "node_modules/@napi-rs/nice-linux-x64-musl": { + "version": "1.1.1", + "integrity": "sha512-xScCGnyj/oppsNPMnevsBe3pvNaoK7FGvMjT35riz9YdhB2WtTG47ZlbxtOLpjeO9SqqQ2J2igCmz6IJOD5JYw==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", - "peer": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" + "node": ">= 10" } }, - "node_modules/@jsonjoy.com/codegen": { - "version": "1.0.0", - "integrity": "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==", + "node_modules/@napi-rs/nice-openharmony-arm64": { + "version": "1.1.1", + "integrity": "sha512-6uJPRVwVCLDeoOaNyeiW0gp2kFIM4r7PL2MczdZQHkFi9gVlgm+Vn+V6nTWRcu856mJ2WjYJiumEajfSm7arPQ==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "Apache-2.0", - "peer": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" + "node": ">= 10" } }, - "node_modules/@jsonjoy.com/fs-core": { - "version": "4.80.0", - "integrity": "sha512-qMKWshnyjbyhm+NbzqGE3w5y1mckFFEMj0aDPU2/JBNh1BzIjVy9esq9lBmRnNVmhMEkxwDzLZ8Gqh5G1P5Dag==", + "node_modules/@napi-rs/nice-win32-arm64-msvc": { + "version": "1.1.1", + "integrity": "sha512-uoTb4eAvM5B2aj/z8j+Nv8OttPf2m+HVx3UjA5jcFxASvNhQriyCQF1OB1lHL43ZhW+VwZlgvjmP5qF3+59atA==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@jsonjoy.com/fs-node-builtins": "4.80.0", - "@jsonjoy.com/fs-node-utils": "4.80.0", - "thingies": "^2.5.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" + "node": ">= 10" } }, - "node_modules/@jsonjoy.com/fs-fsa": { - "version": "4.80.0", - "integrity": "sha512-ZBEKl7J6dbkRCfmrFV48Re211A/FHqniTUhsWCsaRteNSOqMgkX3qF1UreVgwB9oSPm81JLAwhIi6Lb/NH/PoA==", + "node_modules/@napi-rs/nice-win32-ia32-msvc": { + "version": "1.1.1", + "integrity": "sha512-CNQqlQT9MwuCsg1Vd/oKXiuH+TcsSPJmlAFc5frFyX/KkOh0UpBLEj7aoY656d5UKZQMQFP7vJNa1DNUNORvug==", + "cpu": [ + "ia32" + ], "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@jsonjoy.com/fs-core": "4.80.0", - "@jsonjoy.com/fs-node-builtins": "4.80.0", - "@jsonjoy.com/fs-node-utils": "4.80.0", - "thingies": "^2.5.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" + "node": ">= 10" } }, - "node_modules/@jsonjoy.com/fs-node": { - "version": "4.80.0", - "integrity": "sha512-yiBlUfkMMRFFGy8CD/h2zd5rPnrM6bjlF+nx2LRAxNV6hOSiuObQIUEE7m7y2VTMisjxCRtzINH94bFGySWL7g==", + "node_modules/@napi-rs/nice-win32-x64-msvc": { + "version": "1.1.1", + "integrity": "sha512-vB+4G/jBQCAh0jelMTY3+kgFy00Hlx2f2/1zjMoH821IbplbWZOkLiTYXQkygNTzQJTq5cvwBDgn2ppHD+bglQ==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", - "peer": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.2.5", + "integrity": "sha512-HshSvXzmBxNzqejd3k/KemgQ3hdREUYFRRrgxOZ+gPljDIsO2SDZrVZMd4Z/HsxudQbE62OVyVFE8z9BCurfjA==", + "dev": true, + "license": "MIT", + "optional": true, "dependencies": { - "@jsonjoy.com/fs-core": "4.80.0", - "@jsonjoy.com/fs-node-builtins": "4.80.0", - "@jsonjoy.com/fs-node-utils": "4.80.0", - "@jsonjoy.com/fs-print": "4.80.0", - "@jsonjoy.com/fs-snapshot": "4.80.0", - "glob-to-regex.js": "^1.3.1", - "thingies": "^2.5.0" + "@tybys/wasm-util": "^0.10.3" }, "engines": { - "node": ">=10.0" + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" }, "funding": { "type": "github", - "url": "https://github.com/sponsors/streamich" + "url": "https://github.com/sponsors/Brooooooklyn" }, "peerDependencies": { - "tslib": "2" + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" } }, - "node_modules/@jsonjoy.com/fs-node-builtins": { - "version": "4.80.0", - "integrity": "sha512-OIOIhqaWiwUySFMny4epIAoviXsyMaQQ63PTc3zH+5HEOvarl5hpUmZIhp2m5+OfyKAh5ylxXnQIgew1OgJDVw==", + "node_modules/@ngtools/webpack": { + "version": "21.2.26", + "integrity": "sha512-V8ScM6JQdTceaiP/fxSVYcrfABZzUbzYbMLV5nvAL6+x4RgX8uAew+sPUjnyxjBhn/vIG9A36cJ+j7SJtiATIA==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "peer": true, "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" }, "peerDependencies": { - "tslib": "2" + "@angular/compiler-cli": "^21.0.0", + "typescript": ">=5.9 <6.0", + "webpack": "^5.54.0" } }, - "node_modules/@jsonjoy.com/fs-node-to-fsa": { - "version": "4.80.0", - "integrity": "sha512-ljeaR5xwtX1EyRmB0wvXANUMQZ3mjKcm7z8s2N8D8SLQ04CcAGtUhHFRZQXFb9mhgFyEVMydQGQtSOFS7pcrFQ==", + "node_modules/@noble/hashes": { + "version": "1.8.0", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "peer": true, - "dependencies": { - "@jsonjoy.com/fs-fsa": "4.80.0", - "@jsonjoy.com/fs-node-builtins": "4.80.0", - "@jsonjoy.com/fs-node-utils": "4.80.0" - }, "engines": { - "node": ">=10.0" + "node": "^14.21.3 || >=16" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@jsonjoy.com/fs-node-utils": { - "version": "4.80.0", - "integrity": "sha512-xwHeVVi3f+Khg/Fmb8i5qoYVmQ08QznE5TWhQokUmIZjz4KBPzU+zt/+bahBI6G1ghtqajt27lWuAciIxApuzA==", + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, - "license": "Apache-2.0", - "peer": true, + "license": "MIT", "dependencies": { - "@jsonjoy.com/fs-node-builtins": "4.80.0", - "glob-to-regex.js": "^1.3.1" + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" }, "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" + "node": ">= 8" } }, - "node_modules/@jsonjoy.com/fs-print": { - "version": "4.80.0", - "integrity": "sha512-1ocZLJw/m/xlq0moaIHO7HV9nHRVBUvdc2zMzhsJGkFOrqfwWvexfCv5cYCPPLRhP+9p3yunoi4Ep5YhRORC9Q==", + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@jsonjoy.com/fs-node-utils": "4.80.0", - "tree-dump": "^1.1.0" - }, + "license": "MIT", "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" + "node": ">= 8" } }, - "node_modules/@jsonjoy.com/fs-snapshot": { - "version": "4.80.0", - "integrity": "sha512-3AV23Q1H228KTtJKTUE0nNq8FCtxSvtENOLrEat80wkAJCKHuqQEv8yjhpS8Bei2OEFJAnluGXp9Ri2SWBlROw==", + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, - "license": "Apache-2.0", - "peer": true, + "license": "MIT", "dependencies": { - "@jsonjoy.com/buffers": "^17.65.0", - "@jsonjoy.com/fs-node-utils": "4.80.0", - "@jsonjoy.com/json-pack": "^17.65.0", - "@jsonjoy.com/util": "^17.65.0" + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" }, "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" + "node": ">= 8" } }, - "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/base64": { - "version": "17.67.0", - "integrity": "sha512-5SEsJGsm15aP8TQGkDfJvz9axgPwAEm98S5DxOuYe8e1EbfajcDmgeXXzccEjh+mLnjqEKrkBdjHWS5vFNwDdw==", + "node_modules/@npmcli/agent": { + "version": "4.0.2", + "integrity": "sha512-EUEuWAxnL07Sp5/iC/1X6Xj+XThUvnbei9zfRWZdEXa7lss9RTHMhAHBeg+MZ5To9s/gGaSI+UwZTPdYMvKSeg==", "dev": true, - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^11.2.1", + "socks-proxy-agent": "^8.0.3" }, - "peerDependencies": { - "tslib": "2" + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/codegen": { - "version": "17.67.0", - "integrity": "sha512-idnkUplROpdBOV0HMcwhsCUS5TRUi9poagdGs70A6S4ux9+/aPuKbh8+UYRTLYQHtXvAdNfQWXDqZEx5k4Dj2Q==", + "node_modules/@npmcli/agent/node_modules/lru-cache": { + "version": "11.5.3", + "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", "dev": true, - "license": "Apache-2.0", - "peer": true, + "license": "BlueOak-1.0.0", "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" + "node": "20 || >=22" } }, - "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pack": { - "version": "17.67.0", - "integrity": "sha512-t0ejURcGaZsn1ClbJ/3kFqSOjlryd92eQY465IYrezsXmPcfHPE/av4twRSxf6WE+TkZgLY+71vCZbiIiFKA/w==", + "node_modules/@npmcli/fs": { + "version": "5.0.0", + "integrity": "sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og==", "dev": true, - "license": "Apache-2.0", - "peer": true, + "license": "ISC", "dependencies": { - "@jsonjoy.com/base64": "17.67.0", - "@jsonjoy.com/buffers": "17.67.0", - "@jsonjoy.com/codegen": "17.67.0", - "@jsonjoy.com/json-pointer": "17.67.0", - "@jsonjoy.com/util": "17.67.0", - "hyperdyperid": "^1.2.0", - "thingies": "^2.5.0", - "tree-dump": "^1.1.0" + "semver": "^7.3.5" }, "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pointer": { - "version": "17.67.0", - "integrity": "sha512-+iqOFInH+QZGmSuaybBUNdh7yvNrXvqR+h3wjXm0N/3JK1EyyFAeGJvqnmQL61d1ARLlk/wJdFKSL+LHJ1eaUA==", + "node_modules/@npmcli/git": { + "version": "7.0.2", + "integrity": "sha512-oeolHDjExNAJAnlYP2qzNjMX/Xi9bmu78C9dIGr4xjobrSKbuMYCph8lTzn4vnW3NjIqVmw/f8BCfouqyJXlRg==", "dev": true, - "license": "Apache-2.0", - "peer": true, + "license": "ISC", "dependencies": { - "@jsonjoy.com/util": "17.67.0" + "@gar/promise-retry": "^1.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "ini": "^6.0.0", + "lru-cache": "^11.2.1", + "npm-pick-manifest": "^11.0.1", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "which": "^6.0.0" }, "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/util": { - "version": "17.67.0", - "integrity": "sha512-6+8xBaz1rLSohlGh68D1pdw3AwDi9xydm8QNlAFkvnavCJYSze+pxoW2VKP8p308jtlMRLs5NTHfPlZLd4w7ew==", + "node_modules/@npmcli/git/node_modules/isexe": { + "version": "4.0.0", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@jsonjoy.com/buffers": "17.67.0", - "@jsonjoy.com/codegen": "17.67.0" - }, + "license": "BlueOak-1.0.0", "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" + "node": ">=20" } }, - "node_modules/@jsonjoy.com/json-pack": { - "version": "1.21.0", - "integrity": "sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==", + "node_modules/@npmcli/git/node_modules/lru-cache": { + "version": "11.5.3", + "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", "dev": true, - "license": "Apache-2.0", - "peer": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@npmcli/git/node_modules/which": { + "version": "6.0.1", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", + "dev": true, + "license": "ISC", "dependencies": { - "@jsonjoy.com/base64": "^1.1.2", - "@jsonjoy.com/buffers": "^1.2.0", - "@jsonjoy.com/codegen": "^1.0.0", - "@jsonjoy.com/json-pointer": "^1.0.2", - "@jsonjoy.com/util": "^1.9.0", - "hyperdyperid": "^1.2.0", - "thingies": "^2.5.0", - "tree-dump": "^1.1.0" + "isexe": "^4.0.0" + }, + "bin": { + "node-which": "bin/which.js" }, "engines": { - "node": ">=10.0" + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/installed-package-contents": { + "version": "4.0.0", + "integrity": "sha512-yNyAdkBxB72gtZ4GrwXCM0ZUedo9nIbOMKfGjt6Cu6DXf0p8y1PViZAKDC8q8kv/fufx0WTjRBdSlyrvnP7hmA==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-bundled": "^5.0.0", + "npm-normalize-package-bin": "^5.0.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" + "bin": { + "installed-package-contents": "bin/index.js" }, - "peerDependencies": { - "tslib": "2" + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@jsonjoy.com/json-pack/node_modules/@jsonjoy.com/buffers": { - "version": "1.2.1", - "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "node_modules/@npmcli/node-gyp": { + "version": "5.0.0", + "integrity": "sha512-uuG5HZFXLfyFKqg8QypsmgLQW7smiRjVc45bqD/ofZZcR/uxEjgQU8qDPv0s9TEeMUiAAU/GC5bR6++UdTirIQ==", "dev": true, - "license": "Apache-2.0", - "peer": true, + "license": "ISC", "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@jsonjoy.com/json-pointer": { - "version": "1.0.2", - "integrity": "sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==", + "node_modules/@npmcli/package-json": { + "version": "7.0.5", + "integrity": "sha512-iVuTlG3ORq2iaVa1IWUxAO/jIp77tUKBhoMjuzYW2kL4MLN1bi/ofqkZ7D7OOwh8coAx1/S2ge0rMdGv8sLSOQ==", "dev": true, - "license": "Apache-2.0", - "peer": true, + "license": "ISC", "dependencies": { - "@jsonjoy.com/codegen": "^1.0.0", - "@jsonjoy.com/util": "^1.9.0" + "@npmcli/git": "^7.0.0", + "glob": "^13.0.0", + "hosted-git-info": "^9.0.0", + "json-parse-even-better-errors": "^5.0.0", + "proc-log": "^6.0.0", + "semver": "^7.5.3", + "spdx-expression-parse": "^4.0.0" }, "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@jsonjoy.com/util": { - "version": "1.9.0", - "integrity": "sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==", + "node_modules/@npmcli/promise-spawn": { + "version": "9.0.2", + "integrity": "sha512-h7b1j0egrruBm/32eVA6/zMw5VJxxaB3vT+uVGGQ4SAAMrQVFHNuzL4+EAXn66j+huj0jApINc1fW3fC+kVoeA==", "dev": true, - "license": "Apache-2.0", - "peer": true, + "license": "ISC", "dependencies": { - "@jsonjoy.com/buffers": "^1.0.0", - "@jsonjoy.com/codegen": "^1.0.0" + "which": "^6.0.0" }, "engines": { - "node": ">=10.0" + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/isexe": { + "version": "4.0.0", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/which": { + "version": "6.0.1", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^4.0.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" + "bin": { + "node-which": "bin/which.js" }, - "peerDependencies": { - "tslib": "2" + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@jsonjoy.com/util/node_modules/@jsonjoy.com/buffers": { - "version": "1.2.1", - "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "node_modules/@npmcli/redact": { + "version": "4.0.0", + "integrity": "sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q==", "dev": true, - "license": "Apache-2.0", - "peer": true, + "license": "ISC", "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/run-script": { + "version": "10.0.4", + "integrity": "sha512-mGUWr1uMnf0le2TwfOZY4SFxZGXGfm4Jtay/nwAa2FLNAKXUoUwaGwBMNH36UHPtinWfTSJ3nqFQr0091CxVGg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "node-gyp": "^12.1.0", + "proc-log": "^6.0.0" }, - "peerDependencies": { - "tslib": "2" + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.5", - "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "node_modules/@oxc-project/types": { + "version": "0.113.0", + "integrity": "sha512-Tp3XmgxwNQ9pEN9vxgJBAqdRamHibi76iowQ38O2I4PMpcvNRQNVsU2n1x1nv9yh0XoTrGFzf7cZSGxmixxrhA==", "dev": true, "license": "MIT", - "peer": true + "funding": { + "url": "https://github.com/sponsors/Boshen" + } }, - "node_modules/@listr2/prompt-adapter-inquirer": { - "version": "3.0.5", - "integrity": "sha512-WELs+hj6xcilkloBXYf9XXK8tYEnKsgLj01Xl5ONUJpKjmT5hGVUzNUS5tooUxs7pGMrw+jFD/41WpqW4V3LDA==", + "node_modules/@parcel/watcher": { + "version": "2.6.0", + "integrity": "sha512-7FNeNl8NCE7aINx7WXiKQrPYZWC/hvrTsmk6zmxbI7LTXE7hVek/n8AfVgpe2y82zl3w0HvCHN0bVKMBoJcC0w==", "dev": true, + "hasInstallScript": true, "license": "MIT", "dependencies": { - "@inquirer/type": "^3.0.8" + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.4" }, "engines": { - "node": ">=20.0.0" + "node": ">= 10.0.0" }, - "peerDependencies": { - "@inquirer/prompts": ">= 3 < 8", - "listr2": "9.0.5" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.6.0", + "@parcel/watcher-darwin-arm64": "2.6.0", + "@parcel/watcher-darwin-x64": "2.6.0", + "@parcel/watcher-freebsd-x64": "2.6.0", + "@parcel/watcher-linux-arm-glibc": "2.6.0", + "@parcel/watcher-linux-arm-musl": "2.6.0", + "@parcel/watcher-linux-arm64-glibc": "2.6.0", + "@parcel/watcher-linux-arm64-musl": "2.6.0", + "@parcel/watcher-linux-x64-glibc": "2.6.0", + "@parcel/watcher-linux-x64-musl": "2.6.0", + "@parcel/watcher-win32-arm64": "2.6.0", + "@parcel/watcher-win32-x64": "2.6.0" } }, - "node_modules/@lmdb/lmdb-darwin-arm64": { - "version": "3.4.3", - "integrity": "sha512-zR6Y45VNtW5s+A+4AyhrJk0VJKhXdkLhrySCpCu7PSdnakebsOzNxf58p5Xoq66vOSuueGAxlqDAF49HwdrSTQ==", + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.6.0", + "integrity": "sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA==", "cpu": [ "arm64" ], @@ -6846,51 +6485,59 @@ "license": "MIT", "optional": true, "os": [ - "darwin" - ] + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/@lmdb/lmdb-darwin-x64": { - "version": "3.4.3", - "integrity": "sha512-nfGm5pQksBGfaj9uMbjC0YyQreny/Pl7mIDtHtw6g7WQuCgeLullr9FNRsYyKplaEJBPrCVpEjpAznxTBIrXBw==", + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.6.0", + "integrity": "sha512-Y3QV0gl7Q1zbfueunkWIERICbEojQFCgpyG7YqOGNFLsckXyI1xu9mAIUpKY9QBYzBtSkN8dBPwd3yiAO9ovMw==", "cpu": [ - "x64" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "darwin" - ] - }, - "node_modules/@lmdb/lmdb-linux-arm": { - "version": "3.4.3", - "integrity": "sha512-Kjqomp7i0rgSbYSUmv9JnXpS55zYT/YcW3Bdf9oqOTjcH0/8tFAP8MLhu/i9V2pMKIURDZk63Ww49DTK0T3c/Q==", - "cpu": [ - "arm" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/@lmdb/lmdb-linux-arm64": { - "version": "3.4.3", - "integrity": "sha512-uX9eaPqWb740wg5D3TCvU/js23lSRSKT7lJrrQ8IuEG/VLgpPlxO3lHDywU44yFYdGS7pElBn6ioKFKhvALZlw==", + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.6.0", + "integrity": "sha512-Ohv6OpzhUfKYD7Beb8kDvG0jbIxORCYY1JRdZnaBtnjjkJxgD7ZVL0nw2sCYd0yTMKTvz3nnTnOF3cDifK+kvw==", "cpu": [ - "arm64" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" - ] + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/@lmdb/lmdb-linux-x64": { - "version": "3.4.3", - "integrity": "sha512-7/8l20D55CfwdMupkc3fNxNJdn4bHsti2X0cp6PwiXlLeSFvAfWs5kCCx+2Cyje4l4GtN//LtKWjTru/9hDJQg==", + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.6.0", + "integrity": "sha512-5HmXvDgs8VK+74jF9y9/2FE3/OnlcKmc56tjmSrEuZjpSZOGL+fvAu+HKJBdPs9uwoP2hE6TlSUpXZ/C5jUFmQ==", "cpu": [ "x64" ], @@ -6898,77 +6545,59 @@ "license": "MIT", "optional": true, "os": [ - "linux" - ] + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/@lmdb/lmdb-win32-arm64": { - "version": "3.4.3", - "integrity": "sha512-yWVR0e5Gl35EGJBsAuqPOdjtUYuN8CcTLKrqpQFoM+KsMadViVCulhKNhkcjSGJB88Am5bRPjMro4MBB9FS23Q==", + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.6.0", + "integrity": "sha512-Ps/hui3A+vMbjdqlqAowK2ZL8+BO8dBjxeWXj6npTBs3jx4wWmbPpaLuqwrQrSqIVMCnpWo238bJ1U37GhQOYg==", "cpu": [ - "arm64" + "arm" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "win32" - ] + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/@lmdb/lmdb-win32-x64": { - "version": "3.4.3", - "integrity": "sha512-1JdBkcO0Vrua4LUgr4jAe4FUyluwCeq/pDkBrlaVjX3/BBWP1TzVjCL+TibWNQtPAL1BITXPAhlK5Ru4FBd/hg==", + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.6.0", + "integrity": "sha512-9c6AUHgHoG+IY88MRIHupztQiQnrbqHYQjkM2btA+Bf/wQnQMuiD0Wfk1EVv3TlNT3x41uU71rn6E4xh/+zvkw==", "cpu": [ - "x64" + "arm" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "win32" - ] - }, - "node_modules/@modelcontextprotocol/sdk": { - "version": "1.25.2", - "integrity": "sha512-LZFeo4F9M5qOhC/Uc1aQSrBHxMrvxett+9KLHt7OhcExtoiRN9DKgbZffMP/nxjutWDQpfMDfP3nkHI4X9ijww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@hono/node-server": "^1.19.7", - "ajv": "^8.17.1", - "ajv-formats": "^3.0.1", - "content-type": "^1.0.5", - "cors": "^2.8.5", - "cross-spawn": "^7.0.5", - "eventsource": "^3.0.2", - "eventsource-parser": "^3.0.0", - "express": "^5.0.1", - "express-rate-limit": "^7.5.0", - "jose": "^6.1.1", - "json-schema-typed": "^8.0.2", - "pkce-challenge": "^5.0.0", - "raw-body": "^3.0.0", - "zod": "^3.25 || ^4.0", - "zod-to-json-schema": "^3.25.0" - }, + "linux" + ], "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@cfworker/json-schema": "^4.1.1", - "zod": "^3.25 || ^4.0" + "node": ">= 10.0.0" }, - "peerDependenciesMeta": { - "@cfworker/json-schema": { - "optional": true - }, - "zod": { - "optional": false - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { - "version": "3.0.4", - "integrity": "sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ==", + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.6.0", + "integrity": "sha512-yHRqS2owEXe6Hic9z6Mh1ECsCd+ODVOGvZDyciqRd21+v+o+DnXMOrw50DSpIG2sb8GPEaPPmfeCAWKPJdq46g==", "cpu": [ "arm64" ], @@ -6976,51 +6605,59 @@ "license": "MIT", "optional": true, "os": [ - "darwin" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { - "version": "3.0.4", - "integrity": "sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w==", - "cpu": [ - "x64" + "linux" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { - "version": "3.0.4", - "integrity": "sha512-Tg3yX65f5GbtXLkrYEHE5oibZG9epyYWas7FogTTEJeDEF9JlXJzKgXaNhT3UXlTOeA+AfZpYZYZ0uPj7Cfquw==", + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.6.0", + "integrity": "sha512-WhB2e/V7rqdHHWZusBSPuy5Ei8S6lSz6FE5TKKQz5h3a0O+C+mhY7vxU9b/stqvMb8beLnPY82ZrFTLKs+SrKA==", "cpu": [ - "arm" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { - "version": "3.0.4", - "integrity": "sha512-dgX0P/9wGPJeHFBG+ZmhgE6bmtMt7NP5CRBGyyktpopdk/mW4POnrpQsSLtKI1dwpc+pPLuXHDh6vvskyQE/sw==", + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.6.0", + "integrity": "sha512-ulGE6x6Oz6iAwg75T8YQSoguBWasniIbX+QWpaYPcCnDOpdWX3k+4xbEYPZVLxOuoJI+svJJPD3sEj8G7lrQ3A==", "cpu": [ - "arm64" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { - "version": "3.0.4", - "integrity": "sha512-8TNXMEjJc3QEy7R/x1INhgiU+XakDAFUzBhaz7+Rbrs8NH5UQeHQxxmzsSBJGyV6I1jW79undiQm8tOI+D+8FQ==", + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.6.0", + "integrity": "sha512-tkBYKt7YQrjIJWYDnto2YgO8MRkjlMTSNoRHzsXinBqbLdeOM3L32wPZJvIZxqaLMfSlS/4sUjH/6STVP/XDLw==", "cpu": [ "x64" ], @@ -7029,24 +6666,38 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { - "version": "3.0.4", - "integrity": "sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ==", + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.6.0", + "integrity": "sha512-gIZAP23jaHjGWasY/TY6yL7NHFClf0Ga7FN+iINvk+KN94rhm94lYZhFsbYFNcA04/onvGD9kKmiJLJB2HbNwQ==", "cpu": [ - "x64" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "win32" - ] + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/@napi-rs/lzma-linux-x64-gnu": { - "version": "1.5.1", - "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.6.0", + "integrity": "sha512-cA+/pXV2YkfxlIcXOQ5fSWqAzzPyD78/x5qbK/I0vUkrlYHA8TIz+MXjAbGouguKVSI4bOmkTSJ1/poVSsgt+A==", "cpu": [ "x64" ], @@ -7054,256 +6705,321 @@ "license": "MIT", "optional": true, "os": [ - "linux" + "win32" ], "engines": { - "node": "^22.20 || ^24.12 || >=25" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@napi-rs/nice": { - "version": "1.1.1", - "integrity": "sha512-xJIPs+bYuc9ASBl+cvGsKbGrJmS6fAKaSZCnT0lhahT5rhA2VVy9/EcIgd2JhtEuFOJNx7UHNn/qiTPTY4nrQw==", + "node_modules/@peculiar/asn1-cms": { + "version": "2.11.0", + "integrity": "sha512-SlAiDoT/Cd8qaNmbTt78gqyaEvu2fQd6ML6KWJkdjXGrgDKnHHrusVkfQUelkfinL2p665IIADb0JBixtT8W1g==", "dev": true, "license": "MIT", - "optional": true, - "engines": { - "node": ">= 10" + "peer": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.11.0", + "@peculiar/asn1-x509": "^2.11.0", + "@peculiar/asn1-x509-attr": "^2.11.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "optionalDependencies": { - "@napi-rs/nice-android-arm-eabi": "1.1.1", - "@napi-rs/nice-android-arm64": "1.1.1", - "@napi-rs/nice-darwin-arm64": "1.1.1", - "@napi-rs/nice-darwin-x64": "1.1.1", - "@napi-rs/nice-freebsd-x64": "1.1.1", - "@napi-rs/nice-linux-arm-gnueabihf": "1.1.1", - "@napi-rs/nice-linux-arm64-gnu": "1.1.1", - "@napi-rs/nice-linux-arm64-musl": "1.1.1", - "@napi-rs/nice-linux-ppc64-gnu": "1.1.1", - "@napi-rs/nice-linux-riscv64-gnu": "1.1.1", - "@napi-rs/nice-linux-s390x-gnu": "1.1.1", - "@napi-rs/nice-linux-x64-gnu": "1.1.1", - "@napi-rs/nice-linux-x64-musl": "1.1.1", - "@napi-rs/nice-openharmony-arm64": "1.1.1", - "@napi-rs/nice-win32-arm64-msvc": "1.1.1", - "@napi-rs/nice-win32-ia32-msvc": "1.1.1", - "@napi-rs/nice-win32-x64-msvc": "1.1.1" + "engines": { + "node": ">=14" } }, - "node_modules/@napi-rs/nice-android-arm-eabi": { - "version": "1.1.1", - "integrity": "sha512-kjirL3N6TnRPv5iuHw36wnucNqXAO46dzK9oPb0wj076R5Xm8PfUVA9nAFB5ZNMmfJQJVKACAPd/Z2KYMppthw==", - "cpu": [ - "arm" - ], + "node_modules/@peculiar/asn1-csr": { + "version": "2.11.0", + "integrity": "sha512-M/ED0yJ05qMK+UADPKnIa8vhAeewOJALSxTAZB7RQ/batRHhIJZkwznCcWAbFO5saHJ1yEQ+m4lncEcG9RnQSg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "peer": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.11.0", + "@peculiar/asn1-x509": "^2.11.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, "engines": { - "node": ">= 10" + "node": ">=14" } }, - "node_modules/@napi-rs/nice-android-arm64": { - "version": "1.1.1", - "integrity": "sha512-blG0i7dXgbInN5urONoUCNf+DUEAavRffrO7fZSeoRMJc5qD+BJeNcpr54msPF6qfDD6kzs9AQJogZvT2KD5nw==", - "cpu": [ - "arm64" - ], + "node_modules/@peculiar/asn1-ecc": { + "version": "2.11.0", + "integrity": "sha512-jj1alGEKev7/un+cI+exyVK9m31lmP7cCBMCZuCb0wkFQH2zmKmJzNiG4wqXFNzlcP8timHLnzf3lV3OxBpb/Q==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "peer": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.11.0", + "@peculiar/asn1-x509": "^2.11.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, "engines": { - "node": ">= 10" + "node": ">=14" } }, - "node_modules/@napi-rs/nice-darwin-arm64": { - "version": "1.1.1", - "integrity": "sha512-s/E7w45NaLqTGuOjC2p96pct4jRfo61xb9bU1unM/MJ/RFkKlJyJDx7OJI/O0ll/hrfpqKopuAFDV8yo0hfT7A==", - "cpu": [ - "arm64" - ], + "node_modules/@peculiar/asn1-pfx": { + "version": "2.11.0", + "integrity": "sha512-ykSAWRu8KkmzU8LRpk9ZQC3zuLmBEtoSMjIv9XHVamD5W7amqreV5wttKqnvtjWOzzSJQxA/HVT8/tHwpEEGSQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "peer": true, + "dependencies": { + "@peculiar/asn1-cms": "^2.11.0", + "@peculiar/asn1-pkcs8": "^2.11.0", + "@peculiar/asn1-rsa": "^2.11.0", + "@peculiar/asn1-schema": "^2.11.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, "engines": { - "node": ">= 10" + "node": ">=14" } }, - "node_modules/@napi-rs/nice-darwin-x64": { - "version": "1.1.1", - "integrity": "sha512-dGoEBnVpsdcC+oHHmW1LRK5eiyzLwdgNQq3BmZIav+9/5WTZwBYX7r5ZkQC07Nxd3KHOCkgbHSh4wPkH1N1LiQ==", - "cpu": [ - "x64" - ], + "node_modules/@peculiar/asn1-pkcs8": { + "version": "2.11.0", + "integrity": "sha512-lragjkjQGnQ5luxC+sxoAfAO/jBiQ7hOEGlDKJUk7MC5zY3AzjB2DU0GyPUH1Sswdv10t7DurY0zylOYMLb88w==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "peer": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.11.0", + "@peculiar/asn1-x509": "^2.11.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, "engines": { - "node": ">= 10" + "node": ">=14" } }, - "node_modules/@napi-rs/nice-freebsd-x64": { - "version": "1.1.1", - "integrity": "sha512-kHv4kEHAylMYmlNwcQcDtXjklYp4FCf0b05E+0h6nDHsZ+F0bDe04U/tXNOqrx5CmIAth4vwfkjjUmp4c4JktQ==", - "cpu": [ - "x64" - ], + "node_modules/@peculiar/asn1-pkcs9": { + "version": "2.11.0", + "integrity": "sha512-gDdFjpqV0weJjgNUvueUVf4D8uVbpq+VbG0TsxLAY2QGljAkJyL9qwQ7avol9j0jaYSJ7Ey7v7RZp0M3GTcQSA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], + "peer": true, + "dependencies": { + "@peculiar/asn1-cms": "^2.11.0", + "@peculiar/asn1-pfx": "^2.11.0", + "@peculiar/asn1-pkcs8": "^2.11.0", + "@peculiar/asn1-schema": "^2.11.0", + "@peculiar/asn1-x509": "^2.11.0", + "@peculiar/asn1-x509-attr": "^2.11.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, "engines": { - "node": ">= 10" + "node": ">=14" } }, - "node_modules/@napi-rs/nice-linux-arm-gnueabihf": { - "version": "1.1.1", - "integrity": "sha512-E1t7K0efyKXZDoZg1LzCOLxgolxV58HCkaEkEvIYQx12ht2pa8hoBo+4OB3qh7e+QiBlp1SRf+voWUZFxyhyqg==", - "cpu": [ - "arm" - ], + "node_modules/@peculiar/asn1-rsa": { + "version": "2.11.0", + "integrity": "sha512-S2sBJmbfI09Kzdt9cEhE6pDNjHpVTkURs+811+PxS8AvsTLxC4woMNRhxHYaIbClkkNfAVt0tZxJdINVfUmzuA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "peer": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.11.0", + "@peculiar/asn1-x509": "^2.11.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, "engines": { - "node": ">= 10" + "node": ">=14" } }, - "node_modules/@napi-rs/nice-linux-arm64-gnu": { - "version": "1.1.1", - "integrity": "sha512-CIKLA12DTIZlmTaaKhQP88R3Xao+gyJxNWEn04wZwC2wmRapNnxCUZkVwggInMJvtVElA+D4ZzOU5sX4jV+SmQ==", - "cpu": [ - "arm64" - ], + "node_modules/@peculiar/asn1-schema": { + "version": "2.11.0", + "integrity": "sha512-8xh4avo53/vXO6UEv9yaoJ83hY581geaLTygf61NPIXP5AeIZCPjJhpTaYU23IZkG+NpLN/C9FYzWp7hphIT9A==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "peer": true, + "dependencies": { + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, "engines": { - "node": ">= 10" + "node": ">=14" } }, - "node_modules/@napi-rs/nice-linux-arm64-musl": { - "version": "1.1.1", - "integrity": "sha512-+2Rzdb3nTIYZ0YJF43qf2twhqOCkiSrHx2Pg6DJaCPYhhaxbLcdlV8hCRMHghQ+EtZQWGNcS2xF4KxBhSGeutg==", - "cpu": [ - "arm64" - ], + "node_modules/@peculiar/asn1-x509": { + "version": "2.11.0", + "integrity": "sha512-J1sHAwGNlk+8J+FeR1eLVc6Gq0T9f9ITc3HWyPYgJraoW6U8AJuQiWVJ3SCEpltfTdpOY7fHdHT5oKAsbK/jww==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "peer": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.11.0", + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, "engines": { - "node": ">= 10" + "node": ">=14" } }, - "node_modules/@napi-rs/nice-linux-ppc64-gnu": { - "version": "1.1.1", - "integrity": "sha512-4FS8oc0GeHpwvv4tKciKkw3Y4jKsL7FRhaOeiPei0X9T4Jd619wHNe4xCLmN2EMgZoeGg+Q7GY7BsvwKpL22Tg==", - "cpu": [ - "ppc64" - ], + "node_modules/@peculiar/asn1-x509-attr": { + "version": "2.11.0", + "integrity": "sha512-Fgvnd9REmLxEj/4cbDP54dNUNUXkzUksXLqEHqs1mHBuSRjSTCUSPM/WQIZYSvrdG7ND9CX1AQtPoIBekhgQSA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "peer": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.11.0", + "@peculiar/asn1-x509": "^2.11.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, "engines": { - "node": ">= 10" + "node": ">=14" } }, - "node_modules/@napi-rs/nice-linux-riscv64-gnu": { - "version": "1.1.1", - "integrity": "sha512-HU0nw9uD4FO/oGCCk409tCi5IzIZpH2agE6nN4fqpwVlCn5BOq0MS1dXGjXaG17JaAvrlpV5ZeyZwSon10XOXw==", - "cpu": [ - "riscv64" - ], + "node_modules/@peculiar/utils": { + "version": "2.0.3", + "integrity": "sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" + "peer": true, + "dependencies": { + "tslib": "^2.8.1" } }, - "node_modules/@napi-rs/nice-linux-s390x-gnu": { - "version": "1.1.1", - "integrity": "sha512-2YqKJWWl24EwrX0DzCQgPLKQBxYDdBxOHot1KWEq7aY2uYeX+Uvtv4I8xFVVygJDgf6/92h9N3Y43WPx8+PAgQ==", - "cpu": [ - "s390x" - ], + "node_modules/@peculiar/x509": { + "version": "1.14.3", + "integrity": "sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "peer": true, + "dependencies": { + "@peculiar/asn1-cms": "^2.6.0", + "@peculiar/asn1-csr": "^2.6.0", + "@peculiar/asn1-ecc": "^2.6.0", + "@peculiar/asn1-pkcs9": "^2.6.0", + "@peculiar/asn1-rsa": "^2.6.0", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "pvtsutils": "^1.3.6", + "reflect-metadata": "^0.2.2", + "tslib": "^2.8.1", + "tsyringe": "^4.10.0" + }, "engines": { - "node": ">= 10" + "node": ">=20.0.0" } }, - "node_modules/@napi-rs/nice-linux-x64-gnu": { - "version": "1.1.1", - "integrity": "sha512-/gaNz3R92t+dcrfCw/96pDopcmec7oCcAQ3l/M+Zxr82KT4DljD37CpgrnXV+pJC263JkW572pdbP3hP+KjcIg==", - "cpu": [ - "x64" - ], + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">= 10" + "node": ">=14" } }, - "node_modules/@napi-rs/nice-linux-x64-musl": { - "version": "1.1.1", - "integrity": "sha512-xScCGnyj/oppsNPMnevsBe3pvNaoK7FGvMjT35riz9YdhB2WtTG47ZlbxtOLpjeO9SqqQ2J2igCmz6IJOD5JYw==", - "cpu": [ - "x64" - ], + "node_modules/@pkgr/core": { + "version": "0.3.6", + "integrity": "sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">= 10" + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" } }, - "node_modules/@napi-rs/nice-openharmony-arm64": { - "version": "1.1.1", - "integrity": "sha512-6uJPRVwVCLDeoOaNyeiW0gp2kFIM4r7PL2MczdZQHkFi9gVlgm+Vn+V6nTWRcu856mJ2WjYJiumEajfSm7arPQ==", + "node_modules/@playwright/test": { + "version": "1.64.1", + "integrity": "sha512-IaYGml/nSOiZWROfPtuIL7neuu9qDOZ3TNvNljUIrhDMxiGTgPjzj/g9p9RxpS2tSepPDRY+UwAaEeiKtUvIAg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.64.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@primeuix/motion": { + "version": "0.0.10", + "integrity": "sha512-PsZwOPq79Scp7/ionshRcQ5xKVf9+zuLcyY5mf6onK8chHT5C9JGphmcIZ4CzcqxuGEpsm8AIbTGy+zS3RtzLA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@primeuix/utils": "^0.6.3" + }, + "engines": { + "node": ">=12.11.0" + } + }, + "node_modules/@primeuix/motion/node_modules/@primeuix/utils": { + "version": "0.6.4", + "integrity": "sha512-pZ5f+vj7wSzRhC7KoEQRU5fvYAe+RP9+m39CTscZ3UywCD1Y2o6Fe1rRgklMPSkzUcty2jzkA0zMYkiJBD1hgg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12.11.0" + } + }, + "node_modules/@primeuix/styled": { + "version": "0.7.4", + "integrity": "sha512-QSO/NpOQg8e9BONWRBx9y8VGMCMYz0J/uKfNJEya/RGEu7ARx0oYW0ugI1N3/KB1AAvyGxzKBzGImbwg0KUiOQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@primeuix/utils": "^0.6.1" + }, + "engines": { + "node": ">=12.11.0" + } + }, + "node_modules/@primeuix/styled/node_modules/@primeuix/utils": { + "version": "0.6.4", + "integrity": "sha512-pZ5f+vj7wSzRhC7KoEQRU5fvYAe+RP9+m39CTscZ3UywCD1Y2o6Fe1rRgklMPSkzUcty2jzkA0zMYkiJBD1hgg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12.11.0" + } + }, + "node_modules/@primeuix/styles": { + "version": "2.0.3", + "integrity": "sha512-2ykAB6BaHzR/6TwF8ShpJTsZrid6cVIEBVlookSdvOdmlWuevGu5vWOScgIwqWwlZcvkFYAGR/SUV3OHCTBMdw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@primeuix/styled": "^0.7.4" + } + }, + "node_modules/@primeuix/utils": { + "version": "0.7.2", + "integrity": "sha512-pmEbSfP0Phf9W9RweiM66zXnkn73ZeKyYINElbX3uZ2+stzzaba2svLAl3B1pHVcRw5t43O0VciaGe4ye2EXKw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12.11.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.4", + "integrity": "sha512-vRq9f4NzvbdZavhQbjkJBx7rRebDKYR9zHfO/Wg486+I7bSecdUapzCm5cyXoK+LHokTxgSq7A5baAXUZkIz0w==", "cpu": [ "arm64" ], @@ -7311,15 +7027,15 @@ "license": "MIT", "optional": true, "os": [ - "openharmony" + "android" ], "engines": { - "node": ">= 10" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@napi-rs/nice-win32-arm64-msvc": { - "version": "1.1.1", - "integrity": "sha512-uoTb4eAvM5B2aj/z8j+Nv8OttPf2m+HVx3UjA5jcFxASvNhQriyCQF1OB1lHL43ZhW+VwZlgvjmP5qF3+59atA==", + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.4", + "integrity": "sha512-kFgEvkWLqt3YCgKB5re9RlIrx9bRsvyVUnaTakEpOPuLGzLpLapYxE9BufJNvPg8GjT6mB1alN4yN1NjzoeM8Q==", "cpu": [ "arm64" ], @@ -7327,31 +7043,31 @@ "license": "MIT", "optional": true, "os": [ - "win32" + "darwin" ], "engines": { - "node": ">= 10" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@napi-rs/nice-win32-ia32-msvc": { - "version": "1.1.1", - "integrity": "sha512-CNQqlQT9MwuCsg1Vd/oKXiuH+TcsSPJmlAFc5frFyX/KkOh0UpBLEj7aoY656d5UKZQMQFP7vJNa1DNUNORvug==", + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.4", + "integrity": "sha512-JXmaOJGsL/+rsmMfutcDjxWM2fTaVgCHGoXS7nE8Z3c9NAYjGqHvXrAhMUZvMpHS/k7Mg+X7n/MVKb7NYWKKww==", "cpu": [ - "ia32" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "win32" + "darwin" ], "engines": { - "node": ">= 10" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@napi-rs/nice-win32-x64-msvc": { - "version": "1.1.1", - "integrity": "sha512-vB+4G/jBQCAh0jelMTY3+kgFy00Hlx2f2/1zjMoH821IbplbWZOkLiTYXQkygNTzQJTq5cvwBDgn2ppHD+bglQ==", + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.4", + "integrity": "sha512-ep3Catd6sPnHTM0P4hNEvIv5arnDvk01PfyJIJ+J3wVCG1eEaPo09tvFqdtcaTrkwQy0VWR24uz+cb4IsK53Qw==", "cpu": [ "x64" ], @@ -7359,421 +7075,324 @@ "license": "MIT", "optional": true, "os": [ - "win32" + "freebsd" ], "engines": { - "node": ">= 10" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.2.5", - "integrity": "sha512-HshSvXzmBxNzqejd3k/KemgQ3hdREUYFRRrgxOZ+gPljDIsO2SDZrVZMd4Z/HsxudQbE62OVyVFE8z9BCurfjA==", + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.4", + "integrity": "sha512-LwA5ayKIpnsgXJEwWc3h8wPiS33NMIHd9BhsV92T8VetVAbGe2qXlJwNVDGHN5cOQ22R9uYvbrQir2AB+ntT2w==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.3" - }, + "os": [ + "linux" + ], "engines": { - "node": "^20.19.0 || ^22.13.0 || >=23.5.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", - "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@ngtools/webpack": { - "version": "21.2.26", - "integrity": "sha512-V8ScM6JQdTceaiP/fxSVYcrfABZzUbzYbMLV5nvAL6+x4RgX8uAew+sPUjnyxjBhn/vIG9A36cJ+j7SJtiATIA==", + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.4", + "integrity": "sha512-AC1WsGdlV1MtGay/OQ4J9T7GRadVnpYRzTcygV1hKnypbYN20Yh4t6O1Sa2qRBMqv1etulUknqXjc3CTIsBu6A==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "peer": true, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "@angular/compiler-cli": "^21.0.0", - "typescript": ">=5.9 <6.0", - "webpack": "^5.54.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@noble/hashes": { - "version": "1.8.0", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.4", + "integrity": "sha512-lU+6rgXXViO61B4EudxtVMXSOfiZONR29Sys5VGSetUY7X8mg9FCKIIjcPPj8xNDeYzKl+H8F/qSKOBVFJChCQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "peer": true, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.4", + "integrity": "sha512-DZaN1f0PGp/bSvKhtw50pPsnln4T13ycDq1FrDWRiHmWt1JeW+UtYg9touPFf8yt993p8tS2QjybpzKNTxYEwg==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 8" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.4", + "integrity": "sha512-RnGxwZLN7fhMMAItnD6dZ7lvy+TI7ba+2V54UF4dhaWa/p8I/ys1E73KO6HmPmgz92ZkfD8TXS1IMV8+uhbR9g==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 8" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.4", + "integrity": "sha512-6lcI79+X8klGiGd8yHuTgQRjuuJYNggmEml+RsyN596P23l/zf9FVmJ7K0KVKkFAeYEdg0iMUKyIxiV5vebDNQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, + "optional": true, + "os": [ + "openharmony" + ], "engines": { - "node": ">= 8" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@npmcli/agent": { - "version": "4.0.2", - "integrity": "sha512-EUEuWAxnL07Sp5/iC/1X6Xj+XThUvnbei9zfRWZdEXa7lss9RTHMhAHBeg+MZ5To9s/gGaSI+UwZTPdYMvKSeg==", + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.4", + "integrity": "sha512-wz7ohsKCAIWy91blZ/1FlpPdqrsm1xpcEOQVveWoL6+aSPKL4VUcoYmmzuLTssyZxRpEwzuIxL/GDsvpjaBtOw==", + "cpu": [ + "wasm32" + ], "dev": true, - "license": "ISC", + "license": "MIT", + "optional": true, "dependencies": { - "agent-base": "^7.1.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.1", - "lru-cache": "^11.2.1", - "socks-proxy-agent": "^8.0.3" + "@napi-rs/wasm-runtime": "^1.1.1" }, "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": ">=14.0.0" } }, - "node_modules/@npmcli/agent/node_modules/lru-cache": { - "version": "11.5.3", - "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.4", + "integrity": "sha512-cfiMrfuWCIgsFmcVG0IPuO6qTRHvF7NuG3wngX1RZzc6dU8FuBFb+J3MIR5WrdTNozlumfgL4cvz+R4ozBCvsQ==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "BlueOak-1.0.0", + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": "20 || >=22" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@npmcli/fs": { - "version": "5.0.0", - "integrity": "sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og==", + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.4", + "integrity": "sha512-p6UeR9y7ht82AH57qwGuFYn69S6CZ7LLKdCKy/8T3zS9VTrJei2/CGsTUV45Da4Z9Rbhc7G4gyWQ/Ioamqn09g==", + "cpu": [ + "x64" + ], "dev": true, - "license": "ISC", - "dependencies": { - "semver": "^7.3.5" - }, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@npmcli/git": { - "version": "7.0.2", - "integrity": "sha512-oeolHDjExNAJAnlYP2qzNjMX/Xi9bmu78C9dIGr4xjobrSKbuMYCph8lTzn4vnW3NjIqVmw/f8BCfouqyJXlRg==", + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.4", + "integrity": "sha512-1BrrmTu0TWfOP1riA8uakjFc9bpIUGzVKETsOtzY39pPga8zELGDl8eu1Dx7/gjM5CAz14UknsUMpBO8L+YntQ==", "dev": true, - "license": "ISC", + "license": "MIT" + }, + "node_modules/@rollup/plugin-json": { + "version": "6.1.0", + "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", + "dev": true, + "license": "MIT", "dependencies": { - "@gar/promise-retry": "^1.0.0", - "@npmcli/promise-spawn": "^9.0.0", - "ini": "^6.0.0", - "lru-cache": "^11.2.1", - "npm-pick-manifest": "^11.0.1", - "proc-log": "^6.0.0", - "semver": "^7.3.5", - "which": "^6.0.0" + "@rollup/pluginutils": "^5.1.0" }, "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } } }, - "node_modules/@npmcli/git/node_modules/@npmcli/promise-spawn": { - "version": "9.0.1", - "integrity": "sha512-OLUaoqBuyxeTqUvjA3FZFiXUfYC1alp3Sa99gW3EUDz3tZ3CbXDdcZ7qWKBzicrJleIgucoWamWH1saAmH/l2Q==", + "node_modules/@rollup/pluginutils": { + "version": "5.4.0", + "integrity": "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "which": "^6.0.0" + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" }, "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } } }, - "node_modules/@npmcli/git/node_modules/ini": { - "version": "6.0.0", - "integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==", + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.64.6", + "integrity": "sha512-FkO19Fowv/OQeJZPVfdetJaI3/VpbZmBsnmAlR/rQ+b3QwfiGQiOpVegpSRH331kelvHTVw9hXYG3DwjAaRzyQ==", + "cpu": [ + "arm" + ], "dev": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } + "license": "MIT", + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@npmcli/git/node_modules/isexe": { - "version": "4.0.0", - "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.64.6", + "integrity": "sha512-si5/Etm6MphPc2xYHnk455Shls/hoMjTy3mhXge4OPmvCw7TwC4KP4fKokAlKLV4XaQvLvgSHfafPcblLMYYdQ==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=20" - } + "license": "MIT", + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@npmcli/git/node_modules/lru-cache": { - "version": "11.5.3", - "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.64.6", + "integrity": "sha512-PQCl3q7b0BgvVt72i2xrWM6I5KliwwVe4GpWW5QDGyUQawDS9aPZss/aGyFqBmxdRxlE8wDc9CaxiQ5DHRSp/Q==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@npmcli/git/node_modules/proc-log": { - "version": "6.1.0", - "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.64.6", + "integrity": "sha512-l9DFl1Itb1YYdSrP1K3U8ih44AOHa/p+Q4WMMDfGvH5qVWp+ajfgS1DlYo4yCRh8wJ2ZJv0Fqccf+ULAmMFjmg==", + "cpu": [ + "x64" + ], "dev": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@npmcli/git/node_modules/which": { - "version": "6.0.1", - "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.64.6", + "integrity": "sha512-bR4GR3205N9+od0+l5Dd9/iZTDpJkIe6d6Ydp2qbcQZJLmKBdYQQRp8gbWpbMt92twKJY66o1vK4NkoKQODIrA==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^4.0.0" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] }, - "node_modules/@npmcli/installed-package-contents": { - "version": "3.0.0", - "integrity": "sha512-fkxoPuFGvxyrH+OQzyTkX2LUEamrF4jZSmxjAtPPHHGO0dqsQ8tTKjnIS8SAnPHdk2I03BDtSMR5K/4loKg79Q==", + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.64.6", + "integrity": "sha512-XTVNaDzcFl2TeBglvxdGgsCv9JGtAGRAoJENmGBR/XvDH2mA2K0IXc2aMfP0dR8mexSXSKifJ85FixoXX1wfTw==", + "cpu": [ + "x64" + ], "dev": true, - "license": "ISC", - "dependencies": { - "npm-bundled": "^4.0.0", - "npm-normalize-package-bin": "^4.0.0" - }, - "bin": { - "installed-package-contents": "bin/index.js" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] }, - "node_modules/@npmcli/node-gyp": { - "version": "5.0.0", - "integrity": "sha512-uuG5HZFXLfyFKqg8QypsmgLQW7smiRjVc45bqD/ofZZcR/uxEjgQU8qDPv0s9TEeMUiAAU/GC5bR6++UdTirIQ==", + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.64.6", + "integrity": "sha512-qWc6qbc+25ugG9vqPR9P422KJBbVfZ05mjcMggoq8tsfWP+eRfC+/tiDdIlRF5VL2pDVZWN9zAhWsmu7R1+7Dw==", + "cpu": [ + "arm" + ], "dev": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@npmcli/package-json": { - "version": "7.0.5", - "integrity": "sha512-iVuTlG3ORq2iaVa1IWUxAO/jIp77tUKBhoMjuzYW2kL4MLN1bi/ofqkZ7D7OOwh8coAx1/S2ge0rMdGv8sLSOQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "@npmcli/git": "^7.0.0", - "glob": "^13.0.0", - "hosted-git-info": "^9.0.0", - "json-parse-even-better-errors": "^5.0.0", - "proc-log": "^6.0.0", - "semver": "^7.5.3", - "spdx-expression-parse": "^4.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@npmcli/package-json/node_modules/proc-log": { - "version": "6.1.0", - "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@npmcli/promise-spawn": { - "version": "8.0.3", - "integrity": "sha512-Yb00SWaL4F8w+K8YGhQ55+xE4RUNdMHV43WZGsiTM92gS+lC0mGsn7I4hLug7pbao035S6bj3Y3w0cUNGLfmkg==", - "dev": true, - "license": "ISC", - "dependencies": { - "which": "^5.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/@npmcli/promise-spawn/node_modules/isexe": { - "version": "3.1.5", - "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/@npmcli/promise-spawn/node_modules/which": { - "version": "5.0.0", - "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/@npmcli/redact": { - "version": "4.0.0", - "integrity": "sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@npmcli/run-script": { - "version": "10.0.4", - "integrity": "sha512-mGUWr1uMnf0le2TwfOZY4SFxZGXGfm4Jtay/nwAa2FLNAKXUoUwaGwBMNH36UHPtinWfTSJ3nqFQr0091CxVGg==", - "dev": true, - "license": "ISC", - "dependencies": { - "@npmcli/node-gyp": "^5.0.0", - "@npmcli/package-json": "^7.0.0", - "@npmcli/promise-spawn": "^9.0.0", - "node-gyp": "^12.1.0", - "proc-log": "^6.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@npmcli/run-script/node_modules/@npmcli/promise-spawn": { - "version": "9.0.1", - "integrity": "sha512-OLUaoqBuyxeTqUvjA3FZFiXUfYC1alp3Sa99gW3EUDz3tZ3CbXDdcZ7qWKBzicrJleIgucoWamWH1saAmH/l2Q==", - "dev": true, - "license": "ISC", - "dependencies": { - "which": "^6.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@npmcli/run-script/node_modules/isexe": { - "version": "4.0.0", - "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=20" - } - }, - "node_modules/@npmcli/run-script/node_modules/proc-log": { - "version": "6.1.0", - "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@npmcli/run-script/node_modules/which": { - "version": "6.0.1", - "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^4.0.0" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@oxc-project/types": { - "version": "0.96.0", - "integrity": "sha512-r/xkmoXA0xEpU6UGtn18CNVjXH6erU3KCpCDbpLmbVxBFor1U9MqN5Z2uMmCHJuXjJzlnDR+hWY+yPoLo8oHDw==", + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.64.6", + "integrity": "sha512-V4Nwkp8844X/Xnww3PI1LYS0YPei8XQEVG70R+5MPZGO7AhYoun4hCN7Svs4U3wbQLZbwiz+9xRFhUZZdRl8Ww==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@parcel/watcher": { - "version": "2.6.0", - "integrity": "sha512-7FNeNl8NCE7aINx7WXiKQrPYZWC/hvrTsmk6zmxbI7LTXE7hVek/n8AfVgpe2y82zl3w0HvCHN0bVKMBoJcC0w==", + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.64.6", + "integrity": "sha512-xb72A1aPaaIsp+a88VtRae/ovIEXislbUMyp7b9AQQU7ZkP2m/+ik6DW9TQcJyw5R8/RIailF9zExYwkjR++UA==", + "cpu": [ + "arm64" + ], "dev": true, - "hasInstallScript": true, "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.3", - "is-glob": "^4.0.3", - "node-addon-api": "^7.0.0", - "picomatch": "^4.0.4" - }, - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "@parcel/watcher-android-arm64": "2.6.0", - "@parcel/watcher-darwin-arm64": "2.6.0", - "@parcel/watcher-darwin-x64": "2.6.0", - "@parcel/watcher-freebsd-x64": "2.6.0", - "@parcel/watcher-linux-arm-glibc": "2.6.0", - "@parcel/watcher-linux-arm-musl": "2.6.0", - "@parcel/watcher-linux-arm64-glibc": "2.6.0", - "@parcel/watcher-linux-arm64-musl": "2.6.0", - "@parcel/watcher-linux-x64-glibc": "2.6.0", - "@parcel/watcher-linux-x64-musl": "2.6.0", - "@parcel/watcher-win32-arm64": "2.6.0", - "@parcel/watcher-win32-x64": "2.6.0" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@parcel/watcher-android-arm64": { - "version": "2.6.0", - "integrity": "sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA==", + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.64.6", + "integrity": "sha512-pbcs1BVBwC43hZkjhjlF8Rx8m5G0KnhLZs9Azy/gqccP6UsNSzJr0hKGIAVPqzAH1O/NdPxxC9GVGb4msQg/aw==", "cpu": [ "arm64" ], @@ -7781,159 +7400,103 @@ "license": "MIT", "optional": true, "os": [ - "android" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } + "linux" + ] }, - "node_modules/@parcel/watcher-darwin-arm64": { - "version": "2.6.0", - "integrity": "sha512-Y3QV0gl7Q1zbfueunkWIERICbEojQFCgpyG7YqOGNFLsckXyI1xu9mAIUpKY9QBYzBtSkN8dBPwd3yiAO9ovMw==", + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.64.6", + "integrity": "sha512-EKOC8os6Vsg487ZLKUqzLF99Jsmnq6tu7O7mAELakNpcA+8KadktFf/JYdRuHe/DzCUXmN38pUKu4XBcKlqGbQ==", "cpu": [ - "arm64" + "loong64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "darwin" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } + "linux" + ] }, - "node_modules/@parcel/watcher-darwin-x64": { - "version": "2.6.0", - "integrity": "sha512-Ohv6OpzhUfKYD7Beb8kDvG0jbIxORCYY1JRdZnaBtnjjkJxgD7ZVL0nw2sCYd0yTMKTvz3nnTnOF3cDifK+kvw==", + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.64.6", + "integrity": "sha512-v/SQ68GnSlr/HyjB79sNXesGDwkOPu9JjbHfmWaymCiM+nZsQ9TstG1KfA4MuEvTr2d3PoY8PhFxj9bt0e32JA==", "cpu": [ - "x64" + "loong64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "darwin" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } + "linux" + ] }, - "node_modules/@parcel/watcher-freebsd-x64": { - "version": "2.6.0", - "integrity": "sha512-5HmXvDgs8VK+74jF9y9/2FE3/OnlcKmc56tjmSrEuZjpSZOGL+fvAu+HKJBdPs9uwoP2hE6TlSUpXZ/C5jUFmQ==", + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.64.6", + "integrity": "sha512-G3pmliMQ7r15zFFNwBlaT/xTdSLi4lfgI7CUrQueyKYheNVhrd8JrWCE6Ll8xf4AA1RZFqq+mNJYntiElsVOyA==", "cpu": [ - "x64" + "ppc64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } + "linux" + ] }, - "node_modules/@parcel/watcher-linux-arm-glibc": { - "version": "2.6.0", - "integrity": "sha512-Ps/hui3A+vMbjdqlqAowK2ZL8+BO8dBjxeWXj6npTBs3jx4wWmbPpaLuqwrQrSqIVMCnpWo238bJ1U37GhQOYg==", + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.64.6", + "integrity": "sha512-XQTB/IPoB/wG6x/wu9uomAt8YH61IAr5iZTubMiad/lu88sotq2ocz9hJd556+Jz2kIkF7ZQZIsUuWE3OWqtcA==", "cpu": [ - "arm" + "ppc64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } + ] }, - "node_modules/@parcel/watcher-linux-arm-musl": { - "version": "2.6.0", - "integrity": "sha512-9c6AUHgHoG+IY88MRIHupztQiQnrbqHYQjkM2btA+Bf/wQnQMuiD0Wfk1EVv3TlNT3x41uU71rn6E4xh/+zvkw==", + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.64.6", + "integrity": "sha512-O5mpokth6+ydMuwiGtY8fIjvOBfH7u6SzmDNmZ3k6GK5C6iEAC2q2ZN/T5L9jYILx0IcCT0KnfMDihQ5yQFSBw==", "cpu": [ - "arm" + "riscv64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } + ] }, - "node_modules/@parcel/watcher-linux-arm64-glibc": { - "version": "2.6.0", - "integrity": "sha512-yHRqS2owEXe6Hic9z6Mh1ECsCd+ODVOGvZDyciqRd21+v+o+DnXMOrw50DSpIG2sb8GPEaPPmfeCAWKPJdq46g==", + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.64.6", + "integrity": "sha512-aIFxbAD9v3TvIaEDsKmukjcDg1JS4/tFXw8SHyQQgzOwzWCQ4cyDK7Ju1pQKqUXDFkEu/BZCFDCDATsKhQcB/Q==", "cpu": [ - "arm64" + "riscv64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } + ] }, - "node_modules/@parcel/watcher-linux-arm64-musl": { - "version": "2.6.0", - "integrity": "sha512-WhB2e/V7rqdHHWZusBSPuy5Ei8S6lSz6FE5TKKQz5h3a0O+C+mhY7vxU9b/stqvMb8beLnPY82ZrFTLKs+SrKA==", + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.64.6", + "integrity": "sha512-fuNo1UwBc6BxnHDOxxguQL/InhheMN3RJcJMb0b4xP8r5Q3CNCrJbVg0eCIFuJ4e/zAfjR2gYbOfbzO/G7CIyw==", "cpu": [ - "arm64" + "s390x" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } + ] }, - "node_modules/@parcel/watcher-linux-x64-glibc": { - "version": "2.6.0", - "integrity": "sha512-ulGE6x6Oz6iAwg75T8YQSoguBWasniIbX+QWpaYPcCnDOpdWX3k+4xbEYPZVLxOuoJI+svJJPD3sEj8G7lrQ3A==", + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.64.6", + "integrity": "sha512-05v6wFKDKJPZs9xlytVrrRu6IbRuoSvlH9xNwEOafXsKyjBFZPvAcZz2VIAB/M+lblYeKa9iOYC4RDVdfcAAaw==", "cpu": [ "x64" ], @@ -7942,18 +7505,11 @@ "optional": true, "os": [ "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } + ] }, - "node_modules/@parcel/watcher-linux-x64-musl": { - "version": "2.6.0", - "integrity": "sha512-tkBYKt7YQrjIJWYDnto2YgO8MRkjlMTSNoRHzsXinBqbLdeOM3L32wPZJvIZxqaLMfSlS/4sUjH/6STVP/XDLw==", + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.64.6", + "integrity": "sha512-wJXKcdMjvkc/FaavZXNP1yW4i0pfOmhZAIWRSLG2yJ0PB0TWi7dkLErhX0u0vptDcXKKO/m1Y/YTeuyvb2MXwg==", "cpu": [ "x64" ], @@ -7962,18 +7518,24 @@ "optional": true, "os": [ "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.64.6", + "integrity": "sha512-cwg7/h0ThvJwy8CBkmACdiHAxRWpbsCmIsjOt+Zzx0hTnIcSUmKYEV03bGg+hD827OnwUsbKh1ZZDW754xWyBA==", + "cpu": [ + "x64" ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] }, - "node_modules/@parcel/watcher-win32-arm64": { - "version": "2.6.0", - "integrity": "sha512-gIZAP23jaHjGWasY/TY6yL7NHFClf0Ga7FN+iINvk+KN94rhm94lYZhFsbYFNcA04/onvGD9kKmiJLJB2HbNwQ==", + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.64.6", + "integrity": "sha512-T0RkYIJbdYXJEQFw+tMBO09KDFdgmNxkHsof3wWAABrvnx15Yh8RytmrgYy9+q9cBeBNZF66MMQZC+5nzEz6vg==", "cpu": [ "arm64" ], @@ -7981,3414 +7543,1914 @@ "license": "MIT", "optional": true, "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } + "openharmony" + ] }, - "node_modules/@parcel/watcher-win32-x64": { - "version": "2.6.0", - "integrity": "sha512-cA+/pXV2YkfxlIcXOQ5fSWqAzzPyD78/x5qbK/I0vUkrlYHA8TIz+MXjAbGouguKVSI4bOmkTSJ1/poVSsgt+A==", + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.64.6", + "integrity": "sha512-KlfpmF1AAIdAWxt8QQNClbpLFlAUfYOzqIuJ7IXEStMHpFRuH78RG6FjqPb3Rqhh0K89F5npvG+/i3Fjl6IveQ==", "cpu": [ - "x64" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.64.6", + "integrity": "sha512-MrTLjd7Jlt9/ZavJZYG61KQv9Iy9bzbY+yXy73Xvwoa2uP66wvDShkyBr5Chz/yrE6lbD/zHMuTmKQVNQqBApg==", + "cpu": [ + "ia32" ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@peculiar/asn1-cms": { - "version": "2.11.0", - "integrity": "sha512-SlAiDoT/Cd8qaNmbTt78gqyaEvu2fQd6ML6KWJkdjXGrgDKnHHrusVkfQUelkfinL2p665IIADb0JBixtT8W1g==", + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.64.6", + "integrity": "sha512-8fWDH1CkPI8GdBKAxzHDza7UmgficsKo2+MVbvUgOGhB0629w5sHMg3Qu5y7TZsrYKHHWpz8fla4KvoT7WsO3Q==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@peculiar/asn1-schema": "^2.11.0", - "@peculiar/asn1-x509": "^2.11.0", - "@peculiar/asn1-x509-attr": "^2.11.0", - "asn1js": "^3.0.10", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=14" - } + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@peculiar/asn1-csr": { - "version": "2.11.0", - "integrity": "sha512-M/ED0yJ05qMK+UADPKnIa8vhAeewOJALSxTAZB7RQ/batRHhIJZkwznCcWAbFO5saHJ1yEQ+m4lncEcG9RnQSg==", + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.64.6", + "integrity": "sha512-9/c9PWdr5GCIHkkxiHM1HjNBQMVdftP4O8QTcoUAaRza2FSqJ4axbf8QAnBuE6BIGQDl71SaRPeDdDYid5ecTw==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@peculiar/asn1-schema": "^2.11.0", - "@peculiar/asn1-x509": "^2.11.0", - "asn1js": "^3.0.10", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=14" - } + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@peculiar/asn1-ecc": { - "version": "2.11.0", - "integrity": "sha512-jj1alGEKev7/un+cI+exyVK9m31lmP7cCBMCZuCb0wkFQH2zmKmJzNiG4wqXFNzlcP8timHLnzf3lV3OxBpb/Q==", + "node_modules/@rollup/wasm-node": { + "version": "4.64.6", + "integrity": "sha512-ufelhiGxNl8RuflC/Tie5ZVcI3MJUtd27nJ0jC2I4LroyZb7n4iwmajEmXXeTLxJtDiiWUwU8v2UjkACBHqQvw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@peculiar/asn1-schema": "^2.11.0", - "@peculiar/asn1-x509": "^2.11.0", - "asn1js": "^3.0.10", - "tslib": "^2.8.1" + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" }, "engines": { - "node": ">=14" + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "fsevents": "~2.3.2" } }, - "node_modules/@peculiar/asn1-pfx": { - "version": "2.11.0", - "integrity": "sha512-ykSAWRu8KkmzU8LRpk9ZQC3zuLmBEtoSMjIv9XHVamD5W7amqreV5wttKqnvtjWOzzSJQxA/HVT8/tHwpEEGSQ==", + "node_modules/@rrweb/record": { + "version": "2.0.0-alpha.20", + "integrity": "sha512-marVOU3Lc285mwLCp+vI6M/eJ+wZ6lcTa7fX0zcdmBsM+j5loXmMQjkmIvkji5+lAbkq5/w2cwto3GS0TaCjtg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@peculiar/asn1-cms": "^2.11.0", - "@peculiar/asn1-pkcs8": "^2.11.0", - "@peculiar/asn1-rsa": "^2.11.0", - "@peculiar/asn1-schema": "^2.11.0", - "asn1js": "^3.0.10", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=14" + "@rrweb/types": "^2.0.0-alpha.20", + "@rrweb/utils": "^2.0.0-alpha.20", + "rrweb": "^2.0.0-alpha.20" } }, - "node_modules/@peculiar/asn1-pkcs8": { - "version": "2.11.0", - "integrity": "sha512-lragjkjQGnQ5luxC+sxoAfAO/jBiQ7hOEGlDKJUk7MC5zY3AzjB2DU0GyPUH1Sswdv10t7DurY0zylOYMLb88w==", + "node_modules/@rrweb/record/node_modules/rrdom": { + "version": "2.1.7", + "integrity": "sha512-jm7IN2aaG6891OqFAYw/b8dw5FLiMeCTrU4HIUr40I6tTaUX2Rrhba5vjjMRARQClBFDA04vvf9HFD9WnZFpFg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@peculiar/asn1-schema": "^2.11.0", - "@peculiar/asn1-x509": "^2.11.0", - "asn1js": "^3.0.10", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=14" + "rrweb-snapshot": "^2.1.7" } }, - "node_modules/@peculiar/asn1-pkcs9": { - "version": "2.11.0", - "integrity": "sha512-gDdFjpqV0weJjgNUvueUVf4D8uVbpq+VbG0TsxLAY2QGljAkJyL9qwQ7avol9j0jaYSJ7Ey7v7RZp0M3GTcQSA==", + "node_modules/@rrweb/record/node_modules/rrweb": { + "version": "2.1.7", + "integrity": "sha512-UZzK43vVmbtBt5WyEVO/CO8q9hKi38LRZDQS5C+X+FYgpVLK4vhWyUzzi1K0iXzHFb3MOVaTak5TAsdK5K6V6Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@peculiar/asn1-cms": "^2.11.0", - "@peculiar/asn1-pfx": "^2.11.0", - "@peculiar/asn1-pkcs8": "^2.11.0", - "@peculiar/asn1-schema": "^2.11.0", - "@peculiar/asn1-x509": "^2.11.0", - "@peculiar/asn1-x509-attr": "^2.11.0", - "asn1js": "^3.0.10", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=14" + "@rrweb/types": "^2.1.7", + "@rrweb/utils": "^2.1.7", + "@types/css-font-loading-module": "0.0.7", + "@xstate/fsm": "^1.4.0", + "base64-arraybuffer": "^1.0.1", + "mitt": "^3.0.0", + "rrdom": "^2.1.7", + "rrweb-snapshot": "^2.1.7" } }, - "node_modules/@peculiar/asn1-rsa": { - "version": "2.11.0", - "integrity": "sha512-S2sBJmbfI09Kzdt9cEhE6pDNjHpVTkURs+811+PxS8AvsTLxC4woMNRhxHYaIbClkkNfAVt0tZxJdINVfUmzuA==", + "node_modules/@rrweb/types": { + "version": "2.1.7", + "integrity": "sha512-fNua4du3xvMlC5iK8lYU1CEg+9wsA7FXUzahVBBVGBKqVPSifROLVh3mfV90hhsfAcs4jAvLbl23v28hXrxZWg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rrweb/utils": { + "version": "2.1.7", + "integrity": "sha512-ZQpnuthKb8U2BvSDtiX6z8XkEPUUbCXhcrOY5yqqtPP2O5RP8YgPlo8HkU/tFStMd/Wn8cfy9DmkhialwUUvrA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@schematics/angular": { + "version": "21.2.29", + "integrity": "sha512-u8WDSr+AcOo+IwfS4gXVB2jT+j8EhW8P7+zoYzaBywnQl4zYLsPalo0pB8I4tbsmPyZKIMrHmlZ3b6C3UYkvUA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@peculiar/asn1-schema": "^2.11.0", - "@peculiar/asn1-x509": "^2.11.0", - "asn1js": "^3.0.10", - "tslib": "^2.8.1" + "@angular-devkit/core": "21.2.29", + "@angular-devkit/schematics": "21.2.29", + "jsonc-parser": "3.3.1" }, "engines": { - "node": ">=14" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" } }, - "node_modules/@peculiar/asn1-schema": { - "version": "2.11.0", - "integrity": "sha512-8xh4avo53/vXO6UEv9yaoJ83hY581geaLTygf61NPIXP5AeIZCPjJhpTaYU23IZkG+NpLN/C9FYzWp7hphIT9A==", + "node_modules/@schematics/angular/node_modules/@angular-devkit/core": { + "version": "21.2.29", + "integrity": "sha512-807H6zbmVxoTZEZymJM/YKcn9BcHQsgCQg9Yki4jDAQIMockyIN9F7jscxHqBtLt2rXHiiDVyWiJhMEyot1OXQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@peculiar/utils": "^2.0.2", - "asn1js": "^3.0.10", - "tslib": "^2.8.1" + "ajv": "8.18.0", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.4", + "rxjs": "7.8.2", + "source-map": "0.7.6" }, "engines": { - "node": ">=14" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^5.0.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } } }, - "node_modules/@peculiar/asn1-x509": { - "version": "2.11.0", - "integrity": "sha512-J1sHAwGNlk+8J+FeR1eLVc6Gq0T9f9ITc3HWyPYgJraoW6U8AJuQiWVJ3SCEpltfTdpOY7fHdHT5oKAsbK/jww==", + "node_modules/@sigstore/bundle": { + "version": "4.0.0", + "integrity": "sha512-NwCl5Y0V6Di0NexvkTqdoVfmjTaQwoLM236r89KEojGmq/jMls8S+zb7yOwAPdXvbwfKDlP+lmXgAL4vKSQT+A==", "dev": true, - "license": "MIT", - "peer": true, + "license": "Apache-2.0", "dependencies": { - "@peculiar/asn1-schema": "^2.11.0", - "@peculiar/utils": "^2.0.2", - "asn1js": "^3.0.10", - "tslib": "^2.8.1" + "@sigstore/protobuf-specs": "^0.5.0" }, "engines": { - "node": ">=14" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@peculiar/asn1-x509-attr": { - "version": "2.11.0", - "integrity": "sha512-Fgvnd9REmLxEj/4cbDP54dNUNUXkzUksXLqEHqs1mHBuSRjSTCUSPM/WQIZYSvrdG7ND9CX1AQtPoIBekhgQSA==", + "node_modules/@sigstore/core": { + "version": "3.2.1", + "integrity": "sha512-qRsxPnCrbC/puegGxKuynfnxgLiHqWStrSjxkoB4YKqq3Z3s4cyZyj42ZdWFAEblNP65C+rBH8EuREHIXoi83g==", "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@peculiar/asn1-schema": "^2.11.0", - "@peculiar/asn1-x509": "^2.11.0", - "asn1js": "^3.0.10", - "tslib": "^2.8.1" - }, + "license": "Apache-2.0", "engines": { - "node": ">=14" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@peculiar/utils": { - "version": "2.0.3", - "integrity": "sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==", + "node_modules/@sigstore/protobuf-specs": { + "version": "0.5.2", + "integrity": "sha512-SQqvFMt4V78fdjcDdYX6HbiVSOR4QK3ZgwCa2KOsopAgPIHy1rU5UDUmzLl02r5oyyaYcYHR1hpwDRk/yUe+Mw==", "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "tslib": "^2.8.1" + "license": "Apache-2.0", + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/@peculiar/x509": { - "version": "1.14.3", - "integrity": "sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==", + "node_modules/@sigstore/sign": { + "version": "4.1.1", + "integrity": "sha512-Hf4xglukg0XXQ2RiD5vSoLjdPe8OBUPA8XeVjUObheuDcWdYWrnH/BNmxZCzkAy68MzmNCxXLeurJvs6hcP2OQ==", "dev": true, - "license": "MIT", - "peer": true, + "license": "Apache-2.0", "dependencies": { - "@peculiar/asn1-cms": "^2.6.0", - "@peculiar/asn1-csr": "^2.6.0", - "@peculiar/asn1-ecc": "^2.6.0", - "@peculiar/asn1-pkcs9": "^2.6.0", - "@peculiar/asn1-rsa": "^2.6.0", - "@peculiar/asn1-schema": "^2.6.0", - "@peculiar/asn1-x509": "^2.6.0", - "pvtsutils": "^1.3.6", - "reflect-metadata": "^0.2.2", - "tslib": "^2.8.1", - "tsyringe": "^4.10.0" + "@gar/promise-retry": "^1.0.2", + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.2.0", + "@sigstore/protobuf-specs": "^0.5.0", + "make-fetch-happen": "^15.0.4", + "proc-log": "^6.1.0" }, "engines": { - "node": ">=20.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "node_modules/@sigstore/tuf": { + "version": "4.0.2", + "integrity": "sha512-TCAzTy0xzdP79EnxSjq9KQ3eaR7+FmudLC6eRKknVKZbV7ZNlGLClAAQb/HMNJ5n2OBNk2GT1tEmU0xuPr+SLQ==", "dev": true, - "license": "MIT", - "optional": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.5.0", + "tuf-js": "^4.1.0" + }, "engines": { - "node": ">=14" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@pkgr/core": { - "version": "0.3.6", - "integrity": "sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==", + "node_modules/@sigstore/verify": { + "version": "3.1.1", + "integrity": "sha512-qv7+G3J2cc6wwFj3yKvXOamzqhMwSk1ogPGmhpS8iXllcPrJaIIBA+4HbttlHVu1pqWTdmaCH/WE7UOC51kdoA==", "dev": true, - "license": "MIT", - "engines": { - "node": "^14.18.0 || >=16.0.0" + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.2.1", + "@sigstore/protobuf-specs": "^0.5.0" }, - "funding": { - "url": "https://opencollective.com/pkgr" + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-beta.47", - "integrity": "sha512-vPP9/MZzESh9QtmvQYojXP/midjgkkc1E4AdnPPAzQXo668ncHJcVLKjJKzoBdsQmaIvNjrMdsCwES8vTQHRQw==", - "cpu": [ - "arm64" - ], + "node_modules/@sinclair/typebox": { + "version": "0.34.52", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + "license": "MIT" }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-beta.47", - "integrity": "sha512-Lc3nrkxeaDVCVl8qR3qoxh6ltDZfkQ98j5vwIr5ALPkgjZtDK4BGCrrBoLpGVMg+csWcaqUbwbKwH5yvVa0oOw==", - "cpu": [ - "arm64" - ], + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" } }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-beta.47", - "integrity": "sha512-eBYxQDwP0O33plqNVqOtUHqRiSYVneAknviM5XMawke3mwMuVlAsohtOqEjbCEl/Loi/FWdVeks5WkqAkzkYWQ==", - "cpu": [ - "x64" - ], + "node_modules/@sinonjs/fake-timers": { + "version": "15.4.0", + "integrity": "sha512-DsG+8/LscQIQg68J6Ef3dv10u6nVyetYn923s3/sus5eaGfTo1of5WMZSLf0UJc9KDuKPilPH0UDJCjvNbDNCA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1" } }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-beta.47", - "integrity": "sha512-Ns+kgp2+1Iq/44bY/Z30DETUSiHY7ZuqaOgD5bHVW++8vme9rdiWsN4yG4rRPXkdgzjvQ9TDHmZZKfY4/G11AA==", - "cpu": [ - "x64" - ], + "node_modules/@smithy/core": { + "version": "3.36.0", + "integrity": "sha512-3hJiwG1F5t1SoXqp/yOCmYXziyx7ZVqF63S5W6YU201e/qwVaHPex+E/3vvyY9a3ORq/b+BydeNZpP2saQ9X2g==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18.0.0" } }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-beta.47", - "integrity": "sha512-4PecgWCJhTA2EFOlptYJiNyVP2MrVP4cWdndpOu3WmXqWqZUmSubhb4YUAIxAxnXATlGjC1WjxNPhV7ZllNgdA==", - "cpu": [ - "arm" - ], + "node_modules/@smithy/fetch-http-handler": { + "version": "5.8.0", + "integrity": "sha512-ycSJu3tFAQ4v04CBB0agqFMVsSQ1iG3yw+SpgxRqKfaURpQD4CZ8Wn0zPMmSnOuTpTh65Vz+EA0rMrw089wvkA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.18.0", + "tslib": "^2.6.2" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18.0.0" } }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-beta.47", - "integrity": "sha512-CyIunZ6D9U9Xg94roQI1INt/bLkOpPsZjZZkiaAZ0r6uccQdICmC99M9RUPlMLw/qg4yEWLlQhG73W/mG437NA==", - "cpu": [ - "arm64" - ], + "node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=14.0.0" } }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-beta.47", - "integrity": "sha512-doozc/Goe7qRCSnzfJbFINTHsMktqmZQmweull6hsZZ9sjNWQ6BWQnbvOlfZJe4xE5NxM1NhPnY5Giqnl3ZrYQ==", - "cpu": [ - "arm64" - ], + "node_modules/@smithy/protocol-http": { + "version": "5.6.2", + "integrity": "sha512-Asd04MaxODN6FNY8EPTeCAM4kPNi3jDUAjZU0Y4F9rHvpLUrrUo7KLcxFgSthywFr6dZfIyDLIJda6jxmVTk5w==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.2", + "tslib": "^2.6.2" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18.0.0" } }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-beta.47", - "integrity": "sha512-fodvSMf6Aqwa0wEUSTPewmmZOD44rc5Tpr5p9NkwQ6W1SSpUKzD3SwpJIgANDOhwiYhDuiIaYPGB7Ujkx1q0UQ==", - "cpu": [ - "x64" - ], + "node_modules/@smithy/querystring-builder": { + "version": "4.5.2", + "integrity": "sha512-TLuVW4cGFCj+LirJylCVcFwxcUCpZ5Av0I7K9k/xhm6v6zAUeXSNuZ9e2QKuxcwkgrejL8LZvQTJy42pmq5bOg==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.2", + "tslib": "^2.6.2" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18.0.0" } }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-beta.47", - "integrity": "sha512-Rxm5hYc0mGjwLh5sjlGmMygxAaV2gnsx7CNm2lsb47oyt5UQyPDZf3GP/ct8BEcwuikdqzsrrlIp8+kCSvMFNQ==", - "cpu": [ - "x64" - ], + "node_modules/@smithy/signature-v4": { + "version": "5.7.4", + "integrity": "sha512-tHy0K0VtqNd5Y7Y41h0a0Lhh0L1GzC08dTWg0F7vRJWFtTENg7IZikf3wQkanYIRdb7ngoIPMTmqgUi401fEeQ==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.35.0", + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18.0.0" } }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-beta.47", - "integrity": "sha512-YakuVe+Gc87jjxazBL34hbr8RJpRuFBhun7NEqoChVDlH5FLhLXjAPHqZd990TVGVNkemourf817Z8u2fONS8w==", - "cpu": [ - "arm64" - ], + "node_modules/@smithy/types": { + "version": "4.19.0", + "integrity": "sha512-r7jh49VJxGerfAcTQA6gXcKc+98zOp/tqRwzYjgOE+iSQsP6cEU1hq2QzbuipmP68QtYdY9wKEhiCQZIzHgZ4Q==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18.0.0" } }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-beta.47", - "integrity": "sha512-ak2GvTFQz3UAOw8cuQq8pWE+TNygQB6O47rMhvevvTzETh7VkHRFtRUwJynX5hwzFvQMP6G0az5JrBGuwaMwYQ==", - "cpu": [ - "wasm32" - ], + "node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", "dev": true, - "license": "MIT", - "optional": true, + "license": "Apache-2.0", "dependencies": { - "@napi-rs/wasm-runtime": "^1.0.7" + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=14.0.0" } }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-beta.47", - "integrity": "sha512-o5BpmBnXU+Cj+9+ndMcdKjhZlPb79dVPBZnWwMnI4RlNSSq5yOvFZqvfPYbyacvnW03Na4n5XXQAPhu3RydZ0w==", - "cpu": [ - "arm64" - ], + "node_modules/@smithy/util-hex-encoding": { + "version": "3.0.0", + "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=16.0.0" } }, - "node_modules/@rolldown/binding-win32-ia32-msvc": { - "version": "1.0.0-beta.47", - "integrity": "sha512-FVOmfyYehNE92IfC9Kgs913UerDog2M1m+FADJypKz0gmRg3UyTt4o1cZMCAl7MiR89JpM9jegNO1nXuP1w1vw==", - "cpu": [ - "ia32" - ], + "node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=14.0.0" } }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-beta.47", - "integrity": "sha512-by/70F13IUE101Bat0oeH8miwWX5mhMFPk1yjCdxoTNHTyTdLgb0THNaebRM6AP7Kz+O3O2qx87sruYuF5UxHg==", - "cpu": [ - "x64" - ], + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + "license": "MIT" }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.47", - "integrity": "sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw==", + "node_modules/@tsconfig/node10": { + "version": "1.0.13", + "integrity": "sha512-gcLdvR9HO1ZJBypsOGqaP6TFEzb6vIta0KSTLt9NAQ6pXQO3cRgSVyCN6pzYqI9DlJgY71XKO0dpDhCf08b3pg==", "dev": true, "license": "MIT" }, - "node_modules/@rollup/plugin-json": { - "version": "6.1.0", - "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", "dev": true, "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.1.0" - }, "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@rollup/pluginutils": { - "version": "5.4.0", - "integrity": "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==", + "node_modules/@tufjs/models": { + "version": "4.1.0", + "integrity": "sha512-Y8cK9aggNRsqJVaKUlEYs4s7CvQ1b1ta2DVPyAimb0I2qhzjNk+A+mxvll/klL0RlfuIUei8BF7YWiua4kQqww==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^4.0.2" + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^10.1.1" }, "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.64.6", - "integrity": "sha512-FkO19Fowv/OQeJZPVfdetJaI3/VpbZmBsnmAlR/rQ+b3QwfiGQiOpVegpSRH331kelvHTVw9hXYG3DwjAaRzyQ==", - "cpu": [ - "arm" - ], + "node_modules/@tybys/wasm-util": { + "version": "0.10.4", + "integrity": "sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "android" - ] + "dependencies": { + "tslib": "^2.4.0" + } }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.64.6", - "integrity": "sha512-si5/Etm6MphPc2xYHnk455Shls/hoMjTy3mhXge4OPmvCw7TwC4KP4fKokAlKLV4XaQvLvgSHfafPcblLMYYdQ==", - "cpu": [ - "arm64" - ], + "node_modules/@types/babel__core": { + "version": "7.20.5", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ] + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.64.6", - "integrity": "sha512-PQCl3q7b0BgvVt72i2xrWM6I5KliwwVe4GpWW5QDGyUQawDS9aPZss/aGyFqBmxdRxlE8wDc9CaxiQ5DHRSp/Q==", - "cpu": [ - "arm64" - ], + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] + "dependencies": { + "@babel/types": "^7.0.0" + } }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.64.6", - "integrity": "sha512-l9DFl1Itb1YYdSrP1K3U8ih44AOHa/p+Q4WMMDfGvH5qVWp+ajfgS1DlYo4yCRh8wJ2ZJv0Fqccf+ULAmMFjmg==", - "cpu": [ - "x64" - ], + "node_modules/@types/babel__template": { + "version": "7.4.4", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.64.6", - "integrity": "sha512-bR4GR3205N9+od0+l5Dd9/iZTDpJkIe6d6Ydp2qbcQZJLmKBdYQQRp8gbWpbMt92twKJY66o1vK4NkoKQODIrA==", - "cpu": [ - "arm64" - ], + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] + "dependencies": { + "@babel/types": "^7.28.2" + } }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.64.6", - "integrity": "sha512-XTVNaDzcFl2TeBglvxdGgsCv9JGtAGRAoJENmGBR/XvDH2mA2K0IXc2aMfP0dR8mexSXSKifJ85FixoXX1wfTw==", - "cpu": [ - "x64" - ], + "node_modules/@types/body-parser": { + "version": "1.19.6", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.64.6", - "integrity": "sha512-qWc6qbc+25ugG9vqPR9P422KJBbVfZ05mjcMggoq8tsfWP+eRfC+/tiDdIlRF5VL2pDVZWN9zAhWsmu7R1+7Dw==", - "cpu": [ - "arm" - ], + "node_modules/@types/bonjour": { + "version": "3.5.13", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "peer": true, + "dependencies": { + "@types/node": "*" + } }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.64.6", - "integrity": "sha512-V4Nwkp8844X/Xnww3PI1LYS0YPei8XQEVG70R+5MPZGO7AhYoun4hCN7Svs4U3wbQLZbwiz+9xRFhUZZdRl8Ww==", - "cpu": [ - "arm" - ], + "node_modules/@types/connect": { + "version": "3.4.38", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@types/node": "*" + } }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.64.6", - "integrity": "sha512-xb72A1aPaaIsp+a88VtRae/ovIEXislbUMyp7b9AQQU7ZkP2m/+ik6DW9TQcJyw5R8/RIailF9zExYwkjR++UA==", - "cpu": [ - "arm64" - ], + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.4", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "peer": true, + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.64.6", - "integrity": "sha512-pbcs1BVBwC43hZkjhjlF8Rx8m5G0KnhLZs9Azy/gqccP6UsNSzJr0hKGIAVPqzAH1O/NdPxxC9GVGb4msQg/aw==", - "cpu": [ - "arm64" - ], + "node_modules/@types/css-font-loading-module": { + "version": "0.0.7", + "integrity": "sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "license": "MIT" }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.64.6", - "integrity": "sha512-EKOC8os6Vsg487ZLKUqzLF99Jsmnq6tu7O7mAELakNpcA+8KadktFf/JYdRuHe/DzCUXmN38pUKu4XBcKlqGbQ==", - "cpu": [ - "loong64" - ], + "node_modules/@types/eslint": { + "version": "9.6.1", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "peer": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.64.6", - "integrity": "sha512-v/SQ68GnSlr/HyjB79sNXesGDwkOPu9JjbHfmWaymCiM+nZsQ9TstG1KfA4MuEvTr2d3PoY8PhFxj9bt0e32JA==", - "cpu": [ - "loong64" - ], + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "peer": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.64.6", - "integrity": "sha512-G3pmliMQ7r15zFFNwBlaT/xTdSLi4lfgI7CUrQueyKYheNVhrd8JrWCE6Ll8xf4AA1RZFqq+mNJYntiElsVOyA==", - "cpu": [ - "ppc64" - ], + "node_modules/@types/estree": { + "version": "1.0.9", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "license": "MIT" }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.64.6", - "integrity": "sha512-XQTB/IPoB/wG6x/wu9uomAt8YH61IAr5iZTubMiad/lu88sotq2ocz9hJd556+Jz2kIkF7ZQZIsUuWE3OWqtcA==", - "cpu": [ - "ppc64" - ], + "node_modules/@types/express": { + "version": "5.0.6", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" + } }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.64.6", - "integrity": "sha512-O5mpokth6+ydMuwiGtY8fIjvOBfH7u6SzmDNmZ3k6GK5C6iEAC2q2ZN/T5L9jYILx0IcCT0KnfMDihQ5yQFSBw==", - "cpu": [ - "riscv64" - ], + "node_modules/@types/express-serve-static-core": { + "version": "4.19.9", + "integrity": "sha512-QP2ESEe/ImWY0HDwNAnK9PvEffUyhLTnWkk7KXzHfyeWAnlrDe1fN77bXl6ia8KT3wPlmA7t9/VPRpnf4Ex9sg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "peer": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.64.6", - "integrity": "sha512-aIFxbAD9v3TvIaEDsKmukjcDg1JS4/tFXw8SHyQQgzOwzWCQ4cyDK7Ju1pQKqUXDFkEu/BZCFDCDATsKhQcB/Q==", - "cpu": [ - "riscv64" - ], + "node_modules/@types/express/node_modules/@types/express-serve-static-core": { + "version": "5.1.3", + "integrity": "sha512-dPfW8NFiOF4wOHc7+N/QSxlY9cfSsenewGbAz8C8U/MULPd/YZ27LvJUIlzaXie7e6Ove9YunJGgC9tbHD2cKw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.64.6", - "integrity": "sha512-fuNo1UwBc6BxnHDOxxguQL/InhheMN3RJcJMb0b4xP8r5Q3CNCrJbVg0eCIFuJ4e/zAfjR2gYbOfbzO/G7CIyw==", - "cpu": [ - "s390x" - ], + "node_modules/@types/express/node_modules/@types/serve-static": { + "version": "2.2.0", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*" + } }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.64.6", - "integrity": "sha512-05v6wFKDKJPZs9xlytVrrRu6IbRuoSvlH9xNwEOafXsKyjBFZPvAcZz2VIAB/M+lblYeKa9iOYC4RDVdfcAAaw==", - "cpu": [ - "x64" - ], + "node_modules/@types/http-errors": { + "version": "2.0.5", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "license": "MIT" }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.64.6", - "integrity": "sha512-wJXKcdMjvkc/FaavZXNP1yW4i0pfOmhZAIWRSLG2yJ0PB0TWi7dkLErhX0u0vptDcXKKO/m1Y/YTeuyvb2MXwg==", - "cpu": [ - "x64" - ], + "node_modules/@types/http-proxy": { + "version": "1.17.17", + "integrity": "sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "peer": true, + "dependencies": { + "@types/node": "*" + } }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.64.6", - "integrity": "sha512-cwg7/h0ThvJwy8CBkmACdiHAxRWpbsCmIsjOt+Zzx0hTnIcSUmKYEV03bGg+hD827OnwUsbKh1ZZDW754xWyBA==", - "cpu": [ - "x64" - ], + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] + "license": "MIT" }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.64.6", - "integrity": "sha512-T0RkYIJbdYXJEQFw+tMBO09KDFdgmNxkHsof3wWAABrvnx15Yh8RytmrgYy9+q9cBeBNZF66MMQZC+5nzEz6vg==", - "cpu": [ - "arm64" - ], + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.64.6", - "integrity": "sha512-KlfpmF1AAIdAWxt8QQNClbpLFlAUfYOzqIuJ7IXEStMHpFRuH78RG6FjqPb3Rqhh0K89F5npvG+/i3Fjl6IveQ==", - "cpu": [ - "arm64" - ], + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "dependencies": { + "@types/istanbul-lib-report": "*" + } }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.64.6", - "integrity": "sha512-MrTLjd7Jlt9/ZavJZYG61KQv9Iy9bzbY+yXy73Xvwoa2uP66wvDShkyBr5Chz/yrE6lbD/zHMuTmKQVNQqBApg==", - "cpu": [ - "ia32" - ], + "node_modules/@types/jest": { + "version": "30.0.0", + "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "dependencies": { + "expect": "^30.0.0", + "pretty-format": "^30.0.0" + } }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.64.6", - "integrity": "sha512-8fWDH1CkPI8GdBKAxzHDza7UmgficsKo2+MVbvUgOGhB0629w5sHMg3Qu5y7TZsrYKHHWpz8fla4KvoT7WsO3Q==", - "cpu": [ - "x64" - ], + "node_modules/@types/jsdom": { + "version": "21.1.7", + "integrity": "sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.64.6", - "integrity": "sha512-9/c9PWdr5GCIHkkxiHM1HjNBQMVdftP4O8QTcoUAaRza2FSqJ4axbf8QAnBuE6BIGQDl71SaRPeDdDYid5ecTw==", - "cpu": [ - "x64" - ], + "node_modules/@types/json-schema": { + "version": "7.0.15", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "peer": true }, - "node_modules/@rollup/wasm-node": { - "version": "4.64.6", - "integrity": "sha512-ufelhiGxNl8RuflC/Tie5ZVcI3MJUtd27nJ0jC2I4LroyZb7n4iwmajEmXXeTLxJtDiiWUwU8v2UjkACBHqQvw==", + "node_modules/@types/json5": { + "version": "0.0.29", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.9" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@napi-rs/lzma-linux-x64-gnu": "1.5.1", - "fsevents": "~2.3.2" - } + "license": "MIT" }, - "node_modules/@rrweb/record": { - "version": "2.0.0-alpha.20", - "integrity": "sha512-marVOU3Lc285mwLCp+vI6M/eJ+wZ6lcTa7fX0zcdmBsM+j5loXmMQjkmIvkji5+lAbkq5/w2cwto3GS0TaCjtg==", + "node_modules/@types/lodash": { + "version": "4.17.27", + "integrity": "sha512-VenMRv/eKNrspX9v3H4dbmjsrnCOYdYBG3kxXKk3dG6X+urBGcj2Prww3xw1U5K4FV/mE4h5GbWd0szEKf+9ew==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", "dev": true, "license": "MIT", "dependencies": { - "@rrweb/types": "^2.0.0-alpha.20", - "@rrweb/utils": "^2.0.0-alpha.20", - "rrweb": "^2.0.0-alpha.20" + "@types/lodash": "*" } }, - "node_modules/@rrweb/record/node_modules/rrdom": { - "version": "2.1.7", - "integrity": "sha512-jm7IN2aaG6891OqFAYw/b8dw5FLiMeCTrU4HIUr40I6tTaUX2Rrhba5vjjMRARQClBFDA04vvf9HFD9WnZFpFg==", + "node_modules/@types/mime": { + "version": "1.3.5", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", "dev": true, "license": "MIT", - "dependencies": { - "rrweb-snapshot": "^2.1.7" - } + "peer": true }, - "node_modules/@rrweb/record/node_modules/rrweb": { - "version": "2.1.7", - "integrity": "sha512-UZzK43vVmbtBt5WyEVO/CO8q9hKi38LRZDQS5C+X+FYgpVLK4vhWyUzzi1K0iXzHFb3MOVaTak5TAsdK5K6V6Q==", + "node_modules/@types/node": { + "version": "22.20.5", + "integrity": "sha512-U2+DNr+wSjpsTS/wZGYHq7GcwfuSmKiKvoPvK22zwTlRhU91yOniN4qRR5KhIjvif7ysw/dz/hKmfDH0Ris4aA==", "dev": true, "license": "MIT", "dependencies": { - "@rrweb/types": "^2.1.7", - "@rrweb/utils": "^2.1.7", - "@types/css-font-loading-module": "0.0.7", - "@xstate/fsm": "^1.4.0", - "base64-arraybuffer": "^1.0.1", - "mitt": "^3.0.0", - "rrdom": "^2.1.7", - "rrweb-snapshot": "^2.1.7" + "undici-types": "~6.21.0" } }, - "node_modules/@rrweb/types": { - "version": "2.1.7", - "integrity": "sha512-fNua4du3xvMlC5iK8lYU1CEg+9wsA7FXUzahVBBVGBKqVPSifROLVh3mfV90hhsfAcs4jAvLbl23v28hXrxZWg==", + "node_modules/@types/qs": { + "version": "6.15.1", + "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", "dev": true, "license": "MIT" }, - "node_modules/@rrweb/utils": { - "version": "2.1.7", - "integrity": "sha512-ZQpnuthKb8U2BvSDtiX6z8XkEPUUbCXhcrOY5yqqtPP2O5RP8YgPlo8HkU/tFStMd/Wn8cfy9DmkhialwUUvrA==", + "node_modules/@types/range-parser": { + "version": "1.2.7", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", "dev": true, "license": "MIT" }, - "node_modules/@rtsao/scc": { - "version": "1.1.0", - "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "node_modules/@types/retry": { + "version": "0.12.2", + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, - "node_modules/@schematics/angular": { - "version": "21.0.6", - "integrity": "sha512-9oh0RX+Cj2Pc8Aap/75qkcK2rmDZcyoTokiJG1m2b/I9MH1XmiFBdao1du2BA5GJtBRFizzXCoYf5v4LwxIP2w==", + "node_modules/@types/send": { + "version": "1.2.1", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "21.0.6", - "@angular-devkit/schematics": "21.0.6", - "jsonc-parser": "3.3.1" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" + "@types/node": "*" } }, - "node_modules/@schematics/angular/node_modules/@angular-devkit/core": { - "version": "21.0.6", - "integrity": "sha512-quhQrvfbs1zzDev2T3PdyXDsTrthi9QjwegqR4HyDZKvYCWzmXLw9KjScqEWWUSKVLhG7GULrmb+tDsxRFG9lg==", + "node_modules/@types/serve-index": { + "version": "1.9.4", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "ajv": "8.17.1", - "ajv-formats": "3.0.1", - "jsonc-parser": "3.3.1", - "picomatch": "4.0.3", - "rxjs": "7.8.2", - "source-map": "0.7.6" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^4.0.0" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } + "@types/express": "*" } }, - "node_modules/@schematics/angular/node_modules/ajv": { - "version": "8.17.1", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "node_modules/@types/serve-static": { + "version": "1.15.10", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" } }, - "node_modules/@schematics/angular/node_modules/chokidar": { - "version": "4.0.3", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", "dev": true, "license": "MIT", - "optional": true, "peer": true, "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@schematics/angular/node_modules/picomatch": { - "version": "4.0.3", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "@types/mime": "^1", + "@types/node": "*" } }, - "node_modules/@schematics/angular/node_modules/readdirp": { - "version": "4.1.2", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "node_modules/@types/sockjs": { + "version": "0.3.36", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", "dev": true, "license": "MIT", - "optional": true, "peer": true, - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" + "dependencies": { + "@types/node": "*" } }, - "node_modules/@sigstore/bundle": { - "version": "4.0.0", - "integrity": "sha512-NwCl5Y0V6Di0NexvkTqdoVfmjTaQwoLM236r89KEojGmq/jMls8S+zb7yOwAPdXvbwfKDlP+lmXgAL4vKSQT+A==", + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/protobuf-specs": "^0.5.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } + "license": "MIT" }, - "node_modules/@sigstore/core": { - "version": "3.2.1", - "integrity": "sha512-qRsxPnCrbC/puegGxKuynfnxgLiHqWStrSjxkoB4YKqq3Z3s4cyZyj42ZdWFAEblNP65C+rBH8EuREHIXoi83g==", + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } + "license": "MIT" }, - "node_modules/@sigstore/protobuf-specs": { - "version": "0.5.2", - "integrity": "sha512-SQqvFMt4V78fdjcDdYX6HbiVSOR4QK3ZgwCa2KOsopAgPIHy1rU5UDUmzLl02r5oyyaYcYHR1hpwDRk/yUe+Mw==", + "node_modules/@types/ws": { + "version": "8.18.2", + "integrity": "sha512-67MQl+fpWKVTT1NYdnmo3U4sc/xPo/zQBncVnI74qmQa0z/b+1g6iYqNmGCPbxO+zz2aklb08a0oHfegiVd0/w==", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.17.0 || >=20.5.0" + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "*" } }, - "node_modules/@sigstore/sign": { - "version": "4.1.1", - "integrity": "sha512-Hf4xglukg0XXQ2RiD5vSoLjdPe8OBUPA8XeVjUObheuDcWdYWrnH/BNmxZCzkAy68MzmNCxXLeurJvs6hcP2OQ==", + "node_modules/@types/yargs": { + "version": "17.0.35", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@gar/promise-retry": "^1.0.2", - "@sigstore/bundle": "^4.0.0", - "@sigstore/core": "^3.2.0", - "@sigstore/protobuf-specs": "^0.5.0", - "make-fetch-happen": "^15.0.4", - "proc-log": "^6.1.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" + "@types/yargs-parser": "*" } }, - "node_modules/@sigstore/sign/node_modules/proc-log": { - "version": "6.1.0", - "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "dev": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } + "license": "MIT" }, - "node_modules/@sigstore/tuf": { - "version": "4.0.2", - "integrity": "sha512-TCAzTy0xzdP79EnxSjq9KQ3eaR7+FmudLC6eRKknVKZbV7ZNlGLClAAQb/HMNJ5n2OBNk2GT1tEmU0xuPr+SLQ==", + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.71.2", + "integrity": "sha512-K72/pvT7qpm8q3k8G5u93NCSN1RcLxX5h7Gw0bQpbMXviBHeY6Nx1WEwmk9wP+eKTSQ8ecxvcgt/ZX/rB938xQ==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@sigstore/protobuf-specs": "^0.5.0", - "tuf-js": "^4.1.0" + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.71.2", + "@typescript-eslint/type-utils": "8.71.2", + "@typescript-eslint/utils": "8.71.2", + "@typescript-eslint/visitor-keys": "8.71.2", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.71.2", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@sigstore/verify": { - "version": "3.1.1", - "integrity": "sha512-qv7+G3J2cc6wwFj3yKvXOamzqhMwSk1ogPGmhpS8iXllcPrJaIIBA+4HbttlHVu1pqWTdmaCH/WE7UOC51kdoA==", + "node_modules/@typescript-eslint/parser": { + "version": "8.71.2", + "integrity": "sha512-aTtmZjp/x3gsGHkDs04/1I8kNogBJkpQaeGaN0PfiG43NzDEmW2n+OP5im9wfsvyX0yP1UZWBrAGtDBfH9jEVw==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@sigstore/bundle": "^4.0.0", - "@sigstore/core": "^3.2.1", - "@sigstore/protobuf-specs": "^0.5.0" + "@typescript-eslint/scope-manager": "8.71.2", + "@typescript-eslint/types": "8.71.2", + "@typescript-eslint/typescript-estree": "8.71.2", + "@typescript-eslint/visitor-keys": "8.71.2", + "debug": "^4.4.3" }, "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@sinclair/typebox": { - "version": "0.34.52", - "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@sinonjs/fake-timers": { - "version": "15.4.0", - "integrity": "sha512-DsG+8/LscQIQg68J6Ef3dv10u6nVyetYn923s3/sus5eaGfTo1of5WMZSLf0UJc9KDuKPilPH0UDJCjvNbDNCA==", + "node_modules/@typescript-eslint/project-service": { + "version": "8.71.2", + "integrity": "sha512-Pd68GsLVh5ZXlcEccFPdhSOvtHZ/2WU0XsA8zmXOS3F158lVlcTLhfQ6qvtL1RzbMie0Kp8xtbVwAg4nejKPHw==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "dependencies": { - "@sinonjs/commons": "^3.0.1" + "@typescript-eslint/tsconfig-utils": "^8.71.2", + "@typescript-eslint/types": "^8.71.2", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@smithy/core": { - "version": "3.36.0", - "integrity": "sha512-3hJiwG1F5t1SoXqp/yOCmYXziyx7ZVqF63S5W6YU201e/qwVaHPex+E/3vvyY9a3ORq/b+BydeNZpP2saQ9X2g==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.71.2", + "integrity": "sha512-YF4wgg5iUX5Obywoac27B56FhozMNFhdJSbh11H5GBAwmBtfyRhmgTQqW9tRNNmHDvsZ2CX6UGzoz+YfYn2Leg==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@smithy/types": "^4.19.0", - "tslib": "^2.6.2" + "@typescript-eslint/types": "8.71.2", + "@typescript-eslint/visitor-keys": "8.71.2" }, "engines": { - "node": ">=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@smithy/fetch-http-handler": { - "version": "5.8.0", - "integrity": "sha512-ycSJu3tFAQ4v04CBB0agqFMVsSQ1iG3yw+SpgxRqKfaURpQD4CZ8Wn0zPMmSnOuTpTh65Vz+EA0rMrw089wvkA==", + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.71.2", + "integrity": "sha512-jz49TAWokm/7yWD9/o837nYax9DyALxIPOImdImvVzo6canBCZYzin8zJyqNR5k1j9K1DE0bVvr0XYB+99uAdg==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.33.3", - "@smithy/types": "^4.18.0", - "tslib": "^2.6.2" - }, + "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@smithy/is-array-buffer": { - "version": "2.2.0", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "node_modules/@typescript-eslint/type-utils": { + "version": "8.71.2", + "integrity": "sha512-cC8UaalCViJkb3rH/xLIvidEwoFRtMZMUqDjjJMGEKaTFwYRHYBcoczlEGHuvkHEO8svRDrgZ0yQgOArFmwang==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "@typescript-eslint/types": "8.71.2", + "@typescript-eslint/typescript-estree": "8.71.2", + "@typescript-eslint/utils": "8.71.2", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/protocol-http": { - "version": "5.6.2", - "integrity": "sha512-Asd04MaxODN6FNY8EPTeCAM4kPNi3jDUAjZU0Y4F9rHvpLUrrUo7KLcxFgSthywFr6dZfIyDLIJda6jxmVTk5w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.33.2", - "tslib": "^2.6.2" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/querystring-builder": { - "version": "4.5.2", - "integrity": "sha512-TLuVW4cGFCj+LirJylCVcFwxcUCpZ5Av0I7K9k/xhm6v6zAUeXSNuZ9e2QKuxcwkgrejL8LZvQTJy42pmq5bOg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.33.2", - "tslib": "^2.6.2" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@smithy/signature-v4": { - "version": "5.7.4", - "integrity": "sha512-tHy0K0VtqNd5Y7Y41h0a0Lhh0L1GzC08dTWg0F7vRJWFtTENg7IZikf3wQkanYIRdb7ngoIPMTmqgUi401fEeQ==", + "node_modules/@typescript-eslint/types": { + "version": "8.71.2", + "integrity": "sha512-y5Ukl3oVp7e/VGxqIoEXoeb/Roj71PAJ4WG/N/RGj+eeDvKRLM15kGr9MijLbN0i03S52zxu31Z98E5gWChJKw==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.35.0", - "@smithy/types": "^4.19.0", - "tslib": "^2.6.2" - }, + "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@smithy/types": { - "version": "4.19.0", - "integrity": "sha512-r7jh49VJxGerfAcTQA6gXcKc+98zOp/tqRwzYjgOE+iSQsP6cEU1hq2QzbuipmP68QtYdY9wKEhiCQZIzHgZ4Q==", + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.71.2", + "integrity": "sha512-DgNOEsl4pdYzwSL+N/Cl33tX1Hlw3FvId73LnKXfq7jnrF2viOEn7X+P7RBkkSWCVm92gnWd28GIC5vJuLREeA==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "@typescript-eslint/project-service": "8.71.2", + "@typescript-eslint/tsconfig-utils": "8.71.2", + "@typescript-eslint/types": "8.71.2", + "@typescript-eslint/visitor-keys": "8.71.2", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@smithy/util-buffer-from": { - "version": "2.2.0", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "node_modules/@typescript-eslint/utils": { + "version": "8.71.2", + "integrity": "sha512-RyKa1G1M9rKHDhrk+HJtiF1MvLD5Ux2JW3TSkzw+02pRALdpJb+QzG1Y5em+LvCgzGwgg8H7mBRFKljNz7ImPw==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@smithy/is-array-buffer": "^2.2.0", - "tslib": "^2.6.2" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.71.2", + "@typescript-eslint/types": "8.71.2", + "@typescript-eslint/typescript-estree": "8.71.2" }, "engines": { - "node": ">=14.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@smithy/util-hex-encoding": { - "version": "3.0.0", - "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.71.2", + "integrity": "sha512-zozlN0UA32bSG2zkKAgQyRYSbEISVoyU6vvkKYo2aSny74D8Jh4e+POkJ6Lk0dlezZi5x7TtD+88Xb06IB8nhg==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "@typescript-eslint/types": "8.71.2", + "eslint-visitor-keys": "^5.0.0" }, "engines": { - "node": ">=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" - }, "engines": { - "node": ">=14.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@tsconfig/node10": { - "version": "1.0.13", - "integrity": "sha512-gcLdvR9HO1ZJBypsOGqaP6TFEzb6vIta0KSTLt9NAQ6pXQO3cRgSVyCN6pzYqI9DlJgY71XKO0dpDhCf08b3pg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "node_modules/@ungap/structured-clone": { + "version": "1.4.0", + "integrity": "sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==", "dev": true, - "license": "MIT" + "license": "ISC" }, - "node_modules/@tufjs/canonical-json": { - "version": "2.0.0", - "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.12.2", + "integrity": "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "engines": { - "node": "^16.14.0 || >=18.0.0" - } + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@tufjs/models": { - "version": "4.1.0", - "integrity": "sha512-Y8cK9aggNRsqJVaKUlEYs4s7CvQ1b1ta2DVPyAimb0I2qhzjNk+A+mxvll/klL0RlfuIUei8BF7YWiua4kQqww==", + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.12.2", + "integrity": "sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@tufjs/canonical-json": "2.0.0", - "minimatch": "^10.1.1" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.4", - "integrity": "sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==", + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.12.2", + "integrity": "sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } + "os": [ + "darwin" + ] }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.12.2", + "integrity": "sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.12.2", + "integrity": "sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } + "optional": true, + "os": [ + "freebsd" + ] }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.12.2", + "integrity": "sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - } - }, - "node_modules/@types/body-parser": { - "version": "1.19.6", - "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/bonjour": { - "version": "3.5.13", - "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/node": "*" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@types/connect": { - "version": "3.4.38", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.12.2", + "integrity": "sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@types/node": "*" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@types/connect-history-api-fallback": { - "version": "1.5.4", - "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.12.2", + "integrity": "sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@types/express-serve-static-core": "*", - "@types/node": "*" - } - }, - "node_modules/@types/css-font-loading-module": { - "version": "0.0.7", - "integrity": "sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q==", - "dev": true, - "license": "MIT" + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@types/eslint": { - "version": "9.6.1", - "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.12.2", + "integrity": "sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "node_modules/@unrs/resolver-binding-linux-loong64-gnu": { + "version": "1.12.2", + "integrity": "sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==", + "cpu": [ + "loong64" + ], "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.9", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", - "dev": true, - "license": "MIT" + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@types/express": { - "version": "4.17.25", - "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", + "node_modules/@unrs/resolver-binding-linux-loong64-musl": { + "version": "1.12.2", + "integrity": "sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==", + "cpu": [ + "loong64" + ], "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "^1" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@types/express-serve-static-core": { - "version": "4.19.9", - "integrity": "sha512-QP2ESEe/ImWY0HDwNAnK9PvEffUyhLTnWkk7KXzHfyeWAnlrDe1fN77bXl6ia8KT3wPlmA7t9/VPRpnf4Ex9sg==", + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.12.2", + "integrity": "sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@types/http-errors": { - "version": "2.0.5", - "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.12.2", + "integrity": "sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==", + "cpu": [ + "riscv64" + ], "dev": true, "license": "MIT", - "peer": true + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@types/http-proxy": { - "version": "1.17.17", - "integrity": "sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==", + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.12.2", + "integrity": "sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==", + "cpu": [ + "riscv64" + ], "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true, - "license": "MIT" + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.12.2", + "integrity": "sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==", + "cpu": [ + "s390x" + ], "dev": true, "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.12.2", + "integrity": "sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@types/jest": { - "version": "30.0.0", - "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.12.2", + "integrity": "sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "expect": "^30.0.0", - "pretty-format": "^30.0.0" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@types/jsdom": { - "version": "21.1.7", - "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", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@types/json5": { - "version": "0.0.29", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/mime": { - "version": "1.3.5", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@types/node": { - "version": "22.20.5", - "integrity": "sha512-U2+DNr+wSjpsTS/wZGYHq7GcwfuSmKiKvoPvK22zwTlRhU91yOniN4qRR5KhIjvif7ysw/dz/hKmfDH0Ris4aA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@types/qs": { - "version": "6.15.1", - "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@types/retry": { - "version": "0.12.2", - "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@types/send": { - "version": "1.2.1", - "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/serve-index": { - "version": "1.9.4", - "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/express": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.15.10", - "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/http-errors": "*", - "@types/node": "*", - "@types/send": "<1" - } - }, - "node_modules/@types/serve-static/node_modules/@types/send": { - "version": "0.17.6", - "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "node_modules/@types/sockjs": { - "version": "0.3.36", - "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/tough-cookie": { - "version": "4.0.5", - "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/ws": { - "version": "8.18.2", - "integrity": "sha512-67MQl+fpWKVTT1NYdnmo3U4sc/xPo/zQBncVnI74qmQa0z/b+1g6iYqNmGCPbxO+zz2aklb08a0oHfegiVd0/w==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/yargs": { - "version": "17.0.35", - "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.71.2", - "integrity": "sha512-K72/pvT7qpm8q3k8G5u93NCSN1RcLxX5h7Gw0bQpbMXviBHeY6Nx1WEwmk9wP+eKTSQ8ecxvcgt/ZX/rB938xQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.71.2", - "@typescript-eslint/type-utils": "8.71.2", - "@typescript-eslint/utils": "8.71.2", - "@typescript-eslint/visitor-keys": "8.71.2", - "ignore": "^7.0.5", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.71.2", - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.71.2", - "integrity": "sha512-aTtmZjp/x3gsGHkDs04/1I8kNogBJkpQaeGaN0PfiG43NzDEmW2n+OP5im9wfsvyX0yP1UZWBrAGtDBfH9jEVw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/scope-manager": "8.71.2", - "@typescript-eslint/types": "8.71.2", - "@typescript-eslint/typescript-estree": "8.71.2", - "@typescript-eslint/visitor-keys": "8.71.2", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.71.2", - "integrity": "sha512-Pd68GsLVh5ZXlcEccFPdhSOvtHZ/2WU0XsA8zmXOS3F158lVlcTLhfQ6qvtL1RzbMie0Kp8xtbVwAg4nejKPHw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.71.2", - "@typescript-eslint/types": "^8.71.2", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.71.2", - "integrity": "sha512-YF4wgg5iUX5Obywoac27B56FhozMNFhdJSbh11H5GBAwmBtfyRhmgTQqW9tRNNmHDvsZ2CX6UGzoz+YfYn2Leg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.71.2", - "@typescript-eslint/visitor-keys": "8.71.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.71.2", - "integrity": "sha512-jz49TAWokm/7yWD9/o837nYax9DyALxIPOImdImvVzo6canBCZYzin8zJyqNR5k1j9K1DE0bVvr0XYB+99uAdg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.71.2", - "integrity": "sha512-cC8UaalCViJkb3rH/xLIvidEwoFRtMZMUqDjjJMGEKaTFwYRHYBcoczlEGHuvkHEO8svRDrgZ0yQgOArFmwang==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.71.2", - "@typescript-eslint/typescript-estree": "8.71.2", - "@typescript-eslint/utils": "8.71.2", - "debug": "^4.4.3", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.71.2", - "integrity": "sha512-y5Ukl3oVp7e/VGxqIoEXoeb/Roj71PAJ4WG/N/RGj+eeDvKRLM15kGr9MijLbN0i03S52zxu31Z98E5gWChJKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.71.2", - "integrity": "sha512-DgNOEsl4pdYzwSL+N/Cl33tX1Hlw3FvId73LnKXfq7jnrF2viOEn7X+P7RBkkSWCVm92gnWd28GIC5vJuLREeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.71.2", - "@typescript-eslint/tsconfig-utils": "8.71.2", - "@typescript-eslint/types": "8.71.2", - "@typescript-eslint/visitor-keys": "8.71.2", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.71.2", - "integrity": "sha512-RyKa1G1M9rKHDhrk+HJtiF1MvLD5Ux2JW3TSkzw+02pRALdpJb+QzG1Y5em+LvCgzGwgg8H7mBRFKljNz7ImPw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.71.2", - "@typescript-eslint/types": "8.71.2", - "@typescript-eslint/typescript-estree": "8.71.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.71.2", - "integrity": "sha512-zozlN0UA32bSG2zkKAgQyRYSbEISVoyU6vvkKYo2aSny74D8Jh4e+POkJ6Lk0dlezZi5x7TtD+88Xb06IB8nhg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.71.2", - "eslint-visitor-keys": "^5.0.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@ungap/structured-clone": { - "version": "1.4.0", - "integrity": "sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/@unrs/resolver-binding-android-arm-eabi": { - "version": "1.12.2", - "integrity": "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@unrs/resolver-binding-android-arm64": { - "version": "1.12.2", - "integrity": "sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.12.2", - "integrity": "sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.12.2", - "integrity": "sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.12.2", - "integrity": "sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.12.2", - "integrity": "sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.12.2", - "integrity": "sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.12.2", - "integrity": "sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.12.2", - "integrity": "sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-loong64-gnu": { - "version": "1.12.2", - "integrity": "sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-loong64-musl": { - "version": "1.12.2", - "integrity": "sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.12.2", - "integrity": "sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.12.2", - "integrity": "sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.12.2", - "integrity": "sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.12.2", - "integrity": "sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.12.2", - "integrity": "sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.12.2", - "integrity": "sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-openharmony-arm64": { - "version": "1.12.2", - "integrity": "sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.12.2", - "integrity": "sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/core": { - "version": "1.10.0", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/runtime": { - "version": "1.10.0", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.12.2", - "integrity": "sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.12.2", - "integrity": "sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.12.2", - "integrity": "sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@vitejs/plugin-basic-ssl": { - "version": "2.1.0", - "integrity": "sha512-dOxxrhgyDIEUADhb/8OlV9JIqYLgos03YorAueTIeOUskLJSEsfwCByjbu98ctXitUN3znXKp0bYD/WHSudCeA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "peerDependencies": { - "vite": "^6.0.0 || ^7.0.0" - } - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.14.1", - "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@webassemblyjs/helper-numbers": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.13.2", - "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.13.2", - "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.14.1", - "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.13.2", - "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.13.2", - "@webassemblyjs/helper-api-error": "1.13.2", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.13.2", - "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.14.1", - "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/wasm-gen": "1.14.1" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.13.2", - "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.13.2", - "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.13.2", - "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.14.1", - "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/helper-wasm-section": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-opt": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1", - "@webassemblyjs/wast-printer": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.14.1", - "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.14.1", - "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.14.1", - "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-api-error": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.14.1", - "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@xstate/fsm": { - "version": "1.6.5", - "integrity": "sha512-b5o1I6aLNeYlU/3CPlj/Z91ybk1gUsKT+5NAJI+2W4UjvS5KLG28K9v5UvNoFVjHV8PajVZ00RH3vnjyQO7ZAw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true - }, - "node_modules/@yarnpkg/lockfile": { - "version": "1.1.0", - "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/abbrev": { - "version": "4.0.0", - "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/accepts": { - "version": "2.0.0", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.19.0", - "integrity": "sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-import-phases": { - "version": "1.0.4", - "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10.13.0" - }, - "peerDependencies": { - "acorn": "^8.14.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.5", - "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/adjust-sourcemap-loader": { - "version": "4.0.0", - "integrity": "sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "loader-utils": "^2.0.0", - "regex-parser": "^2.2.11" - }, - "engines": { - "node": ">=8.9" - } - }, - "node_modules/adjust-sourcemap-loader/node_modules/loader-utils": { - "version": "2.0.4", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, - "node_modules/agent-base": { - "version": "7.1.4", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/ajv": { - "version": "8.18.0", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ajv-keywords": { - "version": "5.1.0", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/algoliasearch": { - "version": "5.40.1", - "integrity": "sha512-iUNxcXUNg9085TJx0HJLjqtDE0r1RZ0GOGrt8KNQqQT5ugu8lZsHuMUYW/e0lHhq6xBvmktU9Bw4CXP9VQeKrg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/abtesting": "1.6.1", - "@algolia/client-abtesting": "5.40.1", - "@algolia/client-analytics": "5.40.1", - "@algolia/client-common": "5.40.1", - "@algolia/client-insights": "5.40.1", - "@algolia/client-personalization": "5.40.1", - "@algolia/client-query-suggestions": "5.40.1", - "@algolia/client-search": "5.40.1", - "@algolia/ingestion": "1.40.1", - "@algolia/monitoring": "1.40.1", - "@algolia/recommend": "5.40.1", - "@algolia/requester-browser-xhr": "5.40.1", - "@algolia/requester-fetch": "5.40.1", - "@algolia/requester-node-http": "5.40.1" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-html-community": { - "version": "0.0.8", - "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", - "dev": true, - "engines": [ - "node >= 0.8.0" - ], - "license": "Apache-2.0", - "peer": true, - "bin": { - "ansi-html": "bin/ansi-html" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.2", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "license": "MIT" - }, - "node_modules/argparse": { - "version": "2.0.1", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.2", - "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "is-array-buffer": "^3.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/array-includes": { - "version": "3.2.0", - "integrity": "sha512-VXY5eFRarnXcYxwBjJzPmEhH55+rmP79/+ueDhi0F+TuqfHCItagIHqxeUZrmgrOPa31QTh9H85DjX3FfJ0FTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.9", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.24.2", - "es-object-atoms": "^1.1.2", - "es-shim-unscopables": "^1.1.0", - "is-string": "^1.1.1", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.findlastindex": { - "version": "1.2.6", - "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "es-shim-unscopables": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "node_modules/@unrs/resolver-binding-openharmony-arm64": { + "version": "1.12.2", + "integrity": "sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] }, - "node_modules/array.prototype.flat": { - "version": "1.3.3", - "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.12.2", + "integrity": "sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==", + "cpu": [ + "wasm32" + ], "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-shim-unscopables": "^1.0.2" + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=14.0.0" } }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.3", - "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.10.0", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" } }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.4", - "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.10.0", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "is-array-buffer": "^3.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "tslib": "^2.4.0" } }, - "node_modules/asn1js": { - "version": "3.0.10", - "integrity": "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==", + "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", "dev": true, - "license": "BSD-3-Clause", - "peer": true, + "license": "MIT", + "optional": true, "dependencies": { - "pvtsutils": "^1.3.6", - "pvutils": "^1.1.5", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=12.0.0" + "tslib": "^2.4.0" } }, - "node_modules/async-function": { - "version": "1.0.0", - "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.12.2", + "integrity": "sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.4" - } + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/autoprefixer": { - "version": "10.4.27", - "integrity": "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==", + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.12.2", + "integrity": "sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==", + "cpu": [ + "ia32" + ], "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.12.2", + "integrity": "sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==", + "cpu": [ + "x64" ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@vitejs/plugin-basic-ssl": { + "version": "2.1.4", + "integrity": "sha512-HXciTXN/sDBYWgeAD4V4s0DN0g72x5mlxQhHxtYu3Tt8BLa6MzcJZUyDVFCdtjNs3bfENVHVzOsmooTVuNgAAw==", + "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "browserslist": "^4.28.1", - "caniuse-lite": "^1.0.30001774", - "fraction.js": "^5.3.4", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, "engines": { - "node": "^10 || ^12 || >=14" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "peerDependencies": { - "postcss": "^8.1.0" + "vite": "^6.0.0 || ^7.0.0" } }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, - "node_modules/aws-rum-web": { - "version": "3.2.1", - "integrity": "sha512-DBMYmZPBfCtK1ikXjGP5OE9yqiOymtHibn6BO3IFzK91td2tflYaq3ecjv+FDmai6uPXjjF7xjmdhK7gvVoa9A==", + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-rum/web-core": "3.2.1", - "@aws-rum/web-slim": "3.2.1" - } + "license": "MIT", + "peer": true }, - "node_modules/babel-jest": { - "version": "30.5.2", - "integrity": "sha512-ES8WeJ2fNWPEDunyAtUfJ12nHyaWUloCHdkfK2oW5uSoQSUmjNKdWiGaXJITJwvFEWoD/evHEg/QoCXAvLaQqQ==", + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/transform": "30.5.2", - "@types/babel__core": "^7.20.5", - "babel-plugin-istanbul": "^8.0.0", - "babel-preset-jest": "30.5.0", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "slash": "^3.0.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.11.0 || ^8.0.0-0" - } + "peer": true }, - "node_modules/babel-loader": { - "version": "10.0.0", - "integrity": "sha512-z8jt+EdS61AMw22nSfoNJAZ0vrtmhPRVi6ghL3rCeRZI8cdNYFiV5xeV3HbE7rlZZNmGH8BVccwWt8/ED0QOHA==", + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "find-up": "^5.0.0" - }, - "engines": { - "node": "^18.20.0 || ^20.10.0 || >=22.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0", - "webpack": ">=5.61.0" + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" } }, - "node_modules/babel-plugin-istanbul": { - "version": "8.0.0", - "integrity": "sha512-18wCskrN3DgbuBmp1gr7LBGT8xdz5xhQQqFvFhVxbkl8VBCrMKQ2YtqBWtUal1Zrc1HTuX0011+Brjw78TCFkg==", + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", "dev": true, - "license": "BSD-3-Clause", - "workspaces": [ - "test/babel-8" - ], - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-instrument": "^6.0.2", - "test-exclude": "^7.0.1" - }, - "engines": { - "node": ">=18" - } + "license": "MIT", + "peer": true }, - "node_modules/babel-plugin-jest-hoist": { - "version": "30.5.0", - "integrity": "sha512-gtGo1B+u14jrZQv6TdSWIWkTqclboo7Qn+dFAGUOIuXLFuJKAdg3U5MIWzZnW4vfUb4dX9skmAMiby86e/SF4A==", + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@types/babel__core": "^7.20.5" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" } }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.17", - "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==", + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-define-polyfill-provider": "^0.6.8", - "semver": "^6.3.1" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + "@xtuc/ieee754": "^1.2.0" } }, - "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { - "version": "6.3.1", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "dev": true, - "license": "ISC", + "license": "Apache-2.0", "peer": true, - "bin": { - "semver": "bin/semver.js" + "dependencies": { + "@xtuc/long": "4.2.2" } }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.13.0", - "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "dev": true, "license": "MIT", "peer": true, "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" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" } }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.8", - "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==", + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.8" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.2.0", - "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-import-attributes": "^7.24.7", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5" - }, - "peerDependencies": { - "@babel/core": "^7.0.0 || ^8.0.0-0" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" } }, - "node_modules/babel-preset-jest": { - "version": "30.5.0", - "integrity": "sha512-ZGPn5ClP4lBDpuOK8W1yQIOy359HmbnZv3suucAlIe+SEE9yDsxV4S2PjSbH2Vc97U+WmzYD7vw3kr8NsQ/i6w==", + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "babel-plugin-jest-hoist": "30.5.0", - "babel-preset-current-node-syntax": "^1.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.11.0 || ^8.0.0-beta.1 || ^8.0.0" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, - "node_modules/balanced-match": { - "version": "4.0.4", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "dev": true, "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xstate/fsm": { + "version": "1.6.5", + "integrity": "sha512-b5o1I6aLNeYlU/3CPlj/Z91ybk1gUsKT+5NAJI+2W4UjvS5KLG28K9v5UvNoFVjHV8PajVZ00RH3vnjyQO7ZAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true + }, + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/abbrev": { + "version": "4.0.0", + "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", + "dev": true, + "license": "ISC", "engines": { - "node": "18 || 20 || >=22" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/base64-arraybuffer": { - "version": "1.0.2", - "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "node_modules/accepts": { + "version": "2.0.0", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "dev": true, "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, "engines": { - "node": ">= 0.6.0" + "node": ">= 0.6" } }, - "node_modules/baseline-browser-mapping": { - "version": "2.11.28", - "integrity": "sha512-br0LOmjJqr6Rt4AEkadbcNf67Zpo7EPwKsWbjnRrJIa1QYFHQBqM4C7F2WTbV8ahTEcg1Eo+Y0aWjjORInYFyA==", + "node_modules/acorn": { + "version": "8.19.0", + "integrity": "sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "bin": { - "baseline-browser-mapping": "dist/cli.cjs" + "acorn": "bin/acorn" }, "engines": { - "node": ">=6.0.0" + "node": ">=0.4.0" } }, - "node_modules/batch": { - "version": "0.6.1", - "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", "dev": true, "license": "MIT", - "peer": true - }, - "node_modules/beasties": { - "version": "0.3.5", - "integrity": "sha512-NaWu+f4YrJxEttJSm16AzMIFtVldCvaJ68b1L098KpqXmxt9xOLtKoLkKxb8ekhOrLqEJAbvT6n6SEvB/sac7A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "css-select": "^6.0.0", - "css-what": "^7.0.0", - "dom-serializer": "^2.0.0", - "domhandler": "^5.0.3", - "htmlparser2": "^10.0.0", - "picocolors": "^1.1.1", - "postcss": "^8.4.49", - "postcss-media-query-parser": "^0.2.3" - }, + "peer": true, "engines": { - "node": ">=14.0.0" + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" } }, - "node_modules/bidi-js": { - "version": "1.1.0", - "integrity": "sha512-fX1Onk0tdVPC7obPWB5EbJ1z7NVhLq4m2xZLq2YXBkxzMXIGRpNMU88n0EPgWseKl12J7zXs7qrDxPK4sRs2fg==", + "node_modules/acorn-jsx": { + "version": "5.3.2", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "require-from-string": "^2.0.2" + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/big.js": { - "version": "5.2.2", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "node_modules/acorn-walk": { + "version": "8.3.5", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", "dev": true, "license": "MIT", - "peer": true, + "dependencies": { + "acorn": "^8.11.0" + }, "engines": { - "node": "*" + "node": ">=0.4.0" } }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "node_modules/adjust-sourcemap-loader": { + "version": "4.0.0", + "integrity": "sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==", "dev": true, "license": "MIT", "peer": true, - "engines": { - "node": ">=8" + "dependencies": { + "loader-utils": "^2.0.0", + "regex-parser": "^2.2.11" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=8.9" } }, - "node_modules/body-parser": { - "version": "2.3.0", - "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "node_modules/adjust-sourcemap-loader/node_modules/loader-utils": { + "version": "2.0.4", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "bytes": "^3.1.2", - "content-type": "^2.0.0", - "debug": "^4.4.3", - "http-errors": "^2.0.1", - "iconv-lite": "^0.7.2", - "on-finished": "^2.4.1", - "qs": "^6.15.2", - "raw-body": "^3.0.2", - "type-is": "^2.1.0" + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" }, "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">=8.9.0" } }, - "node_modules/body-parser/node_modules/content-type": { - "version": "2.1.0", - "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "node_modules/agent-base": { + "version": "7.1.4", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">= 14" } }, - "node_modules/bonjour-service": { - "version": "1.4.4", - "integrity": "sha512-jCZcVv7eoc4QesRscwEZtSROBen+6LpKAmBIsQYQrsAeVHLyMXWX/t6eIV5KiRZYNUBl8eVqImEEMQ8L5+c/Kw==", + "node_modules/ajv": { + "version": "8.18.0", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", - "multicast-dns": "^7.2.5" + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/boolbase": { - "version": "1.0.0", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true, - "license": "ISC" - }, - "node_modules/brace-expansion": { - "version": "5.0.12", - "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "node_modules/ajv-formats": { + "version": "3.0.1", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^4.0.2" + "ajv": "^8.0.0" }, - "engines": { - "node": "20 || >=22" + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } } }, - "node_modules/braces": { - "version": "3.0.3", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "node_modules/ajv-keywords": { + "version": "5.1.0", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "fill-range": "^7.1.1" + "fast-deep-equal": "^3.1.3" }, - "engines": { - "node": ">=8" + "peerDependencies": { + "ajv": "^8.8.2" } }, - "node_modules/browserslist": { - "version": "4.29.3", - "integrity": "sha512-1R4kiYKXGViqEN0CnoDrXc1StD9niAwu+j2dukWzrD4bJgsD4lDmEp0CRbc6E/vYJIfTHwPmwyaKtVSudICdPA==", + "node_modules/algoliasearch": { + "version": "5.48.1", + "integrity": "sha512-Rf7xmeuIo7nb6S4mp4abW2faW8DauZyE2faBIKFaUfP3wnpOvNSbiI5AwVhqBNj0jPgBWEvhyCu0sLjN2q77Rg==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.11.26", - "caniuse-lite": "^1.0.30001813", - "electron-to-chromium": "^1.5.439", - "node-releases": "^2.0.57", - "update-browserslist-db": "^1.3.3" - }, - "bin": { - "browserslist": "cli.js" + "@algolia/abtesting": "1.14.1", + "@algolia/client-abtesting": "5.48.1", + "@algolia/client-analytics": "5.48.1", + "@algolia/client-common": "5.48.1", + "@algolia/client-insights": "5.48.1", + "@algolia/client-personalization": "5.48.1", + "@algolia/client-query-suggestions": "5.48.1", + "@algolia/client-search": "5.48.1", + "@algolia/ingestion": "1.48.1", + "@algolia/monitoring": "1.48.1", + "@algolia/recommend": "5.48.1", + "@algolia/requester-browser-xhr": "5.48.1", + "@algolia/requester-fetch": "5.48.1", + "@algolia/requester-node-http": "5.48.1" }, "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + "node": ">= 14.0.0" } }, - "node_modules/bs-logger": { - "version": "0.2.6", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "node_modules/ansi-colors": { + "version": "4.1.3", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", "dev": true, "license": "MIT", - "dependencies": { - "fast-json-stable-stringify": "2.x" - }, "engines": { - "node": ">= 6" + "node": ">=6" } }, - "node_modules/bser": { - "version": "2.1.2", - "integrity": "sha512-V2o8YS2EhrKMO9AumnkK0ynynfFlsajpYupwq7S46H5mhcanwxDkNTw3K67wPIcKTH1ffkA4vhMOtAc7gRmPZw==", + "node_modules/ansi-escapes": { + "version": "4.3.2", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "dev": true, "license": "MIT", "dependencies": { - "node-int64": "^0.4.0" + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/buffer-from": { - "version": "1.1.2", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/builtin-modules": { - "version": "3.3.0", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true, - "license": "MIT", - "peer": true, + "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=6" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/builtins": { - "version": "5.1.0", - "integrity": "sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg==", + "node_modules/ansi-html-community": { + "version": "0.0.8", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", "dev": true, - "license": "MIT", + "engines": [ + "node >= 0.8.0" + ], + "license": "Apache-2.0", "peer": true, - "dependencies": { - "semver": "^7.0.0" + "bin": { + "ansi-html": "bin/ansi-html" } }, - "node_modules/bundle-name": { - "version": "4.1.1", - "integrity": "sha512-DdH81/zPLVS11EUgWq3tEu/xn+EzljlMYooDNdzWEnFha3R3NBMpMV1UqYIpjYHV/SgpFKMIX1Oh7o06SjM/oA==", + "node_modules/ansi-regex": { + "version": "5.0.1", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "run-applescript": "^7.0.0" - }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, - "node_modules/bytes": { - "version": "3.1.2", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "node_modules/ansi-styles": { + "version": "4.3.0", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": ">= 0.8" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/bytestreamjs": { - "version": "2.0.1", - "integrity": "sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==", + "node_modules/anymatch": { + "version": "3.1.3", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, - "license": "BSD-3-Clause", - "peer": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, "engines": { - "node": ">=6.0.0" + "node": ">= 8" } }, - "node_modules/cacache": { - "version": "20.0.4", - "integrity": "sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA==", + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.2", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, - "license": "ISC", - "dependencies": { - "@npmcli/fs": "^5.0.0", - "fs-minipass": "^3.0.0", - "glob": "^13.0.0", - "lru-cache": "^11.1.0", - "minipass": "^7.0.3", - "minipass-collect": "^2.0.1", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "p-map": "^7.0.2", - "ssri": "^13.0.0" - }, + "license": "MIT", "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/cacache/node_modules/lru-cache": { - "version": "11.5.3", - "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", + "node_modules/arg": { + "version": "4.1.3", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } + "license": "Python-2.0" }, - "node_modules/cacache/node_modules/ssri": { - "version": "13.0.1", - "integrity": "sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ==", + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "minipass": "^7.0.3" + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" }, "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/call-bind": { - "version": "1.0.9", - "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "node_modules/array-flatten": { + "version": "1.1.1", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/array-includes": { + "version": "3.2.0", + "integrity": "sha512-VXY5eFRarnXcYxwBjJzPmEhH55+rmP79/+ueDhi0F+TuqfHCItagIHqxeUZrmgrOPa31QTh9H85DjX3FfJ0FTg==", "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "get-intrinsic": "^1.3.0", - "set-function-length": "^1.2.2" + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-object-atoms": "^1.1.2", + "es-shim-unscopables": "^1.1.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -11397,27 +9459,37 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", "dev": true, "license": "MIT", "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", "es-errors": "^1.3.0", - "function-bind": "^1.1.2" + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/call-bound": { - "version": "1.0.4", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -11426,437 +9498,420 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/callsites": { - "version": "3.1.0", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001816", - "integrity": "sha512-+8Yx7BXCoqk6IF8yTicjfUR6D/ldJzdlsodlEOiOSPmhK844WIgk67/zIQaGAOxNkZyyvBlrRFnLvqKIoqbAFw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "4.1.2", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/chardet": { - "version": "2.2.0", - "integrity": "sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==", - "dev": true, - "license": "MIT" - }, - "node_modules/chokidar": { - "version": "5.0.0", - "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", "dev": true, "license": "MIT", - "optional": true, - "peer": true, "dependencies": { - "readdirp": "^5.0.0" + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" }, "engines": { - "node": ">= 20.19.0" + "node": ">= 0.4" }, "funding": { - "url": "https://paulmillr.com/funding/" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/chownr": { - "version": "3.0.0", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "node_modules/asn1js": { + "version": "3.0.10", + "integrity": "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.5", + "tslib": "^2.8.1" + }, "engines": { - "node": ">=18" + "node": ">=12.0.0" } }, - "node_modules/chrome-trace-event": { - "version": "1.0.4", - "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "node_modules/async-function": { + "version": "1.0.0", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", "dev": true, "license": "MIT", - "peer": true, "engines": { - "node": ">=6.0" + "node": ">= 0.4" } }, - "node_modules/ci-info": { - "version": "4.4.0", - "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "node_modules/autoprefixer": { + "version": "10.4.27", + "integrity": "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==", "dev": true, "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, { "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" + "url": "https://github.com/sponsors/ai" } ], "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cjs-module-lexer": { - "version": "2.3.0", - "integrity": "sha512-lmNyBzi6iYiqyrExTctfmjd8tInx+dHu9PBomTfH5RXS53c27JMFJgzNv5TyAnm3PgGf+MLy0+Ws41/HlVxdKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/cli-cursor": { - "version": "5.0.0", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", - "dev": true, - "license": "MIT", + "peer": true, "dependencies": { - "restore-cursor": "^5.0.0" + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001774", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" }, - "engines": { - "node": ">=18" + "bin": { + "autoprefixer": "bin/autoprefixer" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-spinners": { - "version": "3.4.0", - "integrity": "sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==", - "dev": true, - "license": "MIT", "engines": { - "node": ">=18.20" + "node": "^10 || ^12 || >=14" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "postcss": "^8.1.0" } }, - "node_modules/cli-truncate": { - "version": "5.2.0", - "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "dev": true, "license": "MIT", "dependencies": { - "slice-ansi": "^8.0.0", - "string-width": "^8.2.0" + "possible-typed-array-names": "^1.0.0" }, "engines": { - "node": ">=20" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/cli-width": { - "version": "4.1.0", - "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "node_modules/aws-rum-web": { + "version": "3.2.1", + "integrity": "sha512-DBMYmZPBfCtK1ikXjGP5OE9yqiOymtHibn6BO3IFzK91td2tflYaq3ecjv+FDmai6uPXjjF7xjmdhK7gvVoa9A==", "dev": true, - "license": "ISC", - "engines": { - "node": ">= 12" + "license": "Apache-2.0", + "dependencies": { + "@aws-rum/web-core": "3.2.1", + "@aws-rum/web-slim": "3.2.1" } }, - "node_modules/cliui": { - "version": "9.0.1", - "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "node_modules/axe-core": { + "version": "4.14.0", + "integrity": "sha512-9WTZxEjsZ7b13TH8JPmbV2z8CHbl80/2hm3XPEG4JgNdQLK81IBRXmSxHfMAOkSqQeRxT/0dwNDz2GOm3zzpcQ==", "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^7.2.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - }, + "license": "MPL-2.0", "engines": { - "node": ">=20" + "node": ">=4" } }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "6.4.0", - "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", + "node_modules/babel-jest": { + "version": "30.5.2", + "integrity": "sha512-ES8WeJ2fNWPEDunyAtUfJ12nHyaWUloCHdkfK2oW5uSoQSUmjNKdWiGaXJITJwvFEWoD/evHEg/QoCXAvLaQqQ==", "dev": true, "license": "MIT", + "dependencies": { + "@jest/transform": "30.5.2", + "@types/babel__core": "^7.20.5", + "babel-plugin-istanbul": "^8.0.0", + "babel-preset-jest": "30.5.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "slash": "^3.0.0" + }, "engines": { - "node": ">=12" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "peerDependencies": { + "@babel/core": "^7.11.0 || ^8.0.0-0" } }, - "node_modules/cliui/node_modules/ansi-styles": { - "version": "6.2.3", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "node_modules/babel-loader": { + "version": "10.0.0", + "integrity": "sha512-z8jt+EdS61AMw22nSfoNJAZ0vrtmhPRVi6ghL3rCeRZI8cdNYFiV5xeV3HbE7rlZZNmGH8BVccwWt8/ED0QOHA==", "dev": true, "license": "MIT", + "peer": true, + "dependencies": { + "find-up": "^5.0.0" + }, "engines": { - "node": ">=12" + "node": "^18.20.0 || ^20.10.0 || >=22.0.0" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "peerDependencies": { + "@babel/core": "^7.12.0", + "webpack": ">=5.61.0" } }, - "node_modules/cliui/node_modules/string-width": { - "version": "7.2.0", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "node_modules/babel-plugin-istanbul": { + "version": "8.0.0", + "integrity": "sha512-18wCskrN3DgbuBmp1gr7LBGT8xdz5xhQQqFvFhVxbkl8VBCrMKQ2YtqBWtUal1Zrc1HTuX0011+Brjw78TCFkg==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", + "workspaces": [ + "test/babel-8" + ], "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-instrument": "^6.0.2", + "test-exclude": "^7.0.1" }, "engines": { "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "7.2.0", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "node_modules/babel-plugin-jest-hoist": { + "version": "30.5.0", + "integrity": "sha512-gtGo1B+u14jrZQv6TdSWIWkTqclboo7Qn+dFAGUOIuXLFuJKAdg3U5MIWzZnW4vfUb4dX9skmAMiby86e/SF4A==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.2.2" + "@types/babel__core": "^7.20.5" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "9.0.2", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.17", + "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" + "@babel/compat-data": "^7.28.6", + "@babel/helper-define-polyfill-provider": "^0.6.8", + "semver": "^6.3.1" }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/clone-deep": { - "version": "4.0.1", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.13.0", + "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" + "@babel/helper-define-polyfill-provider": "^0.6.5", + "core-js-compat": "^3.43.0" }, - "engines": { - "node": ">=6" + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/clone-deep/node_modules/is-plain-object": { - "version": "2.0.4", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.8", + "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "isobject": "^3.0.1" + "@babel/helper-define-polyfill-provider": "^0.6.8" }, - "engines": { - "node": ">=0.10.0" + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/co": { - "version": "4.6.0", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "node_modules/babel-preset-current-node-syntax": { + "version": "1.2.0", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", "dev": true, "license": "MIT", - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0 || ^8.0.0-0" } }, - "node_modules/collect-v8-coverage": { - "version": "1.0.3", - "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", - "dev": true, - "license": "MIT" - }, - "node_modules/color-convert": { - "version": "2.0.1", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/babel-preset-jest": { + "version": "30.5.0", + "integrity": "sha512-ZGPn5ClP4lBDpuOK8W1yQIOy359HmbnZv3suucAlIe+SEE9yDsxV4S2PjSbH2Vc97U+WmzYD7vw3kr8NsQ/i6w==", "dev": true, "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "babel-plugin-jest-hoist": "30.5.0", + "babel-preset-current-node-syntax": "^1.2.0" }, "engines": { - "node": ">=7.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0 || ^8.0.0-beta.1 || ^8.0.0" } }, - "node_modules/color-name": { - "version": "1.1.4", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/colorette": { - "version": "2.0.20", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/commander": { - "version": "14.0.3", - "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "node_modules/balanced-match": { + "version": "4.0.4", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, "license": "MIT", "engines": { - "node": ">=20" + "node": "18 || 20 || >=22" } }, - "node_modules/common-path-prefix": { - "version": "3.0.0", - "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", "dev": true, - "license": "ISC" + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } }, - "node_modules/compressible": { - "version": "2.0.18", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "node_modules/baseline-browser-mapping": { + "version": "2.11.28", + "integrity": "sha512-br0LOmjJqr6Rt4AEkadbcNf67Zpo7EPwKsWbjnRrJIa1QYFHQBqM4C7F2WTbV8ahTEcg1Eo+Y0aWjjORInYFyA==", "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "mime-db": ">= 1.43.0 < 2" + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" }, "engines": { - "node": ">= 0.6" + "node": ">=6.0.0" } }, - "node_modules/compression": { - "version": "1.8.2", - "integrity": "sha512-o8vI5RE5A6EVVOd9o41jKp41aJom+QTEO/Bx8MYNjexMo/Bv2WOjUfZr+aL0WnYSgymUy6zeguqLTsIhV0gMvQ==", + "node_modules/batch": { + "version": "0.6.1", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", "dev": true, "license": "MIT", - "peer": true, + "peer": true + }, + "node_modules/beasties": { + "version": "0.4.1", + "integrity": "sha512-2Imdcw3LznDuxAbJM26RHniOLAzE6WgrK8OuvVXCQtNBS8rsnD9zsSEa3fHl4hHpUY7BYTlrpvtPVbvu9G6neg==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "bytes": "3.1.2", - "compressible": "~2.0.18", - "debug": "2.6.9", - "destroy": "1.2.0", - "negotiator": "~0.6.4", - "on-headers": "~1.1.0", - "safe-buffer": "5.2.1", - "vary": "~1.1.2" + "css-select": "^6.0.0", + "css-what": "^7.0.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "htmlparser2": "^10.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.49", + "postcss-media-query-parser": "^0.2.3", + "postcss-safe-parser": "^7.0.1" }, "engines": { - "node": ">= 0.8.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">=18.0.0" } }, - "node_modules/compression/node_modules/debug": { - "version": "2.6.9", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/bidi-js": { + "version": "1.1.0", + "integrity": "sha512-fX1Onk0tdVPC7obPWB5EbJ1z7NVhLq4m2xZLq2YXBkxzMXIGRpNMU88n0EPgWseKl12J7zXs7qrDxPK4sRs2fg==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "ms": "2.0.0" + "require-from-string": "^2.0.2" } }, - "node_modules/compression/node_modules/ms": { - "version": "2.0.0", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "node_modules/big.js": { + "version": "5.2.2", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", "dev": true, "license": "MIT", - "peer": true + "peer": true, + "engines": { + "node": "*" + } }, - "node_modules/compression/node_modules/negotiator": { - "version": "0.6.4", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "node_modules/binary-extensions": { + "version": "2.3.0", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "dev": true, "license": "MIT", "peer": true, "engines": { - "node": ">= 0.6" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/connect-history-api-fallback": { - "version": "2.0.0", - "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "node_modules/body-parser": { + "version": "2.3.0", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", "dev": true, "license": "MIT", - "peer": true, + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, "engines": { - "node": ">=0.8" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/content-disposition": { - "version": "2.0.1", - "integrity": "sha512-e+H0ZXHSWYrENhQzw1LPuP4oF5MzVKmDU6d3hxlvaPEYLLg62MxtQNPRx4SYSuYJSBUgnQIG4HIN2tEtNv7Dog==", + "node_modules/body-parser/node_modules/content-type": { + "version": "2.1.0", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", "dev": true, "license": "MIT", "engines": { @@ -11867,1280 +9922,1154 @@ "url": "https://opencollective.com/express" } }, - "node_modules/content-type": { - "version": "1.0.5", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "node_modules/bonjour-service": { + "version": "1.4.4", + "integrity": "sha512-jCZcVv7eoc4QesRscwEZtSROBen+6LpKAmBIsQYQrsAeVHLyMXWX/t6eIV5KiRZYNUBl8eVqImEEMQ8L5+c/Kw==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.6" + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" } }, - "node_modules/convert-source-map": { - "version": "1.9.0", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "node_modules/boolbase": { + "version": "1.0.0", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", "dev": true, - "license": "MIT" + "license": "ISC" }, - "node_modules/cookie": { - "version": "0.7.2", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "node_modules/brace-expansion": { + "version": "5.0.12", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "dev": true, "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, "engines": { - "node": ">= 0.6" + "node": "20 || >=22" } }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "node_modules/braces": { + "version": "3.0.3", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "license": "MIT", + "peer": true, + "dependencies": { + "fill-range": "^7.1.1" + }, "engines": { - "node": ">=6.6.0" + "node": ">=8" } }, - "node_modules/copy-anything": { - "version": "3.0.5", - "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", + "node_modules/browserslist": { + "version": "4.29.3", + "integrity": "sha512-1R4kiYKXGViqEN0CnoDrXc1StD9niAwu+j2dukWzrD4bJgsD4lDmEp0CRbc6E/vYJIfTHwPmwyaKtVSudICdPA==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", "dependencies": { - "is-what": "^4.1.8" + "baseline-browser-mapping": "^2.11.26", + "caniuse-lite": "^1.0.30001813", + "electron-to-chromium": "^1.5.439", + "node-releases": "^2.0.57", + "update-browserslist-db": "^1.3.3" }, - "engines": { - "node": ">=12.13" + "bin": { + "browserslist": "cli.js" }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/copy-webpack-plugin": { - "version": "14.0.0", - "integrity": "sha512-3JLW90aBGeaTLpM7mYQKpnVdgsUZRExY55giiZgLuX/xTQRUs1dOCwbBnWnvY6Q6rfZoXMNwzOQJCSZPppfqXA==", + "node_modules/bs-logger": { + "version": "0.2.6", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "glob-parent": "^6.0.1", - "normalize-path": "^3.0.0", - "schema-utils": "^4.2.0", - "serialize-javascript": "^7.0.3", - "tinyglobby": "^0.2.12" + "fast-json-stable-stringify": "2.x" }, "engines": { - "node": ">= 20.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" + "node": ">= 6" } }, - "node_modules/core-js-compat": { - "version": "3.50.0", - "integrity": "sha512-XGpFGbMLHwSt74YLTKho7Ib242qi6O8MSX+sRokV4oz7iKXvQWGYZthjIhjRGMxjzVkAubBO512dKGYcefmX3Q==", + "node_modules/bser": { + "version": "2.1.2", + "integrity": "sha512-V2o8YS2EhrKMO9AumnkK0ynynfFlsajpYupwq7S46H5mhcanwxDkNTw3K67wPIcKTH1ffkA4vhMOtAc7gRmPZw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "browserslist": "^4.28.7" - }, - "engines": { - "node": ">=6.4.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" + "node-int64": "^0.4.0" } }, - "node_modules/core-util-is": { - "version": "1.0.3", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "node_modules/buffer-from": { + "version": "1.1.2", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, - "node_modules/cors": { - "version": "2.8.6", - "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "node_modules/builtin-modules": { + "version": "3.3.0", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", "dev": true, "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, + "peer": true, "engines": { - "node": ">= 0.10" + "node": ">=6" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cosmiconfig": { - "version": "9.0.2", - "integrity": "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==", + "node_modules/builtins": { + "version": "5.1.0", + "integrity": "sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "semver": "^7.0.0" + } + }, + "node_modules/bundle-name": { + "version": "4.1.1", + "integrity": "sha512-DdH81/zPLVS11EUgWq3tEu/xn+EzljlMYooDNdzWEnFha3R3NBMpMV1UqYIpjYHV/SgpFKMIX1Oh7o06SjM/oA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "env-paths": "^2.2.1", - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0" + "run-applescript": "^7.0.0" }, "engines": { - "node": ">=14" + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/create-require": { - "version": "1.1.1", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "node_modules/bytes": { + "version": "3.1.2", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "dev": true, "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, "engines": { - "node": ">= 8" + "node": ">= 0.8" } }, - "node_modules/css-loader": { - "version": "7.1.3", - "integrity": "sha512-frbERmjT0UC5lMheWpJmMilnt9GEhbZJN/heUb7/zaJYeIzj5St9HvDcfshzzOqbsS+rYpMk++2SD3vGETDSyA==", + "node_modules/bytestreamjs": { + "version": "2.0.1", + "integrity": "sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "peer": true, - "dependencies": { - "icss-utils": "^5.1.0", - "postcss": "^8.4.40", - "postcss-modules-extract-imports": "^3.1.0", - "postcss-modules-local-by-default": "^4.0.5", - "postcss-modules-scope": "^3.2.0", - "postcss-modules-values": "^4.0.0", - "postcss-value-parser": "^4.2.0", - "semver": "^7.6.3" - }, "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "@rspack/core": "0.x || 1.x", - "webpack": "^5.27.0" - }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true - }, - "webpack": { - "optional": true - } + "node": ">=6.0.0" } }, - "node_modules/css-select": { - "version": "6.0.0", - "integrity": "sha512-rZZVSLle8v0+EY8QAkDWrKhpgt6SA5OtHsgBnsj6ZaLb5dmDVOWUDtQitd9ydxxvEjhewNudS6eTVU7uOyzvXw==", + "node_modules/cacache": { + "version": "20.0.4", + "integrity": "sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA==", "dev": true, - "license": "BSD-2-Clause", + "license": "ISC", "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^7.0.0", - "domhandler": "^5.0.3", - "domutils": "^3.2.2", - "nth-check": "^2.1.1" + "@npmcli/fs": "^5.0.0", + "fs-minipass": "^3.0.0", + "glob": "^13.0.0", + "lru-cache": "^11.1.0", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^13.0.0" }, - "funding": { - "url": "https://github.com/sponsors/fb55" + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/css-tree": { - "version": "3.2.1", - "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "node_modules/cacache/node_modules/lru-cache": { + "version": "11.5.3", + "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "mdn-data": "2.27.1", - "source-map-js": "^1.2.1" - }, + "license": "BlueOak-1.0.0", "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + "node": "20 || >=22" } }, - "node_modules/css-what": { - "version": "7.0.0", - "integrity": "sha512-wD5oz5xibMOPHzy13CyGmogB3phdvcDaB5t0W/Nr5Z2O/agcB8YwOz6e2Lsp10pNDzBoDO9nVa3RGs/2BttpHQ==", + "node_modules/call-bind": { + "version": "1.0.9", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, "engines": { - "node": ">= 6" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/fb55" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/cssesc": { - "version": "3.0.0", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "dev": true, "license": "MIT", - "peer": true, - "bin": { - "cssesc": "bin/cssesc" + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" }, "engines": { - "node": ">=4" + "node": ">= 0.4" } }, - "node_modules/cssstyle": { - "version": "4.6.0", - "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "node_modules/call-bound": { + "version": "1.0.4", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "dev": true, "license": "MIT", "dependencies": { - "@asamuzakjp/css-color": "^3.2.0", - "rrweb-cssom": "^0.8.0" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { - "node": ">=18" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/cssstyle/node_modules/@asamuzakjp/css-color": { - "version": "3.2.0", - "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "node_modules/callsites": { + "version": "3.1.0", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "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" + "engines": { + "node": ">=6" } }, - "node_modules/cssstyle/node_modules/@csstools/color-helpers": { - "version": "5.1.0", - "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "node_modules/camelcase": { + "version": "5.3.1", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", + "license": "MIT", "engines": { - "node": ">=18" + "node": ">=6" } }, - "node_modules/cssstyle/node_modules/@csstools/css-calc": { - "version": "2.1.4", - "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "node_modules/caniuse-lite": { + "version": "1.0.30001816", + "integrity": "sha512-+8Yx7BXCoqk6IF8yTicjfUR6D/ldJzdlsodlEOiOSPmhK844WIgk67/zIQaGAOxNkZyyvBlrRFnLvqKIoqbAFw==", "dev": true, "funding": [ { - "type": "github", - "url": "https://github.com/sponsors/csstools" + "type": "opencollective", + "url": "https://opencollective.com/browserslist" }, { - "type": "opencollective", - "url": "https://opencollective.com/csstools" + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { - "node": ">=18" + "node": ">=10" }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/cssstyle/node_modules/@csstools/css-color-parser": { - "version": "3.1.0", - "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "node_modules/char-regex": { + "version": "1.0.2", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/chardet": { + "version": "2.2.0", + "integrity": "sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==", + "dev": true, + "license": "MIT" + }, + "node_modules/chokidar": { + "version": "5.0.0", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", "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" + "readdirp": "^5.0.0" }, "engines": { - "node": ">=18" + "node": ">= 20.19.0" }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/cssstyle/node_modules/@csstools/css-parser-algorithms": { - "version": "3.0.5", - "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "node_modules/chownr": { + "version": "3.0.0", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", + "license": "BlueOak-1.0.0", "engines": { "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-tokenizer": "^3.0.4" } }, - "node_modules/cssstyle/node_modules/@csstools/css-tokenizer": { - "version": "3.0.4", - "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/ci-info": { + "version": "4.4.0", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", "dev": true, "funding": [ { "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" + "url": "https://github.com/sponsors/sibiraj-s" } ], "license": "MIT", "engines": { - "node": ">=18" + "node": ">=8" } }, - "node_modules/cssstyle/node_modules/lru-cache": { - "version": "10.4.3", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/data-urls": { - "version": "8.0.0", - "integrity": "sha512-JQUCCK+/CAGbGGx618PlFZMXH81Jtj7t73CHv9dd/zVWvxgGT2zLFRjnKb+Ng46GIwcy+f7f88qd4duqllK+WQ==", + "node_modules/cjs-module-lexer": { + "version": "2.3.0", + "integrity": "sha512-lmNyBzi6iYiqyrExTctfmjd8tInx+dHu9PBomTfH5RXS53c27JMFJgzNv5TyAnm3PgGf+MLy0+Ws41/HlVxdKQ==", "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "whatwg-mimetype": "^5.0.0", - "whatwg-url": "^17.1.2" - }, - "engines": { - "node": "^22.14.0 || >=24.0.0" - } + "license": "MIT" }, - "node_modules/data-view-buffer": { - "version": "1.0.2", - "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "node_modules/cli-cursor": { + "version": "5.0.0", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" + "restore-cursor": "^5.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/data-view-byte-length": { - "version": "1.0.2", - "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "node_modules/cli-spinners": { + "version": "3.4.0", + "integrity": "sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==", "dev": true, "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, "engines": { - "node": ">= 0.4" + "node": ">=18.20" }, "funding": { - "url": "https://github.com/sponsors/inspect-js" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/data-view-byte-offset": { - "version": "1.0.1", - "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "node_modules/cli-truncate": { + "version": "5.2.0", + "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" + "slice-ansi": "^8.0.0", + "string-width": "^8.2.0" }, "engines": { - "node": ">= 0.4" + "node": ">=20" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/debug": { - "version": "4.4.3", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/cli-width": { + "version": "4.1.0", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, + "license": "ISC", "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">= 12" } }, - "node_modules/decimal.js": { - "version": "10.6.0", - "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", - "dev": true, - "license": "MIT" - }, - "node_modules/dedent": { - "version": "1.7.2", - "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", + "node_modules/cliui": { + "version": "9.0.1", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", "dev": true, - "license": "MIT", - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" + "license": "ISC", + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, - "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=20" } }, - "node_modules/default-browser": { - "version": "5.5.1", - "integrity": "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==", + "node_modules/cliui/node_modules/ansi-regex": { + "version": "6.4.0", + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "bundle-name": "^4.1.0", - "default-browser-id": "^5.0.0" - }, "engines": { - "node": ">=18" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/default-browser-id": { - "version": "5.0.1", - "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "node_modules/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", - "peer": true, "engines": { - "node": ">=18" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/define-data-property": { - "version": "1.1.4", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "node_modules/cliui/node_modules/string-width": { + "version": "7.2.0", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, "license": "MIT", "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">= 0.4" + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/define-lazy-prop": { - "version": "3.0.0", - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "node_modules/cliui/node_modules/strip-ansi": { + "version": "7.2.0", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "license": "MIT", - "peer": true, + "dependencies": { + "ansi-regex": "^6.2.2" + }, "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/define-properties": { - "version": "1.2.1", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "9.0.2", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "dev": true, "license": "MIT", "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">= 0.4" + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/depd": { - "version": "2.0.0", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "node_modules/clone-deep": { + "version": "4.0.1", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", "dev": true, "license": "MIT", + "peer": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, "engines": { - "node": ">= 0.8" + "node": ">=6" } }, - "node_modules/dependency-graph": { - "version": "1.0.0", - "integrity": "sha512-cW3gggJ28HZ/LExwxP2B++aiKxhJXMSIt9K48FOXQkm+vuG5gyatXnLsONRJdzO/7VfjDIiaOOa/bs4l464Lwg==", + "node_modules/clone-deep/node_modules/is-plain-object": { + "version": "2.0.4", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, "license": "MIT", + "peer": true, + "dependencies": { + "isobject": "^3.0.1" + }, "engines": { - "node": ">=4" + "node": ">=0.10.0" } }, - "node_modules/destroy": { - "version": "1.2.0", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "node_modules/co": { + "version": "4.6.0", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" } }, - "node_modules/detect-libc": { - "version": "2.1.2", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "node_modules/collect-v8-coverage": { + "version": "1.0.3", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } + "license": "MIT" }, - "node_modules/detect-newline": { - "version": "3.1.0", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "node_modules/color-convert": { + "version": "2.0.1", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, "engines": { - "node": ">=8" + "node": ">=7.0.0" } }, - "node_modules/detect-node": { - "version": "2.1.0", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "node_modules/color-name": { + "version": "1.1.4", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, - "node_modules/diff": { - "version": "4.0.4", - "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "node_modules/colorette": { + "version": "2.0.20", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } + "license": "MIT" }, - "node_modules/dns-packet": { - "version": "5.6.1", - "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "node_modules/commander": { + "version": "14.0.3", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@leichtgewicht/ip-codec": "^2.0.1" - }, "engines": { - "node": ">=6" + "node": ">=20" } }, - "node_modules/doctrine": { + "node_modules/common-path-prefix": { "version": "3.0.0", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } + "license": "ISC" }, - "node_modules/dom-serializer": { - "version": "2.0.0", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "node_modules/compressible": { + "version": "2.0.18", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" + "mime-db": ">= 1.43.0 < 2" }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + "engines": { + "node": ">= 0.6" } }, - "node_modules/domelementtype": { - "version": "2.3.0", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "BSD-2-Clause" - }, - "node_modules/domhandler": { - "version": "5.0.3", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "node_modules/compression": { + "version": "1.8.2", + "integrity": "sha512-o8vI5RE5A6EVVOd9o41jKp41aJom+QTEO/Bx8MYNjexMo/Bv2WOjUfZr+aL0WnYSgymUy6zeguqLTsIhV0gMvQ==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", + "peer": true, "dependencies": { - "domelementtype": "^2.3.0" + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "destroy": "1.2.0", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" }, "engines": { - "node": ">= 4" + "node": ">= 0.8.0" }, "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/domutils": { - "version": "3.2.2", - "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", + "peer": true, "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" + "ms": "2.0.0" } }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true, "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } + "peer": true }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "node_modules/compression/node_modules/negotiator": { + "version": "0.6.4", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } }, - "node_modules/ee-first": { - "version": "1.1.1", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "node_modules/concat-map": { + "version": "0.0.1", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true, "license": "MIT" }, - "node_modules/electron-to-chromium": { - "version": "1.5.456", - "integrity": "sha512-8hboqI6xesozteFb8aa7OGknvxUtTeRJLz0FoePysUEh/oWbddykIGdgpEJRbPRg/nIMfBf2DFos+H2MtvJl2A==", + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", "dev": true, - "license": "ISC" + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.8" + } }, - "node_modules/emittery": { - "version": "0.13.1", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "node_modules/content-disposition": { + "version": "2.0.1", + "integrity": "sha512-e+H0ZXHSWYrENhQzw1LPuP4oF5MzVKmDU6d3hxlvaPEYLLg62MxtQNPRx4SYSuYJSBUgnQIG4HIN2tEtNv7Dog==", "dev": true, "license": "MIT", "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/emoji-regex": { - "version": "10.6.0", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "dev": true, - "license": "MIT" - }, - "node_modules/emojis-list": { - "version": "3.0.0", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "node_modules/content-type": { + "version": "1.0.5", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "dev": true, "license": "MIT", - "peer": true, "engines": { - "node": ">= 4" + "node": ">= 0.6" } }, - "node_modules/encodeurl": { - "version": "2.0.0", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "node_modules/convert-source-map": { + "version": "1.9.0", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } + "license": "MIT" }, - "node_modules/enhanced-resolve": { - "version": "5.26.0", - "integrity": "sha512-9vhedylFonb2YGogzUKX6+Ja72gOJbN1QHAqdrvqLwhdl/QWbKopzoUC9EbQNVsAns/bx/4uyqalrQAoy1IByw==", + "node_modules/cookie": { + "version": "0.7.2", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.3.3" - }, "engines": { - "node": ">=10.13.0" + "node": ">= 0.6" } }, - "node_modules/entities": { - "version": "4.5.0", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "node_modules/cookie-signature": { + "version": "1.2.2", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "node": ">=6.6.0" } }, - "node_modules/env-paths": { - "version": "2.2.1", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "node_modules/copy-anything": { + "version": "3.0.5", + "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", "dev": true, "license": "MIT", + "dependencies": { + "is-what": "^4.1.8" + }, "engines": { - "node": ">=6" + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" } }, - "node_modules/environment": { - "version": "1.1.0", - "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "node_modules/copy-webpack-plugin": { + "version": "14.0.0", + "integrity": "sha512-3JLW90aBGeaTLpM7mYQKpnVdgsUZRExY55giiZgLuX/xTQRUs1dOCwbBnWnvY6Q6rfZoXMNwzOQJCSZPppfqXA==", "dev": true, "license": "MIT", + "peer": true, + "dependencies": { + "glob-parent": "^6.0.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.2.0", + "serialize-javascript": "^7.0.3", + "tinyglobby": "^0.2.12" + }, "engines": { - "node": ">=18" + "node": ">= 20.9.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" } }, - "node_modules/err-code": { - "version": "2.0.3", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "dev": true, - "license": "MIT" - }, - "node_modules/errno": { - "version": "0.1.8", - "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "node_modules/core-js-compat": { + "version": "3.50.0", + "integrity": "sha512-XGpFGbMLHwSt74YLTKho7Ib242qi6O8MSX+sRokV4oz7iKXvQWGYZthjIhjRGMxjzVkAubBO512dKGYcefmX3Q==", "dev": true, "license": "MIT", - "optional": true, + "peer": true, "dependencies": { - "prr": "~1.0.1" + "browserslist": "^4.28.7" }, - "bin": { - "errno": "cli.js" + "engines": { + "node": ">=6.4.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" } }, - "node_modules/error-ex": { - "version": "1.3.4", - "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "node_modules/core-util-is": { + "version": "1.0.3", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true, "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } + "peer": true }, - "node_modules/es-abstract": { - "version": "1.24.2", - "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", + "node_modules/cors": { + "version": "2.8.6", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", "dev": true, "license": "MIT", "dependencies": { - "array-buffer-byte-length": "^1.0.2", - "arraybuffer.prototype.slice": "^1.0.4", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "data-view-buffer": "^1.0.2", - "data-view-byte-length": "^1.0.2", - "data-view-byte-offset": "^1.0.1", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "es-set-tostringtag": "^2.1.0", - "es-to-primitive": "^1.3.0", - "function.prototype.name": "^1.1.8", - "get-intrinsic": "^1.3.0", - "get-proto": "^1.0.1", - "get-symbol-description": "^1.1.0", - "globalthis": "^1.0.4", - "gopd": "^1.2.0", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "internal-slot": "^1.1.0", - "is-array-buffer": "^3.0.5", - "is-callable": "^1.2.7", - "is-data-view": "^1.0.2", - "is-negative-zero": "^2.0.3", - "is-regex": "^1.2.1", - "is-set": "^2.0.3", - "is-shared-array-buffer": "^1.0.4", - "is-string": "^1.1.1", - "is-typed-array": "^1.1.15", - "is-weakref": "^1.1.1", - "math-intrinsics": "^1.1.0", - "object-inspect": "^1.13.4", - "object-keys": "^1.1.1", - "object.assign": "^4.1.7", - "own-keys": "^1.0.1", - "regexp.prototype.flags": "^1.5.4", - "safe-array-concat": "^1.1.3", - "safe-push-apply": "^1.0.0", - "safe-regex-test": "^1.1.0", - "set-proto": "^1.0.0", - "stop-iteration-iterator": "^1.1.0", - "string.prototype.trim": "^1.2.10", - "string.prototype.trimend": "^1.0.9", - "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.3", - "typed-array-byte-length": "^1.0.3", - "typed-array-byte-offset": "^1.0.4", - "typed-array-length": "^1.0.7", - "unbox-primitive": "^1.1.0", - "which-typed-array": "^1.1.19" + "object-assign": "^4", + "vary": "^1" }, "engines": { - "node": ">= 0.4" + "node": ">= 0.10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/es-abstract-get": { - "version": "1.0.0", - "integrity": "sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==", + "node_modules/cosmiconfig": { + "version": "9.0.2", + "integrity": "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.2", - "is-callable": "^1.2.7", - "object-inspect": "^1.13.4" + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" }, "engines": { - "node": ">= 0.4" + "node": ">=14" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/es-define-property": { - "version": "1.0.1", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "node_modules/cps-ui-kit": { + "version": "21.33.0", + "integrity": "sha512-KOmlCVhdnVBA947WUGu/LKnw25mHzsmGrjYJgu9l1ZxYLCIF52Wp1GTqeJXFQaDT+OVXYqPpW/n+Nh3+9tF1uA==", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" + "license": "Apache License 2.0", + "dependencies": { + "tslib": "^2.8.1" + }, + "peerDependencies": { + "@angular/common": "^21.2.6", + "@angular/core": "^21.2.6", + "@angular/forms": "^21.2.6", + "@e965/xlsx": "^0.20.3", + "@primeuix/styled": "^0.7.4", + "@primeuix/utils": "^0.7.1", + "@types/lodash-es": "^4.17.12", + "lodash-es": "^4.17.21", + "primeng": "^21.1.3", + "rxjs": "^7.8.2", + "zone.js": "^0.15.1 || ^0.16.0" } }, - "node_modules/es-errors": { - "version": "1.3.0", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "node_modules/create-require": { + "version": "1.1.1", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, "engines": { - "node": ">= 0.4" + "node": ">= 8" } }, - "node_modules/es-module-lexer": { - "version": "2.3.2", - "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", - "dev": true, - "license": "MIT" - }, - "node_modules/es-object-atoms": { - "version": "1.1.2", - "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "node_modules/css-loader": { + "version": "7.1.3", + "integrity": "sha512-frbERmjT0UC5lMheWpJmMilnt9GEhbZJN/heUb7/zaJYeIzj5St9HvDcfshzzOqbsS+rYpMk++2SD3vGETDSyA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "es-errors": "^1.3.0" + "icss-utils": "^5.1.0", + "postcss": "^8.4.40", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.6.3" }, "engines": { - "node": ">= 0.4" + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.27.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } } }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "node_modules/css-select": { + "version": "6.0.0", + "integrity": "sha512-rZZVSLle8v0+EY8QAkDWrKhpgt6SA5OtHsgBnsj6ZaLb5dmDVOWUDtQitd9ydxxvEjhewNudS6eTVU7uOyzvXw==", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" + "boolbase": "^1.0.0", + "css-what": "^7.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "nth-check": "^2.1.1" }, - "engines": { - "node": ">= 0.4" + "funding": { + "url": "https://github.com/sponsors/fb55" } }, - "node_modules/es-shim-unscopables": { - "version": "1.1.0", - "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "node_modules/css-tree": { + "version": "3.2.1", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "hasown": "^2.0.2" + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" }, "engines": { - "node": ">= 0.4" + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" } }, - "node_modules/es-to-primitive": { - "version": "1.3.4", - "integrity": "sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==", + "node_modules/css-what": { + "version": "7.0.0", + "integrity": "sha512-wD5oz5xibMOPHzy13CyGmogB3phdvcDaB5t0W/Nr5Z2O/agcB8YwOz6e2Lsp10pNDzBoDO9nVa3RGs/2BttpHQ==", "dev": true, - "license": "MIT", - "dependencies": { - "es-abstract-get": "^1.0.0", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "is-callable": "^1.2.7", - "is-date-object": "^1.1.0", - "is-symbol": "^1.1.1" - }, + "license": "BSD-2-Clause", "engines": { - "node": ">= 0.4" + "node": ">= 6" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/fb55" } }, - "node_modules/esbuild": { - "version": "0.28.1", - "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "node_modules/cssesc": { + "version": "3.0.0", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true, - "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { - "esbuild": "bin/esbuild" + "cssesc": "bin/cssesc" }, "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.1", - "@esbuild/android-arm": "0.28.1", - "@esbuild/android-arm64": "0.28.1", - "@esbuild/android-x64": "0.28.1", - "@esbuild/darwin-arm64": "0.28.1", - "@esbuild/darwin-x64": "0.28.1", - "@esbuild/freebsd-arm64": "0.28.1", - "@esbuild/freebsd-x64": "0.28.1", - "@esbuild/linux-arm": "0.28.1", - "@esbuild/linux-arm64": "0.28.1", - "@esbuild/linux-ia32": "0.28.1", - "@esbuild/linux-loong64": "0.28.1", - "@esbuild/linux-mips64el": "0.28.1", - "@esbuild/linux-ppc64": "0.28.1", - "@esbuild/linux-riscv64": "0.28.1", - "@esbuild/linux-s390x": "0.28.1", - "@esbuild/linux-x64": "0.28.1", - "@esbuild/netbsd-arm64": "0.28.1", - "@esbuild/netbsd-x64": "0.28.1", - "@esbuild/openbsd-arm64": "0.28.1", - "@esbuild/openbsd-x64": "0.28.1", - "@esbuild/openharmony-arm64": "0.28.1", - "@esbuild/sunos-x64": "0.28.1", - "@esbuild/win32-arm64": "0.28.1", - "@esbuild/win32-ia32": "0.28.1", - "@esbuild/win32-x64": "0.28.1" + "node": ">=4" } }, - "node_modules/esbuild-wasm": { - "version": "0.28.1", - "integrity": "sha512-p/GD4E8oYRjg3kjdKrnMb0s4PzXgJF42e0MF4H0+ACyK/kIlFRp3e0fzOleIG+wBBm6MM3XQrbpe7soEA+vJIA==", + "node_modules/cssstyle": { + "version": "4.6.0", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", "dev": true, "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" + "dependencies": { + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" }, "engines": { "node": ">=18" } }, - "node_modules/escalade": { + "node_modules/cssstyle/node_modules/@asamuzakjp/css-color": { "version": "3.2.0", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", "dev": true, "license": "MIT", - "engines": { - "node": ">=6" + "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/escape-html": { - "version": "1.0.3", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true, - "license": "MIT" - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "node_modules/cssstyle/node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", "dev": true, - "license": "MIT", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18" } }, - "node_modules/eslint": { - "version": "8.57.1", - "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", - "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "node_modules/cssstyle/node_modules/@csstools/css-calc": { + "version": "2.1.4", + "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", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.1", - "@humanwhocodes/config-array": "^0.13.0", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=18" }, - "funding": { - "url": "https://opencollective.com/eslint" + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" } }, - "node_modules/eslint-compat-utils": { - "version": "0.5.1", - "integrity": "sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==", + "node_modules/cssstyle/node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "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", - "peer": true, "dependencies": { - "semver": "^7.5.4" + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" }, "engines": { - "node": ">=12" + "node": ">=18" }, "peerDependencies": { - "eslint": ">=6.0.0" + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" } }, - "node_modules/eslint-config-prettier": { - "version": "10.1.8", - "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "node_modules/cssstyle/node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "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", - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "funding": { - "url": "https://opencollective.com/eslint-config-prettier" + "engines": { + "node": ">=18" }, "peerDependencies": { - "eslint": ">=7.0.0" + "@csstools/css-tokenizer": "^3.0.4" } }, - "node_modules/eslint-config-standard": { - "version": "17.1.0", - "integrity": "sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==", + "node_modules/cssstyle/node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", "dev": true, "funding": [ { "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" + "url": "https://github.com/sponsors/csstools" }, { - "type": "consulting", - "url": "https://feross.org/support" + "type": "opencollective", + "url": "https://opencollective.com/csstools" } ], "license": "MIT", "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "eslint": "^8.0.1", - "eslint-plugin-import": "^2.25.2", - "eslint-plugin-n": "^15.0.0 || ^16.0.0 ", - "eslint-plugin-promise": "^6.0.0" + "node": ">=18" } }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.10", - "integrity": "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==", + "node_modules/cssstyle/node_modules/lru-cache": { + "version": "10.4.3", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/data-urls": { + "version": "8.0.0", + "integrity": "sha512-JQUCCK+/CAGbGGx618PlFZMXH81Jtj7t73CHv9dd/zVWvxgGT2zLFRjnKb+Ng46GIwcy+f7f88qd4duqllK+WQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "debug": "^3.2.7", - "is-core-module": "^2.16.1", - "resolve": "^2.0.0-next.6" + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^17.1.2" + }, + "engines": { + "node": "^22.14.0 || >=24.0.0" } }, - "node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "node_modules/data-view-buffer": { + "version": "1.0.2", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", "dev": true, "license": "MIT", "dependencies": { - "ms": "^2.1.1" + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-import-resolver-node/node_modules/resolve": { - "version": "2.0.0-next.7", - "integrity": "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==", + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", "dev": true, "license": "MIT", "dependencies": { + "call-bound": "^1.0.3", "es-errors": "^1.3.0", - "is-core-module": "^2.16.2", - "node-exports-info": "^1.6.0", - "object-keys": "^1.1.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" + "is-data-view": "^1.0.2" }, - "bin": { - "resolve": "bin/resolve" + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -13149,522 +11078,713 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-module-utils": { - "version": "2.14.0", - "integrity": "sha512-W2WCRZ9Dqntd+2u8jJcVMV2PKulc6RdLgUUoh/yQr3uB6lo/ZOeGx11sv60/8S4QFFKNslAlWhr9u0Ef7ZW6Ig==", + "node_modules/debug": { + "version": "4.4.3", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "dependencies": { - "debug": "^3.2.7" + "ms": "^2.1.3" }, "engines": { - "node": ">=4" + "node": ">=6.0" }, "peerDependenciesMeta": { - "eslint": { + "supports-color": { "optional": true } } }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "node_modules/decimal.js": { + "version": "10.6.0", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/dedent": { + "version": "1.7.2", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", "dev": true, "license": "MIT", - "dependencies": { - "ms": "^2.1.1" + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } } }, - "node_modules/eslint-plugin-es": { - "version": "3.0.1", - "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", + "node_modules/deep-is": { + "version": "0.1.4", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-browser": { + "version": "5.5.1", + "integrity": "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "eslint-utils": "^2.0.0", - "regexpp": "^3.0.0" + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" }, "engines": { - "node": ">=8.10.0" + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/mysticatea" + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" }, - "peerDependencies": { - "eslint": ">=4.19.1" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint-plugin-es-x": { - "version": "7.8.0", - "integrity": "sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==", + "node_modules/define-data-property": { + "version": "1.1.4", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", "dev": true, - "funding": [ - "https://github.com/sponsors/ota-meshi", - "https://opencollective.com/eslint" - ], "license": "MIT", "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.1.2", - "@eslint-community/regexpp": "^4.11.0", - "eslint-compat-utils": "^0.5.1" + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dependency-graph": { + "version": "1.0.0", + "integrity": "sha512-cW3gggJ28HZ/LExwxP2B++aiKxhJXMSIt9K48FOXQkm+vuG5gyatXnLsONRJdzO/7VfjDIiaOOa/bs4l464Lwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": ">=8" + "node": ">=8" } }, - "node_modules/eslint-plugin-import": { - "version": "2.32.0", - "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "node_modules/detect-newline": { + "version": "3.1.0", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", "dev": true, "license": "MIT", - "dependencies": { - "@rtsao/scc": "^1.1.0", - "array-includes": "^3.1.9", - "array.prototype.findlastindex": "^1.2.6", - "array.prototype.flat": "^1.3.3", - "array.prototype.flatmap": "^1.3.3", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.12.1", - "hasown": "^2.0.2", - "is-core-module": "^2.16.1", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.fromentries": "^2.0.8", - "object.groupby": "^1.0.3", - "object.values": "^1.2.1", - "semver": "^6.3.1", - "string.prototype.trimend": "^1.0.9", - "tsconfig-paths": "^3.15.0" - }, "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + "node": ">=8" } }, - "node_modules/eslint-plugin-import/node_modules/balanced-match": { - "version": "1.0.2", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "node_modules/detect-node": { + "version": "2.1.0", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, - "node_modules/eslint-plugin-import/node_modules/brace-expansion": { - "version": "1.1.21", - "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "node_modules/diff": { + "version": "4.0.4", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" } }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "3.2.7", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "node_modules/dns-packet": { + "version": "5.6.1", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "ms": "^2.1.1" + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" } }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "node_modules/doctrine": { + "version": "3.0.0", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, "license": "Apache-2.0", "dependencies": { "esutils": "^2.0.2" }, "engines": { - "node": ">=0.10.0" + "node": ">=6.0.0" } }, - "node_modules/eslint-plugin-import/node_modules/json5": { - "version": "1.0.2", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "node_modules/dom-serializer": { + "version": "2.0.0", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", "dev": true, "license": "MIT", "dependencies": { - "minimist": "^1.2.0" + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" }, - "bin": { - "json5": "lib/cli.js" + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" } }, - "node_modules/eslint-plugin-import/node_modules/minimatch": { - "version": "3.1.5", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "node_modules/domelementtype": { + "version": "2.3.0", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", "dev": true, - "license": "ISC", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "brace-expansion": "^1.1.7" + "domelementtype": "^2.3.0" }, "engines": { - "node": "*" + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" } }, - "node_modules/eslint-plugin-import/node_modules/semver": { - "version": "6.3.1", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/domutils": { + "version": "3.2.2", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" } }, - "node_modules/eslint-plugin-import/node_modules/strip-bom": { - "version": "3.0.0", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "node_modules/dunder-proto": { + "version": "1.0.1", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "dev": true, "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, "engines": { - "node": ">=4" + "node": ">= 0.4" } }, - "node_modules/eslint-plugin-import/node_modules/tsconfig-paths": { - "version": "3.15.0", - "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "node_modules/eastasianwidth": { + "version": "0.2.0", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.456", + "integrity": "sha512-8hboqI6xesozteFb8aa7OGknvxUtTeRJLz0FoePysUEh/oWbddykIGdgpEJRbPRg/nIMfBf2DFos+H2MtvJl2A==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", "dev": true, "license": "MIT", - "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" } }, - "node_modules/eslint-plugin-n": { - "version": "16.6.2", - "integrity": "sha512-6TyDmZ1HXoFQXnhCTUjVFULReoBPOAjpuiKELMkeP40yffI/1ZRO+d9ug/VC6fqISo2WkuIBk3cvuRPALaWlOQ==", + "node_modules/emoji-regex": { + "version": "10.6.0", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", "dev": true, "license": "MIT", "peer": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "builtins": "^5.0.1", - "eslint-plugin-es-x": "^7.5.0", - "get-tsconfig": "^4.7.0", - "globals": "^13.24.0", - "ignore": "^5.2.4", - "is-builtin-module": "^3.2.1", - "is-core-module": "^2.12.1", - "minimatch": "^3.1.2", - "resolve": "^1.22.2", - "semver": "^7.5.3" - }, "engines": { - "node": ">=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=7.0.0" + "node": ">= 4" } }, - "node_modules/eslint-plugin-n/node_modules/balanced-match": { - "version": "1.0.2", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "node_modules/encodeurl": { + "version": "2.0.0", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "dev": true, "license": "MIT", - "peer": true + "engines": { + "node": ">= 0.8" + } }, - "node_modules/eslint-plugin-n/node_modules/brace-expansion": { - "version": "1.1.21", - "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "node_modules/enhanced-resolve": { + "version": "5.26.0", + "integrity": "sha512-9vhedylFonb2YGogzUKX6+Ja72gOJbN1QHAqdrvqLwhdl/QWbKopzoUC9EbQNVsAns/bx/4uyqalrQAoy1IByw==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" } }, - "node_modules/eslint-plugin-n/node_modules/ignore": { - "version": "5.3.2", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "node_modules/entities": { + "version": "4.5.0", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, - "license": "MIT", - "peer": true, + "license": "BSD-2-Clause", "engines": { - "node": ">= 4" + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/eslint-plugin-n/node_modules/minimatch": { - "version": "3.1.5", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "node_modules/env-paths": { + "version": "2.2.1", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "dev": true, - "license": "ISC", - "peer": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, + "license": "MIT", "engines": { - "node": "*" + "node": ">=6" } }, - "node_modules/eslint-plugin-node": { - "version": "11.1.0", - "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", + "node_modules/environment": { + "version": "1.1.0", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", "dev": true, "license": "MIT", - "dependencies": { - "eslint-plugin-es": "^3.0.0", - "eslint-utils": "^2.0.0", - "ignore": "^5.1.1", - "minimatch": "^3.0.4", - "resolve": "^1.10.1", - "semver": "^6.1.0" - }, "engines": { - "node": ">=8.10.0" + "node": ">=18" }, - "peerDependencies": { - "eslint": ">=5.16.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint-plugin-node/node_modules/balanced-match": { - "version": "1.0.2", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/eslint-plugin-node/node_modules/brace-expansion": { - "version": "1.1.21", - "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "node_modules/errno": { + "version": "0.1.8", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" } }, - "node_modules/eslint-plugin-node/node_modules/ignore": { - "version": "5.3.2", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "node_modules/error-ex": { + "version": "1.3.4", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 4" + "dependencies": { + "is-arrayish": "^0.2.1" } }, - "node_modules/eslint-plugin-node/node_modules/minimatch": { - "version": "3.1.5", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "node_modules/es-abstract": { + "version": "1.24.2", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" }, "engines": { - "node": "*" - } - }, - "node_modules/eslint-plugin-node/node_modules/semver": { - "version": "6.3.1", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-plugin-prettier": { - "version": "5.5.7", - "integrity": "sha512-WjHyn3GrFd+ZR0rzsLiM4zJZ35PZMOE0SUIxzGmnWNG0126yNfJKJzObmmkYDwHJfGlkz3PSlzyG9ez/LJntwg==", + "node_modules/es-abstract-get": { + "version": "1.0.0", + "integrity": "sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==", "dev": true, "license": "MIT", "dependencies": { - "prettier-linter-helpers": "^1.0.1", - "synckit": "^0.12.3" + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.2", + "is-callable": "^1.2.7", + "object-inspect": "^1.13.4" }, "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": ">= 0.4" }, "funding": { - "url": "https://opencollective.com/eslint-plugin-prettier" - }, - "peerDependencies": { - "@types/eslint": ">=8.0.0", - "eslint": ">=8.0.0", - "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", - "prettier": ">=3.0.0" - }, - "peerDependenciesMeta": { - "@types/eslint": { - "optional": true - }, - "eslint-config-prettier": { - "optional": true - } + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-plugin-promise": { - "version": "6.6.0", - "integrity": "sha512-57Zzfw8G6+Gq7axm2Pdo3gW/Rx3h9Yywgn61uE/3elTCOePEHVrn2i5CdfBwA1BLK0Q0WqctICIUSqXZW/VprQ==", + "node_modules/es-define-property": { + "version": "1.0.1", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "dev": true, - "license": "ISC", + "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" + "node": ">= 0.4" } }, - "node_modules/eslint-plugin-standard": { - "version": "5.0.0", - "integrity": "sha512-eSIXPc9wBM4BrniMzJRBm2uoVuXz2EPa+NXPk2+itrVt+r5SbKFERx/IgrK/HmfjddyKVz2f+j+7gBRvu19xLg==", - "deprecated": "standard 16.0.0 and eslint-config-standard 16.0.0 no longer require the eslint-plugin-standard package. You can remove it from your dependencies with 'npm rm eslint-plugin-standard'. More info here: https://github.com/standard/standard/issues/1316", + "node_modules/es-errors": { + "version": "1.3.0", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], "license": "MIT", - "peerDependencies": { - "eslint": ">=5.0.0" + "engines": { + "node": ">= 0.4" } }, - "node_modules/eslint-scope": { - "version": "7.2.2", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "node_modules/es-module-lexer": { + "version": "2.3.2", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "license": "MIT", "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" + "es-errors": "^1.3.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">= 0.4" } }, - "node_modules/eslint-utils": { + "node_modules/es-set-tostringtag": { "version": "2.1.0", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "dev": true, "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^1.1.0" + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" + "node": ">= 0.4" } }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, "engines": { - "node": ">=4" + "node": ">= 0.4" } }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "node_modules/es-to-primitive": { + "version": "1.3.4", + "integrity": "sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "dependencies": { + "es-abstract-get": "^1.0.0", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "is-callable": "^1.2.7", + "is-date-object": "^1.1.0", + "is-symbol": "^1.1.1" + }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">= 0.4" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint/node_modules/ajv": { - "version": "6.15.0", - "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "node_modules/esbuild": { + "version": "0.28.1", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", "dev": true, + "hasInstallScript": true, "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "bin": { + "esbuild": "bin/esbuild" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" } }, - "node_modules/eslint/node_modules/balanced-match": { - "version": "1.0.2", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.21", - "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "node_modules/esbuild-wasm": { + "version": "0.28.1", + "integrity": "sha512-p/GD4E8oYRjg3kjdKrnMb0s4PzXgJF42e0MF4H0+ACyK/kIlFRp3e0fzOleIG+wBBm6MM3XQrbpe7soEA+vJIA==", "dev": true, "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" } }, - "node_modules/eslint/node_modules/ignore": { - "version": "5.3.2", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "node_modules/escalade": { + "version": "3.2.0", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, "license": "MIT", "engines": { - "node": ">= 4" + "node": ">=6" } }, - "node_modules/eslint/node_modules/json-schema-traverse": { - "version": "0.4.1", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "node_modules/escape-html": { + "version": "1.0.3", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "dev": true, "license": "MIT" }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.5", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, + "license": "MIT", "engines": { - "node": "*" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/espree": { - "version": "9.6.1", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "node_modules/eslint": { + "version": "8.57.1", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -13673,956 +11793,1135 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/esprima": { - "version": "4.0.1", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.7.0", - "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "node_modules/eslint-compat-utils": { + "version": "0.5.1", + "integrity": "sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", + "peer": true, "dependencies": { - "estraverse": "^5.1.0" + "semver": "^7.5.4" }, "engines": { - "node": ">=0.10" + "node": ">=12" + }, + "peerDependencies": { + "eslint": ">=6.0.0" } }, - "node_modules/esrecurse": { - "version": "4.3.0", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" }, - "engines": { - "node": ">=4.0" + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" } }, - "node_modules/estraverse": { - "version": "5.3.0", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "node_modules/eslint-config-standard": { + "version": "17.1.0", + "integrity": "sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==", "dev": true, - "license": "BSD-2-Clause", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", "engines": { - "node": ">=4.0" + "node": ">=12.0.0" + }, + "peerDependencies": { + "eslint": "^8.0.1", + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-n": "^15.0.0 || ^16.0.0 ", + "eslint-plugin-promise": "^6.0.0" } }, - "node_modules/estree-walker": { - "version": "2.0.2", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "node_modules/eslint-import-resolver-node": { + "version": "0.3.10", + "integrity": "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.16.1", + "resolve": "^2.0.0-next.6" + } }, - "node_modules/esutils": { - "version": "2.0.3", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" } }, - "node_modules/etag": { - "version": "1.8.1", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "node_modules/eslint-import-resolver-node/node_modules/resolve": { + "version": "2.0.0-next.7", + "integrity": "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==", "dev": true, "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.2", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, "engines": { - "node": ">= 0.6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "node_modules/eslint-module-utils": { + "version": "2.14.0", + "integrity": "sha512-W2WCRZ9Dqntd+2u8jJcVMV2PKulc6RdLgUUoh/yQr3uB6lo/ZOeGx11sv60/8S4QFFKNslAlWhr9u0Ef7ZW6Ig==", "dev": true, "license": "MIT", - "peer": true + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } }, - "node_modules/events": { - "version": "3.3.0", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.8.x" + "dependencies": { + "ms": "^2.1.1" } }, - "node_modules/eventsource": { - "version": "3.0.7", - "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "node_modules/eslint-plugin-es": { + "version": "3.0.1", + "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", "dev": true, "license": "MIT", "dependencies": { - "eventsource-parser": "^3.0.1" + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=4.19.1" } }, - "node_modules/eventsource-parser": { - "version": "3.1.1", - "integrity": "sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==", + "node_modules/eslint-plugin-es-x": { + "version": "7.8.0", + "integrity": "sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==", "dev": true, + "funding": [ + "https://github.com/sponsors/ota-meshi", + "https://opencollective.com/eslint" + ], "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.1.2", + "@eslint-community/regexpp": "^4.11.0", + "eslint-compat-utils": "^0.5.1" + }, "engines": { - "node": ">=18.0.0" + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": ">=8" } }, - "node_modules/execa": { - "version": "5.1.1", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "node_modules/eslint-plugin-import": { + "version": "2.32.0", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" }, "engines": { - "node": ">=10" + "node": ">=4" }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, - "node_modules/execa/node_modules/signal-exit": { - "version": "3.0.7", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "node_modules/eslint-plugin-import/node_modules/balanced-match": { + "version": "1.0.2", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, - "license": "ISC" + "license": "MIT" }, - "node_modules/exit-x": { - "version": "0.2.2", - "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.21", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.8.0" + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/expect": { - "version": "30.5.2", - "integrity": "sha512-0LNuMvs8/5mRYhnCR4k+lGV7fqPMs6Bnksda4S3zsCUmdxBpAn4zU4NNzXdq1pMKe/H4W5t/zVIDSJ/H3e0vDA==", + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/expect-utils": "30.5.2", - "@jest/get-type": "30.5.0", - "jest-matcher-utils": "30.5.2", - "jest-message-util": "30.5.1", - "jest-mock": "30.5.2", - "jest-util": "30.5.1" + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=0.10.0" } }, - "node_modules/exponential-backoff": { - "version": "3.1.3", - "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "node_modules/eslint-plugin-import/node_modules/json5": { + "version": "1.0.2", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, - "license": "Apache-2.0" + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } }, - "node_modules/express": { - "version": "5.3.0", - "integrity": "sha512-qCqy1xSoaugxOD4IVHb1ZWdkvCF2W3aE2okKouWj8VwJ+ztskSiXoBnCSJqfKT+cVPJhuJqe9YLbY425+Zh00g==", + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.5", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.3.0", - "content-disposition": "^2.0.1", - "content-type": "^2.0.0", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "depd": "^2.0.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.8", - "qs": "^6.16.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.1.0", - "vary": "^1.1.2" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": "*" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-import/node_modules/strip-bom": { + "version": "3.0.0", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" } }, - "node_modules/express-rate-limit": { - "version": "7.5.1", - "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", + "node_modules/eslint-plugin-import/node_modules/tsconfig-paths": { + "version": "3.15.0", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/eslint-plugin-n": { + "version": "16.6.2", + "integrity": "sha512-6TyDmZ1HXoFQXnhCTUjVFULReoBPOAjpuiKELMkeP40yffI/1ZRO+d9ug/VC6fqISo2WkuIBk3cvuRPALaWlOQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "builtins": "^5.0.1", + "eslint-plugin-es-x": "^7.5.0", + "get-tsconfig": "^4.7.0", + "globals": "^13.24.0", + "ignore": "^5.2.4", + "is-builtin-module": "^3.2.1", + "is-core-module": "^2.12.1", + "minimatch": "^3.1.2", + "resolve": "^1.22.2", + "semver": "^7.5.3" + }, "engines": { - "node": ">= 16" + "node": ">=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/express-rate-limit" + "url": "https://github.com/sponsors/mysticatea" }, "peerDependencies": { - "express": ">= 4.11" + "eslint": ">=7.0.0" } }, - "node_modules/express/node_modules/content-type": { - "version": "2.1.0", - "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "node_modules/eslint-plugin-n/node_modules/balanced-match": { + "version": "1.0.2", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "peer": true + }, + "node_modules/eslint-plugin-n/node_modules/brace-expansion": { + "version": "1.1.21", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "node_modules/eslint-plugin-n/node_modules/ignore": { + "version": "5.3.2", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 4" + } }, - "node_modules/fast-diff": { - "version": "1.3.0", - "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "node_modules/eslint-plugin-n/node_modules/minimatch": { + "version": "3.1.5", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, - "license": "Apache-2.0" + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "node_modules/eslint-plugin-node": { + "version": "11.1.0", + "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "eslint-plugin-es": "^3.0.0", + "eslint-utils": "^2.0.0", + "ignore": "^5.1.1", + "minimatch": "^3.0.4", + "resolve": "^1.10.1", + "semver": "^6.1.0" + }, + "engines": { + "node": ">=8.10.0" + }, + "peerDependencies": { + "eslint": ">=5.16.0" + } }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "node_modules/eslint-plugin-node/node_modules/balanced-match": { + "version": "1.0.2", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, "license": "MIT" }, - "node_modules/fast-uri": { - "version": "3.1.8", - "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", + "node_modules/eslint-plugin-node/node_modules/brace-expansion": { + "version": "1.1.21", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } }, - "node_modules/fastq": { - "version": "1.20.3", - "integrity": "sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==", + "node_modules/eslint-plugin-node/node_modules/ignore": { + "version": "5.3.2", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" + "license": "MIT", + "engines": { + "node": ">= 4" } }, - "node_modules/faye-websocket": { - "version": "0.11.4", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "node_modules/eslint-plugin-node/node_modules/minimatch": { + "version": "3.1.5", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, - "license": "Apache-2.0", - "peer": true, + "license": "ISC", "dependencies": { - "websocket-driver": ">=0.5.1" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=0.8.0" + "node": "*" } }, - "node_modules/fb-watchman": { - "version": "2.0.4", - "integrity": "sha512-tGQtaoYisPDriJBN0mT5yZSyNETnJGA1gTYxaVRAE5KUO3LI9kPUc9WIu8JXcxaIci+faIKlzXqXyPgrmubtBQ==", + "node_modules/eslint-plugin-node/node_modules/semver": { + "version": "6.3.1", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "MIT", - "dependencies": { - "bser": "2.1.2" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/fdir": { - "version": "6.5.0", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "node_modules/eslint-plugin-prettier": { + "version": "5.5.7", + "integrity": "sha512-WjHyn3GrFd+ZR0rzsLiM4zJZ35PZMOE0SUIxzGmnWNG0126yNfJKJzObmmkYDwHJfGlkz3PSlzyG9ez/LJntwg==", "dev": true, "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.1", + "synckit": "^0.12.3" + }, "engines": { - "node": ">=12.0.0" + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" }, "peerDependencies": { - "picomatch": "^3 || ^4" + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", + "prettier": ">=3.0.0" }, "peerDependenciesMeta": { - "picomatch": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { "optional": true } } }, - "node_modules/fflate": { - "version": "0.4.9", - "integrity": "sha512-zdxgIEddhfsyCaWpJ2SdXEP8ZMrKJ6+5jl4OupODcywU0IhRk6gdXuVGcPICyfx2H97hVK7xmJtRLPjkxAX8Vw==", + "node_modules/eslint-plugin-promise": { + "version": "6.6.0", + "integrity": "sha512-57Zzfw8G6+Gq7axm2Pdo3gW/Rx3h9Yywgn61uE/3elTCOePEHVrn2i5CdfBwA1BLK0Q0WqctICIUSqXZW/VprQ==", "dev": true, - "license": "MIT" + "license": "ISC", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" + } }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "node_modules/eslint-plugin-standard": { + "version": "5.0.0", + "integrity": "sha512-eSIXPc9wBM4BrniMzJRBm2uoVuXz2EPa+NXPk2+itrVt+r5SbKFERx/IgrK/HmfjddyKVz2f+j+7gBRvu19xLg==", + "deprecated": "standard 16.0.0 and eslint-config-standard 16.0.0 no longer require the eslint-plugin-standard package. You can remove it from your dependencies with 'npm rm eslint-plugin-standard'. More info here: https://github.com/standard/standard/issues/1316", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "peerDependencies": { + "eslint": ">=5.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", "dependencies": { - "flat-cache": "^3.0.4" + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/fill-range": { - "version": "7.1.1", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "node_modules/eslint-utils": { + "version": "2.1.0", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "to-regex-range": "^5.0.1" + "eslint-visitor-keys": "^1.1.0" }, "engines": { - "node": ">=8" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" } }, - "node_modules/finalhandler": { - "version": "2.1.1", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, + "license": "Apache-2.0", "engines": { - "node": ">= 18.0.0" + "node": ">=4" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "url": "https://opencollective.com/eslint" } }, - "node_modules/find-cache-directory": { - "version": "6.0.0", - "integrity": "sha512-CvFd5ivA6HcSHbD+59P7CyzINHXzwhuQK8RY7CxJZtgDSAtRlHiCaQpZQ2lMR/WRyUIEmzUvL6G2AGurMfegZA==", + "node_modules/eslint/node_modules/ajv": { + "version": "6.15.0", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { - "common-path-prefix": "^3.0.0", - "pkg-dir": "^8.0.0" - }, - "engines": { - "node": ">=20" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/find-cache-directory/node_modules/pkg-dir": { - "version": "8.0.0", - "integrity": "sha512-4peoBq4Wks0riS0z8741NVv+/8IiTvqnZAr8QGgtdifrtpdXbNw/FxRS1l6NFqm4EMzuS0EDqNNx4XGaz8cuyQ==", + "node_modules/eslint/node_modules/balanced-match": { + "version": "1.0.2", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.21", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { - "find-up-simple": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/find-up": { - "version": "5.0.0", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.5", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "*" } }, - "node_modules/find-up-simple": { - "version": "1.0.1", - "integrity": "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==", + "node_modules/espree": { + "version": "9.6.1", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, "engines": { - "node": ">=18" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/eslint" } }, - "node_modules/flat": { - "version": "5.0.2", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "node_modules/esprima": { + "version": "4.0.1", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true, - "license": "BSD-3-Clause", - "peer": true, + "license": "BSD-2-Clause", "bin": { - "flat": "cli.js" + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" } }, - "node_modules/flat-cache": { - "version": "3.2.0", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "node_modules/esquery": { + "version": "1.7.0", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" + "estraverse": "^5.1.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=0.10" } }, - "node_modules/flatted": { - "version": "3.4.4", - "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", - "dev": true, - "license": "ISC" - }, - "node_modules/follow-redirects": { - "version": "1.16.1", - "integrity": "sha512-FNvFGzoMLWmE6Yj9spb/zjd7yiNCHiAW9/Tg9CXrQ8wuu32HtlJOwWO11OJafl5FfY3DxTdQ0vj42zU1kvv5jg==", + "node_modules/esrecurse": { + "version": "4.3.0", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "peer": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, "engines": { "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } } }, - "node_modules/for-each": { - "version": "0.3.5", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "node_modules/estraverse": { + "version": "5.3.0", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7" - }, + "license": "BSD-2-Clause", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=4.0" } }, - "node_modules/foreground-child": { - "version": "3.3.1", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "node_modules/estree-walker": { + "version": "2.0.2", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=0.10.0" } }, - "node_modules/forwarded": { - "version": "0.2.0", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "node_modules/etag": { + "version": "1.8.1", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" } }, - "node_modules/fraction.js": { - "version": "5.3.4", - "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "node_modules/eventemitter3": { + "version": "4.0.7", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/events": { + "version": "3.3.0", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "dev": true, "license": "MIT", "peer": true, "engines": { - "node": "*" + "node": ">=0.8.x" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/rawify" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/fresh": { - "version": "2.0.0", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "node_modules/eventsource-parser": { + "version": "3.1.1", + "integrity": "sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">=18.0.0" } }, - "node_modules/fs-minipass": { - "version": "3.0.3", - "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "node_modules/execa": { + "version": "5.1.1", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "minipass": "^7.0.3" + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true, "license": "ISC" }, - "node_modules/fsevents": { - "version": "2.3.3", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "node_modules/exit-x": { + "version": "0.2.2", + "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", "dev": true, - "hasInstallScript": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">= 0.8.0" } }, - "node_modules/function-bind": { - "version": "1.1.2", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "node_modules/expect": { + "version": "30.5.2", + "integrity": "sha512-0LNuMvs8/5mRYhnCR4k+lGV7fqPMs6Bnksda4S3zsCUmdxBpAn4zU4NNzXdq1pMKe/H4W5t/zVIDSJ/H3e0vDA==", "dev": true, "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" + "dependencies": { + "@jest/expect-utils": "30.5.2", + "@jest/get-type": "30.5.0", + "jest-matcher-utils": "30.5.2", + "jest-message-util": "30.5.1", + "jest-mock": "30.5.2", + "jest-util": "30.5.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/function.prototype.name": { - "version": "1.2.0", - "integrity": "sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==", + "node_modules/exponential-backoff": { + "version": "3.1.3", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/express": { + "version": "5.3.0", + "integrity": "sha512-qCqy1xSoaugxOD4IVHb1ZWdkvCF2W3aE2okKouWj8VwJ+ztskSiXoBnCSJqfKT+cVPJhuJqe9YLbY425+Zh00g==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.9", - "call-bound": "^1.0.4", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2", - "hasown": "^2.0.4", - "is-callable": "^1.2.7", - "is-document.all": "^1.0.0" + "accepts": "^2.0.0", + "body-parser": "^2.3.0", + "content-disposition": "^2.0.1", + "content-type": "^2.0.0", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.8", + "qs": "^6.16.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.1.0", + "vary": "^1.1.2" }, "engines": { - "node": ">= 0.4" + "node": ">= 18" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "node_modules/express-rate-limit": { + "version": "8.7.3", + "integrity": "sha512-zPLqDI2f0gV2WmGnacLf9XSSqn4VldRyP5LCzOwx0BKH40mO6+2PCFkoSsRO2jePx87/Kf6NUF6P2QOTKXHbQA==", "dev": true, "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "ip-address": "^10.2.0" + }, + "engines": { + "node": ">= 16" + }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" } }, - "node_modules/generator-function": { - "version": "2.0.1", - "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "node_modules/express/node_modules/content-type": { + "version": "2.1.0", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } + "license": "MIT" }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "node_modules/fast-diff": { + "version": "1.3.0", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } + "license": "Apache-2.0" }, - "node_modules/get-east-asian-width": { - "version": "1.7.0", - "integrity": "sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==", + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "license": "MIT" }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true, - "license": "MIT", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.8", + "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastq": { + "version": "1.20.3", + "integrity": "sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==", + "dev": true, + "license": "ISC", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "reusify": "^1.0.4" } }, - "node_modules/get-package-type": { - "version": "0.1.0", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "node_modules/faye-websocket": { + "version": "0.11.4", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "websocket-driver": ">=0.5.1" + }, "engines": { - "node": ">=8.0.0" + "node": ">=0.8.0" } }, - "node_modules/get-proto": { - "version": "1.0.1", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "node_modules/fb-watchman": { + "version": "2.0.4", + "integrity": "sha512-tGQtaoYisPDriJBN0mT5yZSyNETnJGA1gTYxaVRAE5KUO3LI9kPUc9WIu8JXcxaIci+faIKlzXqXyPgrmubtBQ==", "dev": true, "license": "MIT", "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" + "bser": "2.1.2" } }, - "node_modules/get-stream": { - "version": "6.0.1", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "node_modules/fdir": { + "version": "6.5.0", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", "engines": { - "node": ">=10" + "node": ">=12.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/get-symbol-description": { - "version": "1.1.0", - "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "node_modules/fflate": { + "version": "0.4.9", + "integrity": "sha512-zdxgIEddhfsyCaWpJ2SdXEP8ZMrKJ6+5jl4OupODcywU0IhRk6gdXuVGcPICyfx2H97hVK7xmJtRLPjkxAX8Vw==", + "dev": true, + "license": "MIT" + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6" + "flat-cache": "^3.0.4" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/get-tsconfig": { - "version": "4.14.3", - "integrity": "sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA==", + "node_modules/fill-range": { + "version": "7.1.1", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "resolve-pkg-maps": "^1.0.0" + "to-regex-range": "^5.0.1" }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + "engines": { + "node": ">=8" } }, - "node_modules/glob": { - "version": "13.0.6", - "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "node_modules/finalhandler": { + "version": "2.1.1", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "MIT", "dependencies": { - "minimatch": "^10.2.2", - "minipass": "^7.1.3", - "path-scurry": "^2.0.2" + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" }, "engines": { - "node": "18 || 20 || >=22" + "node": ">= 18.0.0" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/glob-parent": { - "version": "6.0.2", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "node_modules/find-cache-directory": { + "version": "6.0.0", + "integrity": "sha512-CvFd5ivA6HcSHbD+59P7CyzINHXzwhuQK8RY7CxJZtgDSAtRlHiCaQpZQ2lMR/WRyUIEmzUvL6G2AGurMfegZA==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "is-glob": "^4.0.3" + "common-path-prefix": "^3.0.0", + "pkg-dir": "^8.0.0" }, "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/glob-to-regex.js": { - "version": "1.3.1", - "integrity": "sha512-zhWhMRsgnOym+1txhUmYJGD6eUsbUdrZv55gCYKuJX8t2p3+M8My23PD09bEIsreGUs5pnSa/idmcfp2/IlqGg==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": ">=10.0" + "node": ">=20" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/globals": { - "version": "13.24.0", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "node_modules/find-cache-directory/node_modules/pkg-dir": { + "version": "8.0.0", + "integrity": "sha512-4peoBq4Wks0riS0z8741NVv+/8IiTvqnZAr8QGgtdifrtpdXbNw/FxRS1l6NFqm4EMzuS0EDqNNx4XGaz8cuyQ==", "dev": true, "license": "MIT", "dependencies": { - "type-fest": "^0.20.2" + "find-up-simple": "^1.0.0" }, "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globalthis": { - "version": "1.0.4", - "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "node_modules/find-up": { + "version": "5.0.0", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "license": "MIT", "dependencies": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/gopd": { - "version": "1.2.0", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "node_modules/find-up-simple": { + "version": "1.0.1", + "integrity": "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/graphemer": { - "version": "1.4.0", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, - "node_modules/handle-thing": { - "version": "2.0.1", - "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/handlebars": { - "version": "4.7.10", - "integrity": "sha512-P5VJMVM7qgBn6vjXMw8WG9uVI+ncf2pi72j4de4yz5ZULLj2RGqLYaKOYGsgyrViQ0tePOVlN1tDCCXXtFqXKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.8", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/handlebars/node_modules/source-map": { - "version": "0.6.1", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "node_modules/flat": { + "version": "5.0.2", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", "dev": true, "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" + "peer": true, + "bin": { + "flat": "cli.js" } }, - "node_modules/has-bigints": { - "version": "1.1.0", - "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "node_modules/flat-cache": { + "version": "3.2.0", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.4" + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", "engines": { - "node": ">=8" + "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "node_modules/flatted": { + "version": "3.4.4", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "license": "ISC" }, - "node_modules/has-proto": { - "version": "1.2.0", - "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "node_modules/follow-redirects": { + "version": "1.16.1", + "integrity": "sha512-FNvFGzoMLWmE6Yj9spb/zjd7yiNCHiAW9/Tg9CXrQ8wuu32HtlJOwWO11OJafl5FfY3DxTdQ0vj42zU1kvv5jg==", "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.0" - }, + "peer": true, "engines": { - "node": ">= 0.4" + "node": ">=4.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependenciesMeta": { + "debug": { + "optional": true + } } }, - "node_modules/has-symbols": { - "version": "1.1.0", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "node_modules/for-each": { + "version": "0.3.5", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", "dev": true, "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, "engines": { "node": ">= 0.4" }, @@ -14630,461 +12929,438 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "node_modules/foreground-child": { + "version": "3.3.1", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "has-symbols": "^1.0.3" + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" }, "engines": { - "node": ">= 0.4" + "node": ">=14" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/hasown": { - "version": "2.0.4", - "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "node_modules/forwarded": { + "version": "0.2.0", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "dev": true, "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, "engines": { - "node": ">= 0.4" + "node": ">= 0.6" } }, - "node_modules/hono": { - "version": "4.13.13", - "integrity": "sha512-CQ46U0ZkAGmbT/4UxdzzGJpacP2IeKgY4a5/tOI9AABbpOMfK739wfDXmv1usCk+3RkKj1hQy4/fjhiwa2xlrA==", + "node_modules/fraction.js": { + "version": "5.3.4", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", "dev": true, "license": "MIT", "peer": true, "engines": { - "node": ">=16.9.0" - } - }, - "node_modules/hosted-git-info": { - "version": "9.0.3", - "integrity": "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^11.1.0" + "node": "*" }, - "engines": { - "node": "^20.17.0 || >=22.9.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" } }, - "node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "11.5.3", - "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", + "node_modules/fresh": { + "version": "2.0.0", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "MIT", "engines": { - "node": "20 || >=22" + "node": ">= 0.8" } }, - "node_modules/hpack.js": { - "version": "2.1.6", - "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "node_modules/fs-minipass": { + "version": "3.0.3", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", "dev": true, - "license": "MIT", - "peer": true, + "license": "ISC", "dependencies": { - "inherits": "^2.0.1", - "obuf": "^1.0.0", - "readable-stream": "^2.0.1", - "wbuf": "^1.1.0" + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/hpack.js/node_modules/isarray": { + "node_modules/fs.realpath": { "version": "1.0.0", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true, - "license": "MIT", - "peer": true + "license": "ISC" }, - "node_modules/hpack.js/node_modules/readable-stream": { - "version": "2.3.8", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "node_modules/fsevents": { + "version": "2.3.3", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, + "hasInstallScript": true, "license": "MIT", - "peer": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/hpack.js/node_modules/safe-buffer": { - "version": "5.1.2", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/hpack.js/node_modules/string_decoder": { - "version": "1.1.1", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "node_modules/function-bind": { + "version": "1.1.2", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "safe-buffer": "~5.1.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/html-encoding-sniffer": { - "version": "7.0.0", - "integrity": "sha512-UikN5yr7xsCDAq87Or5or0PAlD3HJJOKVzM05az588WnpDJ4Ux7a2A53Qi6gofGg2/EtvF/H4hCi/TXfCW4Y6w==", + "node_modules/function.prototype.name": { + "version": "1.2.0", + "integrity": "sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@exodus/bytes": "^1.15.1" + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2", + "hasown": "^2.0.4", + "is-callable": "^1.2.7", + "is-document.all": "^1.0.0" }, "engines": { - "node": "^22.13.0 || >=24.0.0" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, - "node_modules/htmlparser2": { - "version": "10.1.0", - "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", - "dev": true, - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.2.2", - "entities": "^7.0.1" - } - }, - "node_modules/htmlparser2/node_modules/entities": { - "version": "7.0.1", - "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/http-cache-semantics": { - "version": "4.3.0", - "integrity": "sha512-M5t5LlJpS1UHMjvwRQVdFHvPISGeLAxNcrWuJkeGh0KxsqCHZ1O3NXZU/8x7cD0BDcGW8kapxMKTvwlqrNkHkA==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/http-deceiver": { - "version": "1.2.7", - "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "node_modules/functions-have-names": { + "version": "1.2.3", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true, "license": "MIT", - "peer": true + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/http-errors": { + "node_modules/generator-function": { "version": "2.0.1", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", "dev": true, "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">= 0.4" } }, - "node_modules/http-parser-js": { - "version": "0.5.10", - "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/http-proxy": { - "version": "1.18.1", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - }, "engines": { - "node": ">=8.0.0" + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "node_modules/get-east-asian-width": { + "version": "1.7.0", + "integrity": "sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==", "dev": true, "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, "engines": { - "node": ">= 14" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/http-proxy-middleware": { - "version": "3.0.7", - "integrity": "sha512-iwbQltVlx8bCrqePUM8C+hllHvdawVhQJaLrj1X7qllkvFQdXFsr16pW/mo9+JDVjN+QO2XUx9jd8SmoFkE5qw==", + "node_modules/get-intrinsic": { + "version": "1.3.0", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@types/http-proxy": "^1.17.15", - "debug": "^4.3.6", - "http-proxy": "^1.18.1", - "is-glob": "^4.0.3", - "is-plain-object": "^5.0.0", - "micromatch": "^4.0.8" + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { - "node": "^14.18.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "node_modules/get-package-type": { + "version": "0.1.0", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true, "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, "engines": { - "node": ">= 14" + "node": ">=8.0.0" } }, - "node_modules/human-signals": { - "version": "2.1.0", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "node_modules/get-proto": { + "version": "1.0.1", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, "engines": { - "node": ">=10.17.0" + "node": ">= 0.4" } }, - "node_modules/hyperdyperid": { - "version": "1.2.0", - "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "node_modules/get-stream": { + "version": "6.0.1", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, "license": "MIT", - "peer": true, "engines": { - "node": ">=10.18" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/iconv-lite": { - "version": "0.7.3", - "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "node_modules/get-symbol-description": { + "version": "1.1.0", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", "dev": true, "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/icss-utils": { - "version": "5.1.0", - "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "node_modules/get-tsconfig": { + "version": "4.14.3", + "integrity": "sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA==", "dev": true, - "license": "ISC", + "license": "MIT", "peer": true, - "engines": { - "node": "^10 || ^12 || >= 14" + "dependencies": { + "resolve-pkg-maps": "^1.0.0" }, - "peerDependencies": { - "postcss": "^8.1.0" + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/ignore": { - "version": "7.0.12", - "integrity": "sha512-/8UvqAPU9DGTI9k4mxtf49U37Isfwr8Uts96+SBHIkFxPJnHS0Ew4f00sM4Scd8V8EjM0jUNtVDdv1kPdt35lg==", + "node_modules/glob": { + "version": "13.0.6", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", "dev": true, - "license": "MIT", + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, "engines": { - "node": ">= 4" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/ignore-walk": { - "version": "8.0.0", - "integrity": "sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A==", + "node_modules/glob-parent": { + "version": "6.0.2", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "license": "ISC", "dependencies": { - "minimatch": "^10.0.3" + "is-glob": "^4.0.3" }, "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": ">=10.13.0" } }, - "node_modules/immutable": { - "version": "5.1.9", - "integrity": "sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==", + "node_modules/glob-to-regex.js": { + "version": "1.3.1", + "integrity": "sha512-zhWhMRsgnOym+1txhUmYJGD6eUsbUdrZv55gCYKuJX8t2p3+M8My23PD09bEIsreGUs5pnSa/idmcfp2/IlqGg==", "dev": true, - "license": "MIT" + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } }, - "node_modules/import-fresh": { - "version": "3.3.1", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/globals": { + "version": "13.24.0", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "license": "MIT", "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" + "type-fest": "^0.20.2" }, "engines": { - "node": ">=6" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/import-local": { - "version": "3.2.0", - "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "node_modules/globalthis": { + "version": "1.0.4", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", "dev": true, "license": "MIT", "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" + "define-properties": "^1.2.1", + "gopd": "^1.0.1" }, "engines": { - "node": ">=8" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "node_modules/gopd": { + "version": "1.2.0", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.8.19" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/inflight": { - "version": "1.0.6", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "node_modules/graceful-fs": { + "version": "4.2.11", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } + "license": "ISC" }, - "node_modules/inherits": { - "version": "2.0.4", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "node_modules/graphemer": { + "version": "1.4.0", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true, - "license": "ISC" + "license": "MIT" }, - "node_modules/ini": { - "version": "5.0.0", - "integrity": "sha512-+N0ngpO3e7cRUWOJAS7qw0IZIVc6XPrW4MlFBdD066F2L4k1L6ker3hLqSq7iXxU5tgS4WGkIUElWn5vogAEnw==", + "node_modules/handle-thing": { + "version": "2.0.1", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", "dev": true, - "license": "ISC", + "license": "MIT", + "peer": true + }, + "node_modules/handlebars": { + "version": "4.7.10", + "integrity": "sha512-P5VJMVM7qgBn6vjXMw8WG9uVI+ncf2pi72j4de4yz5ZULLj2RGqLYaKOYGsgyrViQ0tePOVlN1tDCCXXtFqXKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.8", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" } }, - "node_modules/injection-js": { - "version": "2.6.1", - "integrity": "sha512-dbR5bdhi7TWDoCye9cByZqeg/gAfamm8Vu3G1KZOTYkOif8WkuM8CD0oeDPtZYMzT5YH76JAFB7bkmyY9OJi2A==", + "node_modules/handlebars/node_modules/source-map": { + "version": "0.6.1", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^2.0.0" + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" } }, - "node_modules/internal-slot": { + "node_modules/has-bigints": { "version": "1.1.0", - "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", "dev": true, "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.2", - "side-channel": "^1.1.0" - }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/ip-address": { - "version": "10.7.4", - "integrity": "sha512-DWBMKyeM25D70nrIiTkgQth59/G+naKOTep6huF9j+GmeK3DMD60Zvxg5d5qQnz+38AHgou6ean2LECX+lrKDg==", + "node_modules/has-flag": { + "version": "4.0.0", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "license": "MIT", "engines": { - "node": ">= 12" + "node": ">=8" } }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.10" + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-array-buffer": { - "version": "3.0.5", - "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "node_modules/has-proto": { + "version": "1.2.0", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" + "dunder-proto": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -15093,24 +13369,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-async-function": { - "version": "2.1.1", - "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "node_modules/has-symbols": { + "version": "1.1.0", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, "license": "MIT", - "dependencies": { - "async-function": "^1.0.0", - "call-bound": "^1.0.3", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, "engines": { "node": ">= 0.4" }, @@ -15118,13 +13381,13 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-bigint": { - "version": "1.1.0", - "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "node_modules/has-tostringtag": { + "version": "1.0.2", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, "license": "MIT", "dependencies": { - "has-bigints": "^1.0.2" + "has-symbols": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -15133,320 +13396,452 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "node_modules/hasown": { + "version": "2.0.4", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "binary-extensions": "^2.0.0" + "function-bind": "^1.1.2" }, "engines": { - "node": ">=8" + "node": ">= 0.4" } }, - "node_modules/is-boolean-object": { - "version": "1.2.2", - "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "node_modules/hono": { + "version": "4.13.13", + "integrity": "sha512-CQ46U0ZkAGmbT/4UxdzzGJpacP2IeKgY4a5/tOI9AABbpOMfK739wfDXmv1usCk+3RkKj1hQy4/fjhiwa2xlrA==", "dev": true, "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/hosted-git-info": { + "version": "9.0.3", + "integrity": "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==", + "dev": true, + "license": "ISC", "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" + "lru-cache": "^11.1.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/is-builtin-module": { - "version": "3.2.1", - "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "11.5.3", + "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "builtin-modules": "^3.3.0" + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/isarray": { + "version": "1.0.0", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.8", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hpack.js/node_modules/safe-buffer": { + "version": "5.1.2", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/hpack.js/node_modules/string_decoder": { + "version": "1.1.1", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "7.0.0", + "integrity": "sha512-UikN5yr7xsCDAq87Or5or0PAlD3HJJOKVzM05az588WnpDJ4Ux7a2A53Qi6gofGg2/EtvF/H4hCi/TXfCW4Y6w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@exodus/bytes": "^1.15.1" }, "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^22.13.0 || >=24.0.0" } }, - "node_modules/is-callable": { - "version": "1.2.7", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "node_modules/html-escaper": { + "version": "2.0.2", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/htmlparser2": { + "version": "10.1.0", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "entities": "^7.0.1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "7.0.1", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "dev": true, + "license": "BSD-2-Clause", "engines": { - "node": ">= 0.4" + "node": ">=0.12" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/is-core-module": { - "version": "2.17.0", - "integrity": "sha512-J/vG0zBCbIKOQFfufSwyXdMrsohyJIUNkrnmo6WZGzoM7tr/lsbfW5b2BvisL6zsyMzK9UxV9L6c7AoFbyXHOA==", + "node_modules/htmlrewriter": { + "name": "wf-htmlrewriter", + "version": "0.0.13", + "integrity": "sha512-ymlCpT94FPRo1eHGOoFgoLQfdJwofayAumP9Bcxl6XBZaoR0Qk1kAIvhsfXY1PADZs1Y6crbujQC8LnaMUi0OQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/http-cache-semantics": { + "version": "4.3.0", + "integrity": "sha512-M5t5LlJpS1UHMjvwRQVdFHvPISGeLAxNcrWuJkeGh0KxsqCHZ1O3NXZU/8x7cD0BDcGW8kapxMKTvwlqrNkHkA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/http-errors": { + "version": "2.0.1", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "dev": true, "license": "MIT", "dependencies": { - "hasown": "^2.0.4" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { - "node": ">= 0.4" + "node": ">= 0.8" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/is-data-view": { - "version": "1.0.2", - "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "node_modules/http-parser-js": { + "version": "0.5.10", + "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "is-typed-array": "^1.1.13" + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8.0.0" } }, - "node_modules/is-date-object": { - "version": "1.1.0", - "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" + "agent-base": "^7.1.0", + "debug": "^4.3.4" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 14" } }, - "node_modules/is-docker": { - "version": "3.0.0", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "node_modules/http-proxy-middleware": { + "version": "3.0.7", + "integrity": "sha512-iwbQltVlx8bCrqePUM8C+hllHvdawVhQJaLrj1X7qllkvFQdXFsr16pW/mo9+JDVjN+QO2XUx9jd8SmoFkE5qw==", "dev": true, "license": "MIT", "peer": true, - "bin": { - "is-docker": "cli.js" + "dependencies": { + "@types/http-proxy": "^1.17.15", + "debug": "^4.3.6", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.3", + "is-plain-object": "^5.0.0", + "micromatch": "^4.0.8" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^14.18.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-document.all": { - "version": "1.0.0", - "integrity": "sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==", + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.4" + "agent-base": "^7.1.2", + "debug": "4" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 14" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "node_modules/human-signals": { + "version": "2.1.0", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "engines": { - "node": ">=0.10.0" + "node": ">=10.17.0" } }, - "node_modules/is-finalizationregistry": { - "version": "1.1.1", - "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "node_modules/hyperdyperid": { + "version": "1.2.0", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", "dev": true, "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, + "peer": true, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=10.18" } }, - "node_modules/is-fullwidth-code-point": { - "version": "5.1.0", - "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "node_modules/iconv-lite": { + "version": "0.7.3", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", "dev": true, "license": "MIT", "dependencies": { - "get-east-asian-width": "^1.3.1" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { - "node": ">=18" + "node": ">=0.10.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "node_modules/icss-utils": { + "version": "5.1.0", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", "dev": true, - "license": "MIT", + "license": "ISC", + "peer": true, "engines": { - "node": ">=6" + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" } }, - "node_modules/is-generator-function": { - "version": "1.1.2", - "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "node_modules/ignore": { + "version": "7.0.12", + "integrity": "sha512-/8UvqAPU9DGTI9k4mxtf49U37Isfwr8Uts96+SBHIkFxPJnHS0Ew4f00sM4Scd8V8EjM0jUNtVDdv1kPdt35lg==", "dev": true, "license": "MIT", - "dependencies": { - "call-bound": "^1.0.4", - "generator-function": "^2.0.0", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 4" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "node_modules/ignore-walk": { + "version": "8.0.0", + "integrity": "sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "is-extglob": "^2.1.1" + "minimatch": "^10.0.3" }, "engines": { - "node": ">=0.10.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/is-in-ssh": { - "version": "1.0.0", - "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==", + "node_modules/immutable": { + "version": "5.1.9", + "integrity": "sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==", + "dev": true, + "license": "MIT" + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, "license": "MIT", - "peer": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, "engines": { - "node": ">=20" + "node": ">=6" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-inside-container": { - "version": "1.0.0", - "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "node_modules/import-local": { + "version": "3.2.0", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "is-docker": "^3.0.0" + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" }, "bin": { - "is-inside-container": "cli.js" + "import-local-fixture": "fixtures/cli.js" }, "engines": { - "node": ">=14.16" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-interactive": { - "version": "2.0.0", - "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "node_modules/imurmurhash": { + "version": "0.1.4", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.8.19" } }, - "node_modules/is-map": { - "version": "2.0.3", - "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "node_modules/inflight": { + "version": "1.0.6", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, - "license": "MIT", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ini": { + "version": "6.0.0", + "integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==", + "dev": true, + "license": "ISC", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/is-negative-zero": { - "version": "2.0.3", - "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "node_modules/injection-js": { + "version": "2.6.1", + "integrity": "sha512-dbR5bdhi7TWDoCye9cByZqeg/gAfamm8Vu3G1KZOTYkOif8WkuM8CD0oeDPtZYMzT5YH76JAFB7bkmyY9OJi2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + } + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", "dev": true, "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-network-error": { - "version": "1.3.2", - "integrity": "sha512-PhBY86zaxNZUuWP6h13Vu5oFe0XY6/UlKzQnYFELzGVHygP3MxmvTfYSG7GN3aIab/iWudSMgjSnG9Dq+nHrgA==", + "node_modules/ip-address": { + "version": "10.7.4", + "integrity": "sha512-DWBMKyeM25D70nrIiTkgQth59/G+naKOTep6huF9j+GmeK3DMD60Zvxg5d5qQnz+38AHgou6ean2LECX+lrKDg==", "dev": true, "license": "MIT", - "peer": true, "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 12" } }, - "node_modules/is-number": { - "version": "7.0.0", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "node_modules/ipaddr.js": { + "version": "1.9.1", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "dev": true, "license": "MIT", - "peer": true, "engines": { - "node": ">=0.12.0" + "node": ">= 0.10" } }, - "node_modules/is-number-object": { - "version": "1.1.1", - "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "node_modules/is-array-buffer": { + "version": "3.0.5", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", "dev": true, "license": "MIT", "dependencies": { + "call-bind": "^1.0.8", "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" @@ -15455,73 +13850,68 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "node_modules/is-arrayish": { + "version": "0.2.1", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } + "license": "MIT" }, - "node_modules/is-plain-obj": { - "version": "3.0.0", - "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "node_modules/is-async-function": { + "version": "2.1.1", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", "dev": true, "license": "MIT", - "peer": true, + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-plain-object": { - "version": "5.1.0", - "integrity": "sha512-bUi/yjmtKYcRVUtWRGr0UA6xEFh2I6zWUwMrUXB3s7bmYCaZ8a+0ZsTRkrawh/mzlSD1Y0Ph8bp/U+TvBpWDNw==", + "node_modules/is-bigint": { + "version": "1.1.0", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", "dev": true, "license": "MIT", - "peer": true, + "dependencies": { + "has-bigints": "^1.0.2" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-promise": { - "version": "4.0.0", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-regex": { - "version": "1.2.1", - "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "node_modules/is-binary-path": { + "version": "2.1.0", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" + "binary-extensions": "^2.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, - "node_modules/is-set": { - "version": "2.0.3", - "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "node_modules/is-boolean-object": { + "version": "1.2.2", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", "dev": true, "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, "engines": { "node": ">= 0.4" }, @@ -15529,41 +13919,41 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.4", - "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "node_modules/is-builtin-module": { + "version": "3.2.1", + "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "call-bound": "^1.0.3" + "builtin-modules": "^3.3.0" }, "engines": { - "node": ">= 0.4" + "node": ">=6" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-stream": { - "version": "2.0.1", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "node_modules/is-callable": { + "version": "1.2.7", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-string": { - "version": "1.1.1", - "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "node_modules/is-core-module": { + "version": "2.17.0", + "integrity": "sha512-J/vG0zBCbIKOQFfufSwyXdMrsohyJIUNkrnmo6WZGzoM7tr/lsbfW5b2BvisL6zsyMzK9UxV9L6c7AoFbyXHOA==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" + "hasown": "^2.0.4" }, "engines": { "node": ">= 0.4" @@ -15572,15 +13962,15 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-symbol": { - "version": "1.1.1", - "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "node_modules/is-data-view": { + "version": "1.0.2", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", - "has-symbols": "^1.1.0", - "safe-regex-test": "^1.1.0" + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -15589,13 +13979,14 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-typed-array": { - "version": "1.1.15", - "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "node_modules/is-date-object": { + "version": "1.1.0", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", "dev": true, "license": "MIT", "dependencies": { - "which-typed-array": "^1.1.16" + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -15604,23 +13995,30 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-unicode-supported": { - "version": "2.1.0", - "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "node_modules/is-docker": { + "version": "3.0.0", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", "dev": true, "license": "MIT", + "peer": true, + "bin": { + "is-docker": "cli.js" + }, "engines": { - "node": ">=18" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-weakmap": { - "version": "2.0.2", - "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "node_modules/is-document.all": { + "version": "1.0.0", + "integrity": "sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==", "dev": true, "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4" + }, "engines": { "node": ">= 0.4" }, @@ -15628,9 +14026,18 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-weakref": { + "node_modules/is-extglob": { + "version": "2.1.1", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { "version": "1.1.1", - "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", "dev": true, "license": "MIT", "dependencies": { @@ -15643,682 +14050,567 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-weakset": { - "version": "2.0.4", - "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" + "get-east-asian-width": "^1.3.1" }, "engines": { - "node": ">= 0.4" + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-what": { - "version": "4.1.16", - "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", + "node_modules/is-generator-fn": { + "version": "2.1.0", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=12.13" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" + "node": ">=6" } }, - "node_modules/is-wsl": { - "version": "3.1.1", - "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "node_modules/is-generator-function": { + "version": "1.1.2", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "is-inside-container": "^1.0.0" + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" }, "engines": { - "node": ">=16" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/isarray": { - "version": "2.0.5", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/isobject": { - "version": "3.0.1", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "node_modules/is-glob": { + "version": "4.0.3", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "license": "MIT", - "peer": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, "engines": { "node": ">=0.10.0" } }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "node_modules/is-in-ssh": { + "version": "1.0.0", + "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", + "peer": true, "engines": { - "node": ">=8" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/istanbul-lib-instrument": { - "version": "6.0.3", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "node_modules/is-inside-container": { + "version": "1.0.0", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", + "peer": true, "dependencies": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" }, "engines": { - "node": ">=10" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "node_modules/is-interactive": { + "version": "2.0.0", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/istanbul-lib-source-maps": { - "version": "5.0.6", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "node_modules/is-map": { + "version": "2.0.3", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.23", - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0" - }, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "node_modules/is-negative-zero": { + "version": "2.0.3", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jackspeak": { - "version": "3.4.3", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "node_modules/is-network-error": { + "version": "1.3.2", + "integrity": "sha512-PhBY86zaxNZUuWP6h13Vu5oFe0XY6/UlKzQnYFELzGVHygP3MxmvTfYSG7GN3aIab/iWudSMgjSnG9Dq+nHrgA==", "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" + "license": "MIT", + "peer": true, + "engines": { + "node": ">=16" }, "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest": { - "version": "30.5.2", - "integrity": "sha512-3gnpdBIza8ijCl3iMV9ChE3hSt1+46865qqod863gQtJr1DixOkAU5DoGbi3u+XlRcw++BhZQMu/y4xBdHBvSQ==", + "node_modules/is-number": { + "version": "7.0.0", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/core": "30.5.2", - "@jest/types": "30.5.1", - "import-local": "^3.2.0", - "jest-cli": "30.5.2" - }, - "bin": { - "jest": "bin/jest.js" - }, + "peer": true, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "node": ">=0.12.0" } }, - "node_modules/jest-changed-files": { - "version": "30.5.1", - "integrity": "sha512-0+bvMM/ENhDI29Z8q1r4HxiDIi4G5tnBmSw4esfPQoj9q8Nik7KIyuxHkTVnnniJQf05SxpGaKDQ+4h28ynGPg==", + "node_modules/is-number-object": { + "version": "1.1.1", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", "dev": true, "license": "MIT", "dependencies": { - "execa": "^5.1.1", - "jest-util": "30.5.1", - "p-limit": "^3.1.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-circus": { - "version": "30.5.2", - "integrity": "sha512-vffFgjs5JSJ9q4ds1vNW3klKYPJfYfIY61LT/zaZgvTeASJOWZUt8LBfHgIcV4rwxlPBLl/ePBGccHEclS4PlQ==", + "node_modules/is-path-inside": { + "version": "3.0.3", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/environment": "30.5.2", - "@jest/expect": "30.5.2", - "@jest/test-result": "30.5.2", - "@jest/types": "30.5.1", - "@types/node": "*", - "chalk": "^4.1.2", - "co": "^4.6.0", - "dedent": "^1.6.0", - "is-generator-fn": "^2.1.0", - "jest-each": "30.5.2", - "jest-matcher-utils": "30.5.2", - "jest-message-util": "30.5.1", - "jest-runtime": "30.5.2", - "jest-snapshot": "30.5.2", - "jest-util": "30.5.1", - "p-limit": "^3.1.0", - "pretty-format": "30.5.1", - "pure-rand": "^7.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.6" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=8" } }, - "node_modules/jest-cli": { - "version": "30.5.2", - "integrity": "sha512-YYYxUUVjFgPvgEleKNKMaYGIFgR3l3832Cl0kRL/oCiDZ03v7wImUtquHl2OOBCCNgprjZgBN5bczS/JklPBDg==", + "node_modules/is-plain-obj": { + "version": "3.0.0", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/core": "30.5.2", - "@jest/test-result": "30.5.2", - "@jest/types": "30.5.1", - "chalk": "^4.1.2", - "exit-x": "^0.2.2", - "import-local": "^3.2.0", - "jest-config": "30.5.2", - "jest-util": "30.5.1", - "jest-validate": "30.5.1", - "yargs": "^17.7.2" - }, - "bin": { - "jest": "bin/jest.js" - }, + "peer": true, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + "node": ">=10" }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-cli/node_modules/cliui": { - "version": "8.0.1", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "node_modules/is-plain-object": { + "version": "5.1.0", + "integrity": "sha512-bUi/yjmtKYcRVUtWRGr0UA6xEFh2I6zWUwMrUXB3s7bmYCaZ8a+0ZsTRkrawh/mzlSD1Y0Ph8bp/U+TvBpWDNw==", "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, + "license": "MIT", + "peer": true, "engines": { - "node": ">=12" + "node": ">=0.10.0" } }, - "node_modules/jest-cli/node_modules/emoji-regex": { - "version": "8.0.0", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", "dev": true, "license": "MIT" }, - "node_modules/jest-cli/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/is-promise": { + "version": "4.0.0", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", "dev": true, "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-cli/node_modules/string-width": { - "version": "4.2.3", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/is-set": { + "version": "2.0.3", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", "dev": true, "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-cli/node_modules/wrap-ansi": { - "version": "7.0.0", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "call-bound": "^1.0.3" }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-cli/node_modules/yargs": { - "version": "17.7.3", - "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "node_modules/is-stream": { + "version": "2.0.1", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, "engines": { - "node": ">=12" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-config": { - "version": "30.5.2", - "integrity": "sha512-U6221OZekabePvGcGPf4raIBKMvYQWFHvhobDJRojOYaFMj2HaGytAdknjDhYc6JTz2fdHW9+6k0wWUDaNeOFQ==", + "node_modules/is-string": { + "version": "1.1.1", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.27.4", - "@jest/get-type": "30.5.0", - "@jest/pattern": "30.5.0", - "@jest/test-sequencer": "30.5.2", - "@jest/types": "30.5.1", - "babel-jest": "30.5.2", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "deepmerge": "^4.3.1", - "glob": "^13.0.6", - "graceful-fs": "^4.2.11", - "jest-circus": "30.5.2", - "jest-docblock": "30.5.0", - "jest-environment-node": "30.5.2", - "jest-regex-util": "30.5.0", - "jest-resolve": "30.5.1", - "jest-runner": "30.5.2", - "jest-util": "30.5.1", - "jest-validate": "30.5.1", - "parse-json": "^5.2.0", - "pretty-format": "30.5.1", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "esbuild-register": ">=3.4.0", - "ts-node": ">=9.0.0" + "node": ">= 0.4" }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "esbuild-register": { - "optional": true - }, - "ts-node": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-diff": { - "version": "30.5.2", - "integrity": "sha512-rBtHnI6BWTiWj3lM7fOLVfChx7R9B0u9VV3PRHawKs79x6ZjW1QoSrKenaigNGWlvhtoi2BqvDbABDie+Os3xQ==", + "node_modules/is-symbol": { + "version": "1.1.1", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", "dev": true, "license": "MIT", "dependencies": { - "@jest/diff-sequences": "30.5.0", - "@jest/get-type": "30.5.0", - "chalk": "^4.1.2", - "pretty-format": "30.5.1" + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-docblock": { - "version": "30.5.0", - "integrity": "sha512-NwDqcxtoZi33RhuW+zJS/RVA3rmheQ8BnwpYZuc/Eruaz6seQb7+aoCeDZu/3X7W2XmD8DbSo9Pn72DbKsBFYw==", + "node_modules/is-typed-array": { + "version": "1.1.15", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", "dev": true, "license": "MIT", "dependencies": { - "detect-newline": "^3.1.0" + "which-typed-array": "^1.1.16" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-each": { - "version": "30.5.2", - "integrity": "sha512-GRrW+7fmmgmkvyfEBqc1QFdWnF1Ex5yG6Y6AWL4TTxnkWlKIT98dsKh3P3UuWm6v4XsQwVE6DEzXIPMzWJ3ZbA==", + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/get-type": "30.5.0", - "@jest/types": "30.5.1", - "chalk": "^4.1.2", - "jest-regex-util": "30.5.0", - "jest-util": "30.5.1", - "pretty-format": "30.5.1" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-environment-jsdom": { - "version": "30.5.2", - "integrity": "sha512-6NOZjEP+WNi4WXjbxb/1ROnfaMxn6Dhqu6uSC22o0H28z+uppejs/jZ9KCVoLepqqoCjcNZujtzQqC9jSV15jg==", + "node_modules/is-weakmap": { + "version": "2.0.2", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/environment": "30.5.2", - "@jest/environment-jsdom-abstract": "30.5.2", - "@types/jsdom": "^21.1.7", - "jsdom": "^26.1.0" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "canvas": "^3.0.0" + "node": ">= 0.4" }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-environment-jsdom/node_modules/data-urls": { - "version": "5.0.0", - "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "node_modules/is-weakref": { + "version": "1.1.1", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", "dev": true, "license": "MIT", "dependencies": { - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.0.0" + "call-bound": "^1.0.3" }, "engines": { - "node": ">=18" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-environment-jsdom/node_modules/html-encoding-sniffer": { - "version": "4.0.0", - "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "node_modules/is-weakset": { + "version": "2.0.4", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", "dev": true, "license": "MIT", "dependencies": { - "whatwg-encoding": "^3.1.1" + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" }, "engines": { - "node": ">=18" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-environment-jsdom/node_modules/jsdom": { - "version": "26.1.0", - "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", + "node_modules/is-what": { + "version": "4.1.16", + "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", "dev": true, "license": "MIT", - "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" + "node": ">=12.13" }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/mesqueeb" } - }, - "node_modules/jest-environment-jsdom/node_modules/tldts": { - "version": "6.1.86", - "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "tldts-core": "^6.1.86" + "is-inside-container": "^1.0.0" }, - "bin": { - "tldts": "bin/cli.js" + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-environment-jsdom/node_modules/tldts-core": { - "version": "6.1.86", - "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "node_modules/isarray": { + "version": "2.0.5", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", "dev": true, "license": "MIT" }, - "node_modules/jest-environment-jsdom/node_modules/tough-cookie": { - "version": "5.1.2", - "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "node_modules/isexe": { + "version": "2.0.0", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tldts": "^6.1.32" - }, - "engines": { - "node": ">=16" - } + "license": "ISC" }, - "node_modules/jest-environment-jsdom/node_modules/tr46": { - "version": "5.1.1", - "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "node_modules/isobject": { + "version": "3.0.1", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", "dev": true, "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, + "peer": true, "engines": { - "node": ">=18" + "node": ">=0.10.0" } }, - "node_modules/jest-environment-jsdom/node_modules/w3c-xmlserializer": { - "version": "5.0.0", - "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, - "license": "MIT", - "dependencies": { - "xml-name-validator": "^5.0.0" - }, + "license": "BSD-3-Clause", "engines": { - "node": ">=18" + "node": ">=8" } }, - "node_modules/jest-environment-jsdom/node_modules/webidl-conversions": { - "version": "7.0.0", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", "dev": true, - "license": "BSD-2-Clause", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, "engines": { - "node": ">=12" + "node": ">=10" } }, - "node_modules/jest-environment-jsdom/node_modules/whatwg-mimetype": { - "version": "4.0.0", - "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, "engines": { - "node": ">=18" + "node": ">=10" } }, - "node_modules/jest-environment-jsdom/node_modules/whatwg-url": { - "version": "14.2.0", - "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "dependencies": { - "tr46": "^5.1.0", - "webidl-conversions": "^7.0.0" + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" }, "engines": { - "node": ">=18" + "node": ">=10" } }, - "node_modules/jest-environment-node": { - "version": "30.5.2", - "integrity": "sha512-Ciz4KgTGIbojxSKpuFImgWJj5EyqfE8xMq9NxWmOXnQywHHS03H9Mv+vaCB29J5pr3JBrY27WbRV18rBdjh8Dg==", + "node_modules/istanbul-reports": { + "version": "3.2.0", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "dependencies": { - "@jest/environment": "30.5.2", - "@jest/fake-timers": "30.5.2", - "@jest/types": "30.5.1", - "@types/node": "*", - "jest-mock": "30.5.2", - "jest-util": "30.5.1", - "jest-validate": "30.5.1" + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=8" } }, - "node_modules/jest-haste-map": { - "version": "30.5.1", - "integrity": "sha512-VIFgt67jW480YDxKfEv9IYQKrFpYt7bOCMn3VsnjK7AK9qo7p6acpnA1DHwsVOULE3dTaYew/6JaTD/d0VDHoQ==", + "node_modules/jackspeak": { + "version": "3.4.3", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, - "license": "MIT", + "license": "BlueOak-1.0.0", "dependencies": { - "@jest/types": "30.5.1", - "@parcel/watcher": "^2.6.0", - "@types/node": "*", - "anymatch": "^3.1.3", - "fb-watchman": "^2.0.2", - "fdir": "^6.5.0", - "graceful-fs": "^4.2.11", - "jest-regex-util": "30.5.0", - "jest-util": "30.5.1", - "jest-worker": "30.5.1", - "picomatch": "^4.0.3" + "@isaacs/cliui": "^8.0.2" }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/jest-leak-detector": { - "version": "30.5.1", - "integrity": "sha512-gt4GT2aWgEoCNTcBe4rqS74xTIUJxi+UD9SNSu9aOk5LmTEd6fS5KSTmAKAfitCCktQHNN+upUuL6EkBfFbMDQ==", + "node_modules/jest": { + "version": "30.5.2", + "integrity": "sha512-3gnpdBIza8ijCl3iMV9ChE3hSt1+46865qqod863gQtJr1DixOkAU5DoGbi3u+XlRcw++BhZQMu/y4xBdHBvSQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.5.0", - "pretty-format": "30.5.1" + "@jest/core": "30.5.2", + "@jest/types": "30.5.1", + "import-local": "^3.2.0", + "jest-cli": "30.5.2" + }, + "bin": { + "jest": "bin/jest.js" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/jest-matcher-utils": { - "version": "30.5.2", - "integrity": "sha512-lFkB365PTIHOhPzwrb9T6gvlDnPpOZ7/c3ewOKKB7bzztqjVrtlyRL2MkyfJXpyY2u2SKw001JKrTx14fBUFcQ==", + "node_modules/jest-changed-files": { + "version": "30.5.1", + "integrity": "sha512-0+bvMM/ENhDI29Z8q1r4HxiDIi4G5tnBmSw4esfPQoj9q8Nik7KIyuxHkTVnnniJQf05SxpGaKDQ+4h28ynGPg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.5.0", - "chalk": "^4.1.2", - "jest-diff": "30.5.2", - "pretty-format": "30.5.1" + "execa": "^5.1.1", + "jest-util": "30.5.1", + "p-limit": "^3.1.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-message-util": { - "version": "30.5.1", - "integrity": "sha512-UdQlLdd9wL/Ys7xRErckqwD6wPlSZYueosSWuHc1r2ztGLwlgPvtSJq2+BPEgaEY13WLvfFbmhTj8pba0Sd1jg==", + "node_modules/jest-circus": { + "version": "30.5.2", + "integrity": "sha512-vffFgjs5JSJ9q4ds1vNW3klKYPJfYfIY61LT/zaZgvTeASJOWZUt8LBfHgIcV4rwxlPBLl/ePBGccHEclS4PlQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", + "@jest/environment": "30.5.2", + "@jest/expect": "30.5.2", + "@jest/test-result": "30.5.2", "@jest/types": "30.5.1", - "@types/stack-utils": "^2.0.3", + "@types/node": "*", "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", + "co": "^4.6.0", + "dedent": "^1.6.0", + "is-generator-fn": "^2.1.0", + "jest-each": "30.5.2", + "jest-matcher-utils": "30.5.2", + "jest-message-util": "30.5.1", + "jest-runtime": "30.5.2", + "jest-snapshot": "30.5.2", "jest-util": "30.5.1", - "picomatch": "^4.0.3", + "p-limit": "^3.1.0", "pretty-format": "30.5.1", + "pure-rand": "^7.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.6" }, @@ -16326,1142 +14618,1187 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-mock": { + "node_modules/jest-cli": { "version": "30.5.2", - "integrity": "sha512-KTHb37Fhj8xY8qPFvTFaFJHcdsumQp+ExsUlgoX232Agmc706gagBs53+CYvulV7MVQo9AgXOUxsqzzVBoJlrA==", + "integrity": "sha512-YYYxUUVjFgPvgEleKNKMaYGIFgR3l3832Cl0kRL/oCiDZ03v7wImUtquHl2OOBCCNgprjZgBN5bczS/JklPBDg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/expect-utils": "30.5.2", + "@jest/core": "30.5.2", + "@jest/test-result": "30.5.2", "@jest/types": "30.5.1", - "@types/node": "*", - "jest-util": "30.5.1" + "chalk": "^4.1.2", + "exit-x": "^0.2.2", + "import-local": "^3.2.0", + "jest-config": "30.5.2", + "jest-util": "30.5.1", + "jest-validate": "30.5.1", + "yargs": "^17.7.2" + }, + "bin": { + "jest": "bin/jest.js" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/jest-preset-angular": { - "version": "17.0.3", - "integrity": "sha512-ufDKlvNgT5eMq72OCVkac+9ibOkgL2Ee7orUhlQ1IdTLqn76eniNDz8oZ9vE3Biwh5zSitdSojZvB/Tf+KaWsQ==", + "node_modules/jest-cli/node_modules/cliui": { + "version": "8.0.1", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@jest/environment-jsdom-abstract": "^30.5.1", - "bs-logger": "^0.2.6", - "esbuild-wasm": ">=0.28.0", - "jest-util": "^30.5.1", - "pretty-format": "^30.5.1", - "ts-jest": "^29.4.12" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": "^20.11.1 || >=22.0.0" - }, - "optionalDependencies": { - "esbuild": ">=0.28.0" - }, - "peerDependencies": { - "@angular/compiler-cli": ">=20.0.0 <23.0.0", - "@angular/core": ">=20.0.0 <23.0.0", - "@angular/platform-browser": ">=20.0.0 <23.0.0", - "jest": "^30.0.0", - "jsdom": ">=26.0.0", - "typescript": ">=5.8" + "node": ">=12" } }, - "node_modules/jest-regex-util": { - "version": "30.5.0", - "integrity": "sha512-Mg0WK7A6xRHLSA1udJ8y9f3lM0uUhFTBnLKzwPmqB9AylvpleJ6BLemR8K9dK27DY+cesDryoA7yLZCAHsPG1A==", + "node_modules/jest-cli/node_modules/emoji-regex": { + "version": "8.0.0", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-cli/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, "license": "MIT", "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=8" } }, - "node_modules/jest-resolve": { - "version": "30.5.1", - "integrity": "sha512-wprhLejRtwN6h8ZgaqC0eYjGJ1uMdGPT/+b3eFncbG4NWuuP9QL+vWwRinu9waw7hkOLcpMJGVJAMgBwsPssMQ==", + "node_modules/jest-cli/node_modules/string-width": { + "version": "4.2.3", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.5.1", - "jest-util": "30.5.1", - "jest-validate": "30.5.1", - "slash": "^3.0.0", - "unrs-resolver": "^1.12.1" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=8" } }, - "node_modules/jest-resolve-dependencies": { - "version": "30.5.2", - "integrity": "sha512-GMI0DP5enX9Z2qW2zFy1bOkrCfWAOPRJQ7qHt0pdfrxBPsgWxglMPrRSK2TX/wRWYxcPRSLFg3Z88qPOzHx7FQ==", + "node_modules/jest-cli/node_modules/wrap-ansi": { + "version": "7.0.0", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", "dependencies": { - "jest-regex-util": "30.5.0", - "jest-snapshot": "30.5.2" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/jest-runner": { - "version": "30.5.2", - "integrity": "sha512-/B6px7hiiNhVSRwuv9AGn0nawYvwsHeQZ3ItTd5Gp2diFd0EOKaWVeWqcbw3L88vx1GfpCKjQkxWItZCTl11Rw==", + "node_modules/jest-cli/node_modules/yargs": { + "version": "17.7.3", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.5.2", - "@jest/environment": "30.5.2", - "@jest/source-map": "30.5.2", - "@jest/test-result": "30.5.2", - "@jest/transform": "30.5.2", - "@jest/types": "30.5.1", - "@types/node": "*", - "chalk": "^4.1.2", - "emittery": "^0.13.1", - "exit-x": "^0.2.2", - "graceful-fs": "^4.2.11", - "jest-docblock": "30.5.0", - "jest-environment-node": "30.5.2", - "jest-haste-map": "30.5.1", - "jest-leak-detector": "30.5.1", - "jest-message-util": "30.5.1", - "jest-resolve": "30.5.1", - "jest-runtime": "30.5.2", - "jest-util": "30.5.1", - "jest-watcher": "30.5.2", - "jest-worker": "30.5.1", - "p-limit": "^3.1.0" + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=12" } }, - "node_modules/jest-runtime": { + "node_modules/jest-config": { "version": "30.5.2", - "integrity": "sha512-0paUAHBlheai10A3XSy9Xh+YBG0IROy+/qzcDPzQ/nbbXrznQzeWy5qniICWK6d3W3scaRQCVAbXdcK0ibBq4w==", + "integrity": "sha512-U6221OZekabePvGcGPf4raIBKMvYQWFHvhobDJRojOYaFMj2HaGytAdknjDhYc6JTz2fdHW9+6k0wWUDaNeOFQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.5.2", - "@jest/fake-timers": "30.5.2", - "@jest/globals": "30.5.2", - "@jest/source-map": "30.5.2", - "@jest/test-result": "30.5.2", - "@jest/transform": "30.5.2", + "@babel/core": "^7.27.4", + "@jest/get-type": "30.5.0", + "@jest/pattern": "30.5.0", + "@jest/test-sequencer": "30.5.2", "@jest/types": "30.5.1", - "@types/node": "*", + "babel-jest": "30.5.2", "chalk": "^4.1.2", - "cjs-module-lexer": "^2.2.0", - "collect-v8-coverage": "^1.0.2", - "es-module-lexer": "^2.1.0", + "ci-info": "^4.2.0", + "deepmerge": "^4.3.1", "glob": "^13.0.6", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.5.1", - "jest-message-util": "30.5.1", - "jest-mock": "30.5.2", + "jest-circus": "30.5.2", + "jest-docblock": "30.5.0", + "jest-environment-node": "30.5.2", "jest-regex-util": "30.5.0", "jest-resolve": "30.5.1", - "jest-snapshot": "30.5.2", + "jest-runner": "30.5.2", "jest-util": "30.5.1", + "jest-validate": "30.5.1", + "parse-json": "^5.2.0", + "pretty-format": "30.5.1", "slash": "^3.0.0", - "strip-bom": "^4.0.0" + "strip-json-comments": "^3.1.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "esbuild-register": ">=3.4.0", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "esbuild-register": { + "optional": true + }, + "ts-node": { + "optional": true + } } }, - "node_modules/jest-snapshot": { + "node_modules/jest-diff": { "version": "30.5.2", - "integrity": "sha512-jKIdes25AnAH73dOCi2qE6J6vHJ6L2vyBS11M89kT50DVJcLTf2AOjvcc+2vVaN8lUbY5v2R83aqXo3Ro9fqYA==", + "integrity": "sha512-rBtHnI6BWTiWj3lM7fOLVfChx7R9B0u9VV3PRHawKs79x6ZjW1QoSrKenaigNGWlvhtoi2BqvDbABDie+Os3xQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.27.4", - "@babel/generator": "^7.27.5", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/plugin-syntax-typescript": "^7.27.1", - "@babel/types": "^7.27.3", - "@jest/expect-utils": "30.5.2", + "@jest/diff-sequences": "30.5.0", "@jest/get-type": "30.5.0", - "@jest/snapshot-utils": "30.5.1", - "@jest/transform": "30.5.2", - "@jest/types": "30.5.1", - "babel-preset-current-node-syntax": "^1.2.0", "chalk": "^4.1.2", - "expect": "30.5.2", - "graceful-fs": "^4.2.11", - "jest-diff": "30.5.2", - "jest-matcher-utils": "30.5.2", - "jest-message-util": "30.5.1", - "jest-util": "30.5.1", - "pretty-format": "30.5.1", - "semver": "^7.7.2", - "synckit": "^0.11.8" + "pretty-format": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-snapshot/node_modules/synckit": { - "version": "0.11.13", - "integrity": "sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@pkgr/core": "^0.3.6" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/synckit" - } - }, - "node_modules/jest-util": { - "version": "30.5.1", - "integrity": "sha512-yKuxmNy2rSbTXw+3SIPanJo+nV4/BS1p26v44IYBFMsswSQySfMMcPHErnOncda7i9HEz0q605rIhSTBVgrZTg==", + "node_modules/jest-docblock": { + "version": "30.5.0", + "integrity": "sha512-NwDqcxtoZi33RhuW+zJS/RVA3rmheQ8BnwpYZuc/Eruaz6seQb7+aoCeDZu/3X7W2XmD8DbSo9Pn72DbKsBFYw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.5.1", - "@types/node": "*", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "graceful-fs": "^4.2.11", - "picomatch": "^4.0.3" + "detect-newline": "^3.1.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-validate": { - "version": "30.5.1", - "integrity": "sha512-i/buJ56wTpxihE93hQYNMfdOThS87+HGvLZzZJmU4xggPcdTYQq051iwALLCHp3q+SkCGH+EFejQZz5EbBSkkg==", + "node_modules/jest-each": { + "version": "30.5.2", + "integrity": "sha512-GRrW+7fmmgmkvyfEBqc1QFdWnF1Ex5yG6Y6AWL4TTxnkWlKIT98dsKh3P3UuWm6v4XsQwVE6DEzXIPMzWJ3ZbA==", "dev": true, "license": "MIT", "dependencies": { "@jest/get-type": "30.5.0", "@jest/types": "30.5.1", - "camelcase": "^6.3.0", "chalk": "^4.1.2", - "leven": "^3.1.0", + "jest-regex-util": "30.5.0", + "jest-util": "30.5.1", "pretty-format": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "node_modules/jest-environment-jsdom": { + "version": "30.5.2", + "integrity": "sha512-6NOZjEP+WNi4WXjbxb/1ROnfaMxn6Dhqu6uSC22o0H28z+uppejs/jZ9KCVoLepqqoCjcNZujtzQqC9jSV15jg==", "dev": true, "license": "MIT", + "dependencies": { + "@jest/environment": "30.5.2", + "@jest/environment-jsdom-abstract": "30.5.2", + "@types/jsdom": "^21.1.7", + "jsdom": "^26.1.0" + }, "engines": { - "node": ">=10" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } } }, - "node_modules/jest-watcher": { - "version": "30.5.2", - "integrity": "sha512-rqRgg4R11GA9m1UsEZwQeixHsgZReCn6TOHAHtIH33yXrIvoX0OGPA20Yzzjkz7X40fZvcRSxEpQppYPcWzmcQ==", + "node_modules/jest-environment-jsdom/node_modules/data-urls": { + "version": "5.0.0", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "30.5.2", - "@jest/types": "30.5.1", - "@types/node": "*", - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", - "emittery": "^0.13.1", - "jest-util": "30.5.1", - "string-length": "^4.0.2" + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18" } }, - "node_modules/jest-worker": { - "version": "30.5.1", - "integrity": "sha512-Cbxh5v7AoLuFRmFJSM4/aHdQ68rjXvUWr716EE0Dh3I7T+T/3FgFKhOERGXHcU2Meftq9+9zxPM3TSNyI9D+HA==", + "node_modules/jest-environment-jsdom/node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", "dev": true, "license": "MIT", "dependencies": { - "@types/node": "*", - "@ungap/structured-clone": "^1.3.0", - "jest-util": "30.5.1", - "merge-stream": "^2.0.0", - "supports-color": "^8.1.1" + "whatwg-encoding": "^3.1.1" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18" } }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/jest-environment-jsdom/node_modules/jsdom": { + "version": "26.1.0", + "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", "dev": true, "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "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": ">=10" + "node": ">=18" }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } } }, - "node_modules/jiti": { - "version": "2.7.0", - "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "node_modules/jest-environment-jsdom/node_modules/tldts": { + "version": "6.1.86", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", "dev": true, "license": "MIT", - "peer": true, + "dependencies": { + "tldts-core": "^6.1.86" + }, "bin": { - "jiti": "lib/jiti-cli.mjs" + "tldts": "bin/cli.js" } }, - "node_modules/jose": { - "version": "6.2.12", - "integrity": "sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==", + "node_modules/jest-environment-jsdom/node_modules/tldts-core": { + "version": "6.1.86", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" - } + "license": "MIT" }, - "node_modules/js-tokens": { - "version": "4.0.0", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "node_modules/jest-environment-jsdom/node_modules/tough-cookie": { + "version": "5.1.2", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", "dev": true, - "license": "MIT" + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } }, - "node_modules/js-yaml": { - "version": "4.3.2", - "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "node_modules/jest-environment-jsdom/node_modules/tr46": { + "version": "5.1.1", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/puzrin" - }, - { - "type": "github", - "url": "https://github.com/sponsors/nodeca" - } - ], "license": "MIT", "dependencies": { - "argparse": "^2.0.1" + "punycode": "^2.3.1" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": ">=18" } }, - "node_modules/jsdom": { - "version": "30.1.2", - "integrity": "sha512-0FFE/jE1rppmVfUrJUgxqXjcwolZYIGtAgj1pTussMhNZxIxi7W/PvfWaMNroGi2B36X1IKHbexpZ9DhkoOPiQ==", + "node_modules/jest-environment-jsdom/node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@asamuzakjp/css-color": "^7.1.3", - "@asamuzakjp/dom-selector": "^9.2.4", - "@bramus/specificity": "^2.4.2", - "@csstools/css-syntax-patches-for-csstree": "^1.1.15", - "@exodus/bytes": "^1.16.0", - "css-tree": "^3.2.1", - "data-urls": "^8.0.0", - "decimal.js": "^10.6.0", - "html-encoding-sniffer": "^7.0.0", - "is-potential-custom-element-name": "^1.0.1", - "lru-cache": "^11.5.3", - "parse5": "^8.0.1", - "saxes": "^6.0.0", - "tough-cookie": "^6.0.2", - "undici": "^8.11.2", - "w3c-xmlserializer": "^6.0.0", - "webidl-conversions": "^8.0.1", - "whatwg-mimetype": "^5.0.0", - "whatwg-url": "^17.2.0", "xml-name-validator": "^5.0.0" }, "engines": { - "node": "^22.22.2 || ^24.15.0 || >=26.0.0" - }, - "peerDependencies": { - "canvas": "^3.2.3" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } + "node": ">=18" } }, - "node_modules/jsdom/node_modules/entities": { - "version": "8.1.0", - "integrity": "sha512-kxL7msIffSuh9aaFAMD7rxAIuTRMAHMeBtgHW2yUdWw732ZNh4MehkF2gdjvtdmikkaIP9bFDDJOPlsvm7avrA==", + "node_modules/jest-environment-jsdom/node_modules/webidl-conversions": { + "version": "7.0.0", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "engines": { - "node": ">=20.19.0" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "node": ">=12" } }, - "node_modules/jsdom/node_modules/lru-cache": { - "version": "11.5.3", - "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", + "node_modules/jest-environment-jsdom/node_modules/whatwg-mimetype": { + "version": "4.0.0", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", "dev": true, - "license": "BlueOak-1.0.0", - "peer": true, + "license": "MIT", "engines": { - "node": "20 || >=22" + "node": ">=18" } }, - "node_modules/jsdom/node_modules/parse5": { - "version": "8.0.1", - "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", + "node_modules/jest-environment-jsdom/node_modules/whatwg-url": { + "version": "14.2.0", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "entities": "^8.0.0" + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" + "engines": { + "node": ">=18" } }, - "node_modules/jsdom/node_modules/undici": { - "version": "8.12.1", - "integrity": "sha512-usz3OVQBeh1LxNn0DzU0voLXMgn5CU/WUdpBsY6Ti5afNao+vTkmn4H/NxoXolc10cOqe2+YIG0Hspq2rsl64w==", + "node_modules/jest-environment-node": { + "version": "30.5.2", + "integrity": "sha512-Ciz4KgTGIbojxSKpuFImgWJj5EyqfE8xMq9NxWmOXnQywHHS03H9Mv+vaCB29J5pr3JBrY27WbRV18rBdjh8Dg==", "dev": true, "license": "MIT", - "peer": true, + "dependencies": { + "@jest/environment": "30.5.2", + "@jest/fake-timers": "30.5.2", + "@jest/types": "30.5.1", + "@types/node": "*", + "jest-mock": "30.5.2", + "jest-util": "30.5.1", + "jest-validate": "30.5.1" + }, "engines": { - "node": ">=22.19.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jsesc": { - "version": "3.1.0", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "node_modules/jest-haste-map": { + "version": "30.5.1", + "integrity": "sha512-VIFgt67jW480YDxKfEv9IYQKrFpYt7bOCMn3VsnjK7AK9qo7p6acpnA1DHwsVOULE3dTaYew/6JaTD/d0VDHoQ==", "dev": true, "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" + "dependencies": { + "@jest/types": "30.5.1", + "@parcel/watcher": "^2.6.0", + "@types/node": "*", + "anymatch": "^3.1.3", + "fb-watchman": "^2.0.2", + "fdir": "^6.5.0", + "graceful-fs": "^4.2.11", + "jest-regex-util": "30.5.0", + "jest-util": "30.5.1", + "jest-worker": "30.5.1", + "picomatch": "^4.0.3" }, "engines": { - "node": ">=6" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/json-buffer": { - "version": "3.0.1", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-parse-even-better-errors": { - "version": "5.0.0", - "integrity": "sha512-ZF1nxZ28VhQouRWhUcVlUIN3qwSgPuswK05s/HIaoetAoE/9tngVmCHjSxmSQPav1nd+lPtTL0YZ/2AFdR/iYQ==", + "node_modules/jest-leak-detector": { + "version": "30.5.1", + "integrity": "sha512-gt4GT2aWgEoCNTcBe4rqS74xTIUJxi+UD9SNSu9aOk5LmTEd6fS5KSTmAKAfitCCktQHNN+upUuL6EkBfFbMDQ==", "dev": true, "license": "MIT", + "dependencies": { + "@jest/get-type": "30.5.0", + "pretty-format": "30.5.1" + }, "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-typed": { - "version": "8.0.2", - "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } }, - "node_modules/json5": { - "version": "2.2.3", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "node_modules/jest-matcher-utils": { + "version": "30.5.2", + "integrity": "sha512-lFkB365PTIHOhPzwrb9T6gvlDnPpOZ7/c3ewOKKB7bzztqjVrtlyRL2MkyfJXpyY2u2SKw001JKrTx14fBUFcQ==", "dev": true, "license": "MIT", - "bin": { - "json5": "lib/cli.js" + "dependencies": { + "@jest/get-type": "30.5.0", + "chalk": "^4.1.2", + "jest-diff": "30.5.2", + "pretty-format": "30.5.1" }, "engines": { - "node": ">=6" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jsonc-parser": { - "version": "3.3.1", - "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/jsonparse": { - "version": "1.3.1", - "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "node_modules/jest-message-util": { + "version": "30.5.1", + "integrity": "sha512-UdQlLdd9wL/Ys7xRErckqwD6wPlSZYueosSWuHc1r2ztGLwlgPvtSJq2+BPEgaEY13WLvfFbmhTj8pba0Sd1jg==", "dev": true, - "engines": [ - "node >= 0.2.0" - ], - "license": "MIT" + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.5.1", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "jest-util": "30.5.1", + "picomatch": "^4.0.3", + "pretty-format": "30.5.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } }, - "node_modules/karma-source-map-support": { - "version": "1.4.0", - "integrity": "sha512-RsBECncGO17KAoJCYXjv+ckIz+Ii9NCi+9enk+rq6XC81ezYkb4/RHE6CTXdA7IOJqoF3wcaLfVG0CPmE5ca6A==", + "node_modules/jest-mock": { + "version": "30.5.2", + "integrity": "sha512-KTHb37Fhj8xY8qPFvTFaFJHcdsumQp+ExsUlgoX232Agmc706gagBs53+CYvulV7MVQo9AgXOUxsqzzVBoJlrA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "source-map-support": "^0.5.5" + "@jest/expect-utils": "30.5.2", + "@jest/types": "30.5.1", + "@types/node": "*", + "jest-util": "30.5.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/keyv": { - "version": "4.5.4", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "node_modules/jest-preset-angular": { + "version": "17.0.3", + "integrity": "sha512-ufDKlvNgT5eMq72OCVkac+9ibOkgL2Ee7orUhlQ1IdTLqn76eniNDz8oZ9vE3Biwh5zSitdSojZvB/Tf+KaWsQ==", "dev": true, "license": "MIT", "dependencies": { - "json-buffer": "3.0.1" + "@jest/environment-jsdom-abstract": "^30.5.1", + "bs-logger": "^0.2.6", + "esbuild-wasm": ">=0.28.0", + "jest-util": "^30.5.1", + "pretty-format": "^30.5.1", + "ts-jest": "^29.4.12" + }, + "engines": { + "node": "^20.11.1 || >=22.0.0" + }, + "optionalDependencies": { + "esbuild": ">=0.28.0" + }, + "peerDependencies": { + "@angular/compiler-cli": ">=20.0.0 <23.0.0", + "@angular/core": ">=20.0.0 <23.0.0", + "@angular/platform-browser": ">=20.0.0 <23.0.0", + "jest": "^30.0.0", + "jsdom": ">=26.0.0", + "typescript": ">=5.8" } }, - "node_modules/kind-of": { - "version": "6.0.3", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "node_modules/jest-regex-util": { + "version": "30.5.0", + "integrity": "sha512-Mg0WK7A6xRHLSA1udJ8y9f3lM0uUhFTBnLKzwPmqB9AylvpleJ6BLemR8K9dK27DY+cesDryoA7yLZCAHsPG1A==", "dev": true, "license": "MIT", - "peer": true, "engines": { - "node": ">=0.10.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/launch-editor": { - "version": "2.14.2", - "integrity": "sha512-NHJEb30MGBCkmZXLZwz25t37QR4aY03ZzPYUJXOJaOa92uWwExI9SBQUuIznBYc1xkD7cJNGX7oc3Nl3sq2kQQ==", + "node_modules/jest-resolve": { + "version": "30.5.1", + "integrity": "sha512-wprhLejRtwN6h8ZgaqC0eYjGJ1uMdGPT/+b3eFncbG4NWuuP9QL+vWwRinu9waw7hkOLcpMJGVJAMgBwsPssMQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "picocolors": "^1.1.1", - "shell-quote": "^1.10.0" + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.5.1", + "jest-util": "30.5.1", + "jest-validate": "30.5.1", + "slash": "^3.0.0", + "unrs-resolver": "^1.12.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/less": { - "version": "4.9.0", - "integrity": "sha512-umRhrCH7fCi8Uj2RcwKjJdvUORTjeWqkdKx0LbcZvjIwsAVsnIAGcxHaqowPeBFBjQuWOeC/bve0AlpFzF/+SQ==", + "node_modules/jest-resolve-dependencies": { + "version": "30.5.2", + "integrity": "sha512-GMI0DP5enX9Z2qW2zFy1bOkrCfWAOPRJQ7qHt0pdfrxBPsgWxglMPrRSK2TX/wRWYxcPRSLFg3Z88qPOzHx7FQ==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "copy-anything": "^3.0.5", - "parse-node-version": "^1.0.1" - }, - "bin": { - "lessc": "bin/lessc" + "jest-regex-util": "30.5.0", + "jest-snapshot": "30.5.2" }, "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "errno": "^0.1.1", - "graceful-fs": "^4.1.2", - "make-dir": "^5.1.0", - "mime": "^1.4.1", - "needle": "^3.1.0", - "probe-image-size": "^7.2.3", - "source-map": "~0.6.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/less-loader": { - "version": "12.3.1", - "integrity": "sha512-JZZmG7gMzoDP3VGeEG8Sh6FW5wygB5jYL7Wp29FFihuRTsIBacqO3LbRPr2yStYD11riVf13selLm/CPFRDBRQ==", + "node_modules/jest-runner": { + "version": "30.5.2", + "integrity": "sha512-/B6px7hiiNhVSRwuv9AGn0nawYvwsHeQZ3ItTd5Gp2diFd0EOKaWVeWqcbw3L88vx1GfpCKjQkxWItZCTl11Rw==", "dev": true, "license": "MIT", - "peer": true, - "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "@rspack/core": "0.x || ^1.0.0 || ^2.0.0-0", - "less": "^3.5.0 || ^4.0.0", - "webpack": "^5.0.0" + "dependencies": { + "@jest/console": "30.5.2", + "@jest/environment": "30.5.2", + "@jest/source-map": "30.5.2", + "@jest/test-result": "30.5.2", + "@jest/transform": "30.5.2", + "@jest/types": "30.5.1", + "@types/node": "*", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-docblock": "30.5.0", + "jest-environment-node": "30.5.2", + "jest-haste-map": "30.5.1", + "jest-leak-detector": "30.5.1", + "jest-message-util": "30.5.1", + "jest-resolve": "30.5.1", + "jest-runtime": "30.5.2", + "jest-util": "30.5.1", + "jest-watcher": "30.5.2", + "jest-worker": "30.5.1", + "p-limit": "^3.1.0" }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true - }, - "webpack": { - "optional": true - } + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/less/node_modules/make-dir": { - "version": "5.1.0", - "integrity": "sha512-IfpFq6UM39dUNiphpA6uDezNx/AvWyhwfICWPR3t1VspkgkMZrL+Rk1RbN1bx+aeNYwOrqGJgEgV3yotk+ZUVw==", + "node_modules/jest-runtime": { + "version": "30.5.2", + "integrity": "sha512-0paUAHBlheai10A3XSy9Xh+YBG0IROy+/qzcDPzQ/nbbXrznQzeWy5qniICWK6d3W3scaRQCVAbXdcK0ibBq4w==", "dev": true, "license": "MIT", - "optional": true, + "dependencies": { + "@jest/environment": "30.5.2", + "@jest/fake-timers": "30.5.2", + "@jest/globals": "30.5.2", + "@jest/source-map": "30.5.2", + "@jest/test-result": "30.5.2", + "@jest/transform": "30.5.2", + "@jest/types": "30.5.1", + "@types/node": "*", + "chalk": "^4.1.2", + "cjs-module-lexer": "^2.2.0", + "collect-v8-coverage": "^1.0.2", + "es-module-lexer": "^2.1.0", + "glob": "^13.0.6", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.5.1", + "jest-message-util": "30.5.1", + "jest-mock": "30.5.2", + "jest-regex-util": "30.5.0", + "jest-resolve": "30.5.1", + "jest-snapshot": "30.5.2", + "jest-util": "30.5.1", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, "engines": { - "node": ">=18" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "30.5.2", + "integrity": "sha512-jKIdes25AnAH73dOCi2qE6J6vHJ6L2vyBS11M89kT50DVJcLTf2AOjvcc+2vVaN8lUbY5v2R83aqXo3Ro9fqYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@babel/generator": "^7.27.5", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1", + "@babel/types": "^7.27.3", + "@jest/expect-utils": "30.5.2", + "@jest/get-type": "30.5.0", + "@jest/snapshot-utils": "30.5.1", + "@jest/transform": "30.5.2", + "@jest/types": "30.5.1", + "babel-preset-current-node-syntax": "^1.2.0", + "chalk": "^4.1.2", + "expect": "30.5.2", + "graceful-fs": "^4.2.11", + "jest-diff": "30.5.2", + "jest-matcher-utils": "30.5.2", + "jest-message-util": "30.5.1", + "jest-util": "30.5.1", + "pretty-format": "30.5.1", + "semver": "^7.7.2", + "synckit": "^0.11.8" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/less/node_modules/source-map": { - "version": "0.6.1", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "optional": true, "engines": { - "node": ">=0.10.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/leven": { - "version": "3.1.0", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "node_modules/jest-snapshot/node_modules/synckit": { + "version": "0.11.13", + "integrity": "sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==", "dev": true, "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.3.6" + }, "engines": { - "node": ">=6" + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" } }, - "node_modules/levn": { - "version": "0.4.1", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "node_modules/jest-util": { + "version": "30.5.1", + "integrity": "sha512-yKuxmNy2rSbTXw+3SIPanJo+nV4/BS1p26v44IYBFMsswSQySfMMcPHErnOncda7i9HEz0q605rIhSTBVgrZTg==", "dev": true, "license": "MIT", "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" + "@jest/types": "30.5.1", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" }, "engines": { - "node": ">= 0.8.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/license-webpack-plugin": { - "version": "4.0.2", - "integrity": "sha512-771TFWFD70G1wLTC4oU2Cw4qvtmNrIw+wRvBtn+okgHl7slJVi7zfNcdmqDL72BojM30VNJ2UHylr1o77U37Jw==", + "node_modules/jest-validate": { + "version": "30.5.1", + "integrity": "sha512-i/buJ56wTpxihE93hQYNMfdOThS87+HGvLZzZJmU4xggPcdTYQq051iwALLCHp3q+SkCGH+EFejQZz5EbBSkkg==", "dev": true, - "license": "ISC", - "peer": true, + "license": "MIT", "dependencies": { - "webpack-sources": "^3.0.0" + "@jest/get-type": "30.5.0", + "@jest/types": "30.5.1", + "camelcase": "^6.3.0", + "chalk": "^4.1.2", + "leven": "^3.1.0", + "pretty-format": "30.5.1" }, - "peerDependenciesMeta": { - "webpack": { - "optional": true - }, - "webpack-sources": { - "optional": true - } + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/listr2": { - "version": "9.0.5", - "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", + "node_modules/jest-watcher": { + "version": "30.5.2", + "integrity": "sha512-rqRgg4R11GA9m1UsEZwQeixHsgZReCn6TOHAHtIH33yXrIvoX0OGPA20Yzzjkz7X40fZvcRSxEpQppYPcWzmcQ==", "dev": true, "license": "MIT", "dependencies": { - "cli-truncate": "^5.0.0", - "colorette": "^2.0.20", - "eventemitter3": "^5.0.1", - "log-update": "^6.1.0", - "rfdc": "^1.4.1", - "wrap-ansi": "^9.0.0" + "@jest/test-result": "30.5.2", + "@jest/types": "30.5.1", + "@types/node": "*", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "jest-util": "30.5.1", + "string-length": "^4.0.2" }, "engines": { - "node": ">=20.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/listr2/node_modules/ansi-regex": { - "version": "6.4.0", - "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", + "node_modules/jest-worker": { + "version": "30.5.1", + "integrity": "sha512-Cbxh5v7AoLuFRmFJSM4/aHdQ68rjXvUWr716EE0Dh3I7T+T/3FgFKhOERGXHcU2Meftq9+9zxPM3TSNyI9D+HA==", "dev": true, "license": "MIT", - "engines": { - "node": ">=12" + "dependencies": { + "@types/node": "*", + "@ungap/structured-clone": "^1.3.0", + "jest-util": "30.5.1", + "merge-stream": "^2.0.0", + "supports-color": "^8.1.1" }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/listr2/node_modules/ansi-styles": { - "version": "6.2.3", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, "engines": { - "node": ">=12" + "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/listr2/node_modules/eventemitter3": { - "version": "5.0.4", - "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "node_modules/jiti": { + "version": "2.7.0", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true, + "bin": { + "jiti": "lib/jiti-cli.mjs" + } }, - "node_modules/listr2/node_modules/string-width": { - "version": "7.2.0", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "node_modules/jose": { + "version": "6.2.12", + "integrity": "sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==", "dev": true, "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/panva" } }, - "node_modules/listr2/node_modules/strip-ansi": { - "version": "7.2.0", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "node_modules/js-tokens": { + "version": "4.0.0", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.2", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" + "argparse": "^2.0.1" }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/listr2/node_modules/wrap-ansi": { - "version": "9.0.2", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "node_modules/jsdom": { + "version": "30.1.2", + "integrity": "sha512-0FFE/jE1rppmVfUrJUgxqXjcwolZYIGtAgj1pTussMhNZxIxi7W/PvfWaMNroGi2B36X1IKHbexpZ9DhkoOPiQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" + "@asamuzakjp/css-color": "^7.1.3", + "@asamuzakjp/dom-selector": "^9.2.4", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.15", + "@exodus/bytes": "^1.16.0", + "css-tree": "^3.2.1", + "data-urls": "^8.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^7.0.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.5.3", + "parse5": "^8.0.1", + "saxes": "^6.0.0", + "tough-cookie": "^6.0.2", + "undici": "^8.11.2", + "w3c-xmlserializer": "^6.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^17.2.0", + "xml-name-validator": "^5.0.0" }, "engines": { - "node": ">=18" + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "peerDependencies": { + "canvas": "^3.2.3" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } } }, - "node_modules/lmdb": { - "version": "3.4.3", - "integrity": "sha512-GWV1kVi6uhrXWqe+3NXWO73OYe8fto6q8JMo0HOpk1vf8nEyFWgo4CSNJpIFzsOxOrysVUlcO48qRbQfmKd1gA==", + "node_modules/jsdom/node_modules/entities": { + "version": "8.1.0", + "integrity": "sha512-kxL7msIffSuh9aaFAMD7rxAIuTRMAHMeBtgHW2yUdWw732ZNh4MehkF2gdjvtdmikkaIP9bFDDJOPlsvm7avrA==", "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "dependencies": { - "msgpackr": "^1.11.2", - "node-addon-api": "^6.1.0", - "node-gyp-build-optional-packages": "5.2.2", - "ordered-binary": "^1.5.3", - "weak-lru-cache": "^1.2.2" - }, - "bin": { - "download-lmdb-prebuilds": "bin/download-prebuilds.js" + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=20.19.0" }, - "optionalDependencies": { - "@lmdb/lmdb-darwin-arm64": "3.4.3", - "@lmdb/lmdb-darwin-x64": "3.4.3", - "@lmdb/lmdb-linux-arm": "3.4.3", - "@lmdb/lmdb-linux-arm64": "3.4.3", - "@lmdb/lmdb-linux-x64": "3.4.3", - "@lmdb/lmdb-win32-arm64": "3.4.3", - "@lmdb/lmdb-win32-x64": "3.4.3" + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/lmdb/node_modules/node-addon-api": { - "version": "6.1.0", - "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", + "node_modules/jsdom/node_modules/lru-cache": { + "version": "11.5.3", + "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", "dev": true, - "license": "MIT", - "optional": true + "license": "BlueOak-1.0.0", + "peer": true, + "engines": { + "node": "20 || >=22" + } }, - "node_modules/loader-runner": { - "version": "4.3.2", - "integrity": "sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w==", + "node_modules/jsdom/node_modules/parse5": { + "version": "8.0.1", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", "dev": true, "license": "MIT", "peer": true, - "engines": { - "node": ">=6.11.5" + "dependencies": { + "entities": "^8.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" + "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/loader-utils": { - "version": "3.3.1", - "integrity": "sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==", + "node_modules/jsdom/node_modules/undici": { + "version": "8.12.1", + "integrity": "sha512-usz3OVQBeh1LxNn0DzU0voLXMgn5CU/WUdpBsY6Ti5afNao+vTkmn4H/NxoXolc10cOqe2+YIG0Hspq2rsl64w==", "dev": true, "license": "MIT", "peer": true, "engines": { - "node": ">= 12.13.0" + "node": ">=22.19.0" } }, - "node_modules/locate-path": { - "version": "6.0.0", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "node_modules/jsesc": { + "version": "3.1.0", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "dev": true, "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" + "bin": { + "jsesc": "bin/jsesc" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" } }, - "node_modules/lodash": { - "version": "4.18.1", - "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "node_modules/json-buffer": { + "version": "3.0.1", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true, "license": "MIT" }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "node_modules/json-parse-even-better-errors": { + "version": "5.0.0", + "integrity": "sha512-ZF1nxZ28VhQouRWhUcVlUIN3qwSgPuswK05s/HIaoetAoE/9tngVmCHjSxmSQPav1nd+lPtTL0YZ/2AFdR/iYQ==", "dev": true, "license": "MIT", - "peer": true + "engines": { + "node": "^20.17.0 || >=22.9.0" + } }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true, "license": "MIT" }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "node_modules/json-schema-typed": { + "version": "8.0.2", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true, "license": "MIT" }, - "node_modules/log-symbols": { - "version": "7.0.1", - "integrity": "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==", + "node_modules/json5": { + "version": "2.2.3", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "license": "MIT", - "dependencies": { - "is-unicode-supported": "^2.0.0", - "yoctocolors": "^2.1.1" + "bin": { + "json5": "lib/cli.js" }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" } }, - "node_modules/log-update": { - "version": "6.1.0", - "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "node_modules/jsonc-parser": { + "version": "3.3.1", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", "dev": true, - "license": "MIT", - "dependencies": { - "ansi-escapes": "^7.0.0", - "cli-cursor": "^5.0.0", - "slice-ansi": "^7.1.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "license": "MIT" }, - "node_modules/log-update/node_modules/ansi-escapes": { - "version": "7.3.0", - "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "node_modules/jsonparse": { + "version": "1.3.1", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" + ], + "license": "MIT" + }, + "node_modules/karma-source-map-support": { + "version": "1.4.0", + "integrity": "sha512-RsBECncGO17KAoJCYXjv+ckIz+Ii9NCi+9enk+rq6XC81ezYkb4/RHE6CTXdA7IOJqoF3wcaLfVG0CPmE5ca6A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "environment": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "source-map-support": "^0.5.5" } }, - "node_modules/log-update/node_modules/ansi-regex": { - "version": "6.4.0", - "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", + "node_modules/keyv": { + "version": "4.5.4", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "dependencies": { + "json-buffer": "3.0.1" } }, - "node_modules/log-update/node_modules/ansi-styles": { - "version": "6.2.3", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "node_modules/kind-of": { + "version": "6.0.3", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true, "license": "MIT", + "peer": true, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=0.10.0" } }, - "node_modules/log-update/node_modules/slice-ansi": { - "version": "7.1.2", - "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "node_modules/launch-editor": { + "version": "2.14.2", + "integrity": "sha512-NHJEb30MGBCkmZXLZwz25t37QR4aY03ZzPYUJXOJaOa92uWwExI9SBQUuIznBYc1xkD7cJNGX7oc3Nl3sq2kQQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "ansi-styles": "^6.2.1", - "is-fullwidth-code-point": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" + "picocolors": "^1.1.1", + "shell-quote": "^1.10.0" } }, - "node_modules/log-update/node_modules/string-width": { - "version": "7.2.0", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "node_modules/less": { + "version": "4.9.0", + "integrity": "sha512-umRhrCH7fCi8Uj2RcwKjJdvUORTjeWqkdKx0LbcZvjIwsAVsnIAGcxHaqowPeBFBjQuWOeC/bve0AlpFzF/+SQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" + "copy-anything": "^3.0.5", + "parse-node-version": "^1.0.1" + }, + "bin": { + "lessc": "bin/lessc" }, "engines": { "node": ">=18" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "optionalDependencies": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "make-dir": "^5.1.0", + "mime": "^1.4.1", + "needle": "^3.1.0", + "probe-image-size": "^7.2.3", + "source-map": "~0.6.0" } }, - "node_modules/log-update/node_modules/strip-ansi": { - "version": "7.2.0", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "node_modules/less-loader": { + "version": "12.3.1", + "integrity": "sha512-JZZmG7gMzoDP3VGeEG8Sh6FW5wygB5jYL7Wp29FFihuRTsIBacqO3LbRPr2yStYD11riVf13selLm/CPFRDBRQ==", "dev": true, "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, + "peer": true, "engines": { - "node": ">=12" + "node": ">= 18.12.0" }, "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || ^1.0.0 || ^2.0.0-0", + "less": "^3.5.0 || ^4.0.0", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } } }, - "node_modules/log-update/node_modules/wrap-ansi": { - "version": "9.0.2", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "node_modules/less/node_modules/make-dir": { + "version": "5.1.0", + "integrity": "sha512-IfpFq6UM39dUNiphpA6uDezNx/AvWyhwfICWPR3t1VspkgkMZrL+Rk1RbN1bx+aeNYwOrqGJgEgV3yotk+ZUVw==", "dev": true, "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, + "optional": true, "engines": { "node": ">=18" }, "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lru-cache": { - "version": "5.1.1", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "node_modules/less/node_modules/source-map": { + "version": "0.6.1", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" } }, - "node_modules/magic-string": { - "version": "0.30.19", - "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==", + "node_modules/leven": { + "version": "3.1.0", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" + "engines": { + "node": ">=6" } }, - "node_modules/make-dir": { - "version": "4.0.0", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "node_modules/levn": { + "version": "0.4.1", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "license": "MIT", "dependencies": { - "semver": "^7.5.3" + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.8.0" } }, - "node_modules/make-error": { - "version": "1.3.6", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true, - "license": "ISC" - }, - "node_modules/make-fetch-happen": { - "version": "15.0.6", - "integrity": "sha512-Je0fLJ0F5atA7F+eIlLzk+Wkcl57JDf4kf+EW8xiP5E31xOQxkIxTbgf1Oi1Lw9tRI9UEMRdI5Vz2xTzoNU1Jw==", + "node_modules/license-webpack-plugin": { + "version": "4.0.2", + "integrity": "sha512-771TFWFD70G1wLTC4oU2Cw4qvtmNrIw+wRvBtn+okgHl7slJVi7zfNcdmqDL72BojM30VNJ2UHylr1o77U37Jw==", "dev": true, "license": "ISC", + "peer": true, "dependencies": { - "@gar/promise-retry": "^1.0.0", - "@npmcli/agent": "^4.0.0", - "@npmcli/redact": "^4.0.0", - "cacache": "^20.0.1", - "http-cache-semantics": "^4.1.1", - "minipass": "^7.0.2", - "minipass-fetch": "^5.0.0", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^1.0.0", - "proc-log": "^6.0.0", - "ssri": "^13.0.0" + "webpack-sources": "^3.0.0" }, - "engines": { - "node": "^20.17.0 || >=22.9.0" + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-sources": { + "optional": true + } } }, - "node_modules/make-fetch-happen/node_modules/proc-log": { - "version": "6.1.0", - "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "node_modules/lines-and-columns": { + "version": "1.2.4", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } + "license": "MIT" }, - "node_modules/make-fetch-happen/node_modules/ssri": { - "version": "13.0.1", - "integrity": "sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ==", + "node_modules/listr2": { + "version": "9.0.5", + "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "minipass": "^7.0.3" + "cli-truncate": "^5.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" }, "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": ">=20.0.0" } }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "node_modules/listr2/node_modules/ansi-regex": { + "version": "6.4.0", + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/mdn-data": { - "version": "2.27.1", - "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", - "dev": true, - "license": "CC0-1.0", - "peer": true - }, - "node_modules/media-typer": { - "version": "1.1.1", - "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", + "node_modules/listr2/node_modules/ansi-styles": { + "version": "6.2.3", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">=12" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/memfs": { - "version": "4.80.0", - "integrity": "sha512-N70W2isi44XdGn7uPo9NJ4zRukcdkwSFHlnYx42SWOH/+hzqNc8P7+tVoA2up4z7faHvNi1RSNCFjxoYQ71VPQ==", + "node_modules/listr2/node_modules/eventemitter3": { + "version": "5.0.4", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@jsonjoy.com/fs-core": "4.80.0", - "@jsonjoy.com/fs-fsa": "4.80.0", - "@jsonjoy.com/fs-node": "4.80.0", - "@jsonjoy.com/fs-node-builtins": "4.80.0", - "@jsonjoy.com/fs-node-to-fsa": "4.80.0", - "@jsonjoy.com/fs-node-utils": "4.80.0", - "@jsonjoy.com/fs-print": "4.80.0", - "@jsonjoy.com/fs-snapshot": "4.80.0", - "@jsonjoy.com/json-pack": "^1.11.0", - "@jsonjoy.com/util": "^1.9.0", - "glob-to-regex.js": "^1.3.1", - "thingies": "^2.5.0", - "tree-dump": "^1.0.3", - "tslib": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - } + "license": "MIT" }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "node_modules/listr2/node_modules/string-width": { + "version": "7.2.0", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, "engines": { "node": ">=18" }, @@ -17469,3211 +15806,3129 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/merge-stream": { - "version": "2.0.0", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, - "node_modules/methods": { - "version": "1.1.2", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "node_modules/listr2/node_modules/strip-ansi": { + "version": "7.2.0", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" + "ansi-regex": "^6.2.2" }, "engines": { - "node": ">=8.6" - } - }, - "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.2", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8.6" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/mime": { - "version": "1.6.0", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "9.0.2", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "dev": true, "license": "MIT", - "bin": { - "mime": "cli.js" + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.54.0", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/mime-types": { - "version": "3.0.2", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "node_modules/lmdb": { + "version": "3.5.1", + "integrity": "sha512-NYHA0MRPjvNX+vSw8Xxg6FLKxzAG+e7Pt8RqAQA/EehzHVXq9SxDqJIN3JL1hK0dweb884y8kIh6rkWvPyg9Wg==", "dev": true, + "hasInstallScript": true, "license": "MIT", + "optional": true, "dependencies": { - "mime-db": "^1.54.0" + "@harperfast/extended-iterable": "^1.0.3", + "msgpackr": "^1.11.2", + "node-addon-api": "^6.1.0", + "node-gyp-build-optional-packages": "5.2.2", + "ordered-binary": "^1.5.3", + "weak-lru-cache": "^1.2.2" }, - "engines": { - "node": ">=18" + "bin": { + "download-lmdb-prebuilds": "bin/download-prebuilds.js" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "optionalDependencies": { + "@lmdb/lmdb-darwin-arm64": "3.5.1", + "@lmdb/lmdb-darwin-x64": "3.5.1", + "@lmdb/lmdb-linux-arm": "3.5.1", + "@lmdb/lmdb-linux-arm64": "3.5.1", + "@lmdb/lmdb-linux-x64": "3.5.1", + "@lmdb/lmdb-win32-arm64": "3.5.1", + "@lmdb/lmdb-win32-x64": "3.5.1" } }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "node_modules/lmdb/node_modules/node-addon-api": { + "version": "6.1.0", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", "dev": true, "license": "MIT", - "engines": { - "node": ">=6" - } + "optional": true }, - "node_modules/mimic-function": { - "version": "5.0.1", - "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "node_modules/loader-runner": { + "version": "4.3.2", + "integrity": "sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w==", "dev": true, "license": "MIT", + "peer": true, "engines": { - "node": ">=18" + "node": ">=6.11.5" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "node_modules/mini-css-extract-plugin": { - "version": "2.10.0", - "integrity": "sha512-540P2c5dYnJlyJxTaSloliZexv8rji6rY8FhQN+WF/82iHQfA23j/xtJx97L+mXOML27EqksSek/g4eK7jaL3g==", + "node_modules/loader-utils": { + "version": "3.3.1", + "integrity": "sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==", "dev": true, "license": "MIT", "peer": true, - "dependencies": { - "schema-utils": "^4.0.0", - "tapable": "^2.2.1" - }, "engines": { "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" } }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true, - "license": "ISC", - "peer": true - }, - "node_modules/minimatch": { - "version": "10.2.6", - "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "node_modules/locate-path": { + "version": "6.0.0", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "MIT", "dependencies": { - "brace-expansion": "^5.0.8" + "p-locate": "^5.0.0" }, "engines": { - "node": "18 || 20 || >=22" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/minimist": { - "version": "1.2.8", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "node_modules/lodash": { + "version": "4.18.1", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.18.1", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "dev": true, "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "peer": true }, - "node_modules/minipass": { - "version": "7.1.3", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "node_modules/lodash.memoize": { + "version": "4.1.2", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=16 || 14 >=14.17" - } + "license": "MIT" }, - "node_modules/minipass-collect": { - "version": "2.0.1", - "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "node_modules/lodash.merge": { + "version": "4.6.2", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - } + "license": "MIT" }, - "node_modules/minipass-fetch": { - "version": "5.0.2", - "integrity": "sha512-2d0q2a8eCi2IRg/IGubCNRJoYbA1+YPXAzQVRFmB45gdGZafyivnZ5YSEfo3JikbjGxOdntGFvBQGqaSMXlAFQ==", + "node_modules/log-symbols": { + "version": "7.0.1", + "integrity": "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==", "dev": true, "license": "MIT", "dependencies": { - "minipass": "^7.0.3", - "minipass-sized": "^2.0.0", - "minizlib": "^3.0.1" + "is-unicode-supported": "^2.0.0", + "yoctocolors": "^2.1.1" }, "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": ">=18" }, - "optionalDependencies": { - "iconv-lite": "^0.7.2" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/minipass-flush": { - "version": "1.0.7", - "integrity": "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==", + "node_modules/log-update": { + "version": "6.1.0", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "MIT", "dependencies": { - "minipass": "^3.0.0" + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" }, "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-flush/node_modules/minipass": { - "version": "3.3.6", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" + "node": ">=18" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/minipass-flush/node_modules/yallist": { - "version": "4.0.0", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, - "node_modules/minipass-pipeline": { - "version": "1.2.4", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "node_modules/log-update/node_modules/ansi-escapes": { + "version": "7.3.0", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "minipass": "^3.0.0" + "environment": "^1.0.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-pipeline/node_modules/minipass": { - "version": "3.3.6", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" + "node": ">=18" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/minipass-pipeline/node_modules/yallist": { - "version": "4.0.0", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, - "node_modules/minipass-sized": { - "version": "2.0.0", - "integrity": "sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA==", + "node_modules/log-update/node_modules/ansi-regex": { + "version": "6.4.0", + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.1.2" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/minizlib": { - "version": "3.1.0", - "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.3", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, "engines": { - "node": ">= 18" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/mitt": { - "version": "3.0.1", - "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", - "dev": true, - "license": "MIT" - }, - "node_modules/mrmime": { - "version": "2.0.1", - "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.2", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", "dev": true, "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, "engines": { - "node": ">=10" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" } - }, - "node_modules/ms": { - "version": "2.1.3", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/msgpackr": { - "version": "1.12.1", - "integrity": "sha512-4EUH9tQHnMmEgzW/MdAP0KIfa1T9AF+htl0ffe2n5vb2EKn9y2co8ccpgWko6S52Jy1PQZKwRnx5/KkYjtd9MQ==", + }, + "node_modules/log-update/node_modules/string-width": { + "version": "7.2.0", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, "license": "MIT", - "optional": true, - "optionalDependencies": { - "msgpackr-extract": "^3.0.2" + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/msgpackr-extract": { - "version": "3.0.4", - "integrity": "sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw==", + "node_modules/log-update/node_modules/strip-ansi": { + "version": "7.2.0", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, - "hasInstallScript": true, "license": "MIT", - "optional": true, "dependencies": { - "node-gyp-build-optional-packages": "5.2.2" + "ansi-regex": "^6.2.2" }, - "bin": { - "download-msgpackr-prebuilds": "bin/download-prebuilds.js" + "engines": { + "node": ">=12" }, - "optionalDependencies": { - "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.4", - "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.4", - "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.4", - "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.4", - "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.4", - "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.4" + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/multicast-dns": { - "version": "7.2.5", - "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "9.0.2", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "dns-packet": "^5.2.2", - "thunky": "^1.0.2" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" }, - "bin": { - "multicast-dns": "cli.js" + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/mute-stream": { - "version": "2.0.0", - "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "node_modules/lru-cache": { + "version": "5.1.1", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" + "dependencies": { + "yallist": "^3.0.2" } }, - "node_modules/nanoid": { - "version": "3.3.20", - "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", + "node_modules/magic-string": { + "version": "0.30.21", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/napi-postinstall": { - "version": "0.3.4", - "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "node_modules/make-dir": { + "version": "4.0.0", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, "license": "MIT", - "bin": { - "napi-postinstall": "lib/cli.js" + "dependencies": { + "semver": "^7.5.3" }, "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + "node": ">=10" }, "funding": { - "url": "https://opencollective.com/napi-postinstall" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/natural-compare": { - "version": "1.4.0", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "node_modules/make-error": { + "version": "1.3.6", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true, - "license": "MIT" + "license": "ISC" }, - "node_modules/needle": { - "version": "3.5.0", - "integrity": "sha512-jaQyPKKk2YokHrEg+vFDYxXIHTCBgiZwSHOoVx/8V3GIBS8/VN6NdVRmg8q1ERtPkMvmOvebsgga4sAj5hls/w==", + "node_modules/make-fetch-happen": { + "version": "15.0.6", + "integrity": "sha512-Je0fLJ0F5atA7F+eIlLzk+Wkcl57JDf4kf+EW8xiP5E31xOQxkIxTbgf1Oi1Lw9tRI9UEMRdI5Vz2xTzoNU1Jw==", "dev": true, - "license": "MIT", - "optional": true, + "license": "ISC", "dependencies": { - "iconv-lite": "^0.6.3", - "sax": "^1.2.4" - }, - "bin": { - "needle": "bin/needle" + "@gar/promise-retry": "^1.0.0", + "@npmcli/agent": "^4.0.0", + "@npmcli/redact": "^4.0.0", + "cacache": "^20.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^5.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^6.0.0", + "ssri": "^13.0.0" }, "engines": { - "node": ">= 4.4.x" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/needle/node_modules/iconv-lite": { - "version": "0.6.3", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "node_modules/math-intrinsics": { + "version": "1.1.0", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "dev": true, "license": "MIT", - "optional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, - "node_modules/negotiator": { - "version": "1.1.0", - "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", + "node_modules/mdn-data": { + "version": "2.27.1", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0", + "peer": true + }, + "node_modules/media-typer": { + "version": "1.1.1", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", "dev": true, "license": "MIT", - "dependencies": { - "content-type": "^2.1.0" - }, "engines": { - "node": ">=18" + "node": ">= 0.8" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/express" } }, - "node_modules/negotiator/node_modules/content-type": { - "version": "2.1.0", - "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "node_modules/memfs": { + "version": "4.80.0", + "integrity": "sha512-N70W2isi44XdGn7uPo9NJ4zRukcdkwSFHlnYx42SWOH/+hzqNc8P7+tVoA2up4z7faHvNi1RSNCFjxoYQ71VPQ==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@jsonjoy.com/fs-core": "4.80.0", + "@jsonjoy.com/fs-fsa": "4.80.0", + "@jsonjoy.com/fs-node": "4.80.0", + "@jsonjoy.com/fs-node-builtins": "4.80.0", + "@jsonjoy.com/fs-node-to-fsa": "4.80.0", + "@jsonjoy.com/fs-node-utils": "4.80.0", + "@jsonjoy.com/fs-print": "4.80.0", + "@jsonjoy.com/fs-snapshot": "4.80.0", + "@jsonjoy.com/json-pack": "^1.11.0", + "@jsonjoy.com/util": "^1.9.0", + "glob-to-regex.js": "^1.3.1", + "thingies": "^2.5.0", + "tree-dump": "^1.0.3", + "tslib": "^2.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "type": "github", + "url": "https://github.com/sponsors/streamich" } }, - "node_modules/neo-async": { - "version": "2.6.2", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, - "license": "MIT" - }, - "node_modules/ng-packagr": { - "version": "21.0.1", - "integrity": "sha512-IZGxuF226GF0d8FOZIfPvHsyBl53PrDEg/IB2+CVamsm3r4+gUw3mBp27eygpowBpdVLG0Sm2IbUiH4aSspzyA==", + "node_modules/merge-descriptors": { + "version": "2.0.0", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "dev": true, "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.3.0", - "@rollup/plugin-json": "^6.1.0", - "@rollup/wasm-node": "^4.24.0", - "ajv": "^8.17.1", - "ansi-colors": "^4.1.3", - "browserslist": "^4.26.0", - "chokidar": "^4.0.1", - "commander": "^14.0.0", - "dependency-graph": "^1.0.0", - "esbuild": "^0.27.0", - "find-cache-directory": "^6.0.0", - "injection-js": "^2.4.0", - "jsonc-parser": "^3.3.1", - "less": "^4.2.0", - "ora": "^9.0.0", - "piscina": "^5.0.0", - "postcss": "^8.4.47", - "rollup-plugin-dts": "^6.2.0", - "rxjs": "^7.8.1", - "sass": "^1.81.0", - "tinyglobby": "^0.2.12" - }, - "bin": { - "ng-packagr": "src/cli/main.js" - }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - }, - "optionalDependencies": { - "rollup": "^4.24.0" - }, - "peerDependencies": { - "@angular/compiler-cli": "^21.0.0-next || ^21.0.0", - "tailwindcss": "^2.0.0 || ^3.0.0 || ^4.0.0", - "tslib": "^2.3.0", - "typescript": ">=5.9 <6.0" + "node": ">=18" }, - "peerDependenciesMeta": { - "tailwindcss": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ng-packagr/node_modules/@esbuild/aix-ppc64": { - "version": "0.27.7", - "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", - "cpu": [ - "ppc64" - ], + "node_modules/merge-stream": { + "version": "2.0.0", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/methods": { + "version": "1.1.2", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "aix" - ], + "peer": true, "engines": { - "node": ">=18" + "node": ">= 0.6" } }, - "node_modules/ng-packagr/node_modules/@esbuild/android-arm": { - "version": "0.27.7", - "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", - "cpu": [ - "arm" - ], + "node_modules/micromatch": { + "version": "4.0.8", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "peer": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, "engines": { - "node": ">=18" + "node": ">=8.6" } }, - "node_modules/ng-packagr/node_modules/@esbuild/android-arm64": { - "version": "0.27.7", - "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", - "cpu": [ - "arm64" - ], + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "peer": true, "engines": { - "node": ">=18" + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/ng-packagr/node_modules/@esbuild/android-x64": { - "version": "0.27.7", - "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", - "cpu": [ - "x64" - ], + "node_modules/mime": { + "version": "1.6.0", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "bin": { + "mime": "cli.js" + }, "engines": { - "node": ">=18" + "node": ">=4" } }, - "node_modules/ng-packagr/node_modules/@esbuild/darwin-arm64": { - "version": "0.27.7", - "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", - "cpu": [ - "arm64" - ], + "node_modules/mime-db": { + "version": "1.54.0", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": ">=18" + "node": ">= 0.6" } }, - "node_modules/ng-packagr/node_modules/@esbuild/darwin-x64": { - "version": "0.27.7", - "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", - "cpu": [ - "x64" - ], + "node_modules/mime-types": { + "version": "3.0.2", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "mime-db": "^1.54.0" + }, "engines": { "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/ng-packagr/node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.7", - "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", - "cpu": [ - "arm64" - ], + "node_modules/mimic-fn": { + "version": "2.1.0", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], "engines": { - "node": ">=18" + "node": ">=6" } }, - "node_modules/ng-packagr/node_modules/@esbuild/freebsd-x64": { - "version": "0.27.7", - "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", - "cpu": [ - "x64" - ], + "node_modules/mimic-function": { + "version": "5.0.1", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], "engines": { "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ng-packagr/node_modules/@esbuild/linux-arm": { - "version": "0.27.7", - "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", - "cpu": [ - "arm" - ], + "node_modules/mini-css-extract-plugin": { + "version": "2.10.0", + "integrity": "sha512-540P2c5dYnJlyJxTaSloliZexv8rji6rY8FhQN+WF/82iHQfA23j/xtJx97L+mXOML27EqksSek/g4eK7jaL3g==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "peer": true, + "dependencies": { + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" + }, "engines": { - "node": ">=18" + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" } }, - "node_modules/ng-packagr/node_modules/@esbuild/linux-arm64": { - "version": "0.27.7", - "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", - "cpu": [ - "arm64" - ], + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "license": "ISC", + "peer": true + }, + "node_modules/minimatch": { + "version": "10.2.6", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, "engines": { - "node": ">=18" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/ng-packagr/node_modules/@esbuild/linux-ia32": { - "version": "0.27.7", - "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", - "cpu": [ - "ia32" - ], + "node_modules/minimist": { + "version": "1.2.8", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", "engines": { - "node": ">=18" + "node": ">=16 || 14 >=14.17" } }, - "node_modules/ng-packagr/node_modules/@esbuild/linux-loong64": { - "version": "0.27.7", - "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", - "cpu": [ - "loong64" - ], + "node_modules/minipass-collect": { + "version": "2.0.1", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-fetch": { + "version": "5.0.2", + "integrity": "sha512-2d0q2a8eCi2IRg/IGubCNRJoYbA1+YPXAzQVRFmB45gdGZafyivnZ5YSEfo3JikbjGxOdntGFvBQGqaSMXlAFQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^2.0.0", + "minizlib": "^3.0.1" + }, "engines": { - "node": ">=18" + "node": "^20.17.0 || >=22.9.0" + }, + "optionalDependencies": { + "iconv-lite": "^0.7.2" } }, - "node_modules/ng-packagr/node_modules/@esbuild/linux-mips64el": { - "version": "0.27.7", - "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", - "cpu": [ - "mips64el" - ], + "node_modules/minipass-flush": { + "version": "1.0.7", + "integrity": "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "license": "BlueOak-1.0.0", + "dependencies": { + "minipass": "^3.0.0" + }, "engines": { - "node": ">=18" + "node": ">= 8" } }, - "node_modules/ng-packagr/node_modules/@esbuild/linux-ppc64": { - "version": "0.27.7", - "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", - "cpu": [ - "ppc64" - ], + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, "engines": { - "node": ">=18" + "node": ">=8" } }, - "node_modules/ng-packagr/node_modules/@esbuild/linux-riscv64": { - "version": "0.27.7", - "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", - "cpu": [ - "riscv64" - ], + "node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "license": "ISC" + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, "engines": { - "node": ">=18" + "node": ">=8" } }, - "node_modules/ng-packagr/node_modules/@esbuild/linux-s390x": { - "version": "0.27.7", - "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", - "cpu": [ - "s390x" - ], + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, "engines": { - "node": ">=18" + "node": ">=8" } }, - "node_modules/ng-packagr/node_modules/@esbuild/linux-x64": { - "version": "0.27.7", - "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", - "cpu": [ - "x64" - ], + "node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "license": "ISC" + }, + "node_modules/minipass-sized": { + "version": "2.0.0", + "integrity": "sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.1.2" + }, "engines": { - "node": ">=18" + "node": ">=8" } }, - "node_modules/ng-packagr/node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.7", - "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", - "cpu": [ - "arm64" - ], + "node_modules/minizlib": { + "version": "3.1.0", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], + "dependencies": { + "minipass": "^7.1.2" + }, "engines": { - "node": ">=18" + "node": ">= 18" } }, - "node_modules/ng-packagr/node_modules/@esbuild/netbsd-x64": { - "version": "0.27.7", - "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", - "cpu": [ - "x64" - ], + "node_modules/mitt": { + "version": "3.0.1", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true, + "license": "MIT" + }, + "node_modules/mrmime": { + "version": "2.0.1", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], "engines": { - "node": ">=18" + "node": ">=10" } }, - "node_modules/ng-packagr/node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.7", - "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", - "cpu": [ - "arm64" - ], + "node_modules/ms": { + "version": "2.1.3", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/msgpackr": { + "version": "1.12.1", + "integrity": "sha512-4EUH9tQHnMmEgzW/MdAP0KIfa1T9AF+htl0ffe2n5vb2EKn9y2co8ccpgWko6S52Jy1PQZKwRnx5/KkYjtd9MQ==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" + "optionalDependencies": { + "msgpackr-extract": "^3.0.2" } }, - "node_modules/ng-packagr/node_modules/@esbuild/openbsd-x64": { - "version": "0.27.7", - "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", - "cpu": [ - "x64" - ], + "node_modules/msgpackr-extract": { + "version": "3.0.4", + "integrity": "sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw==", "dev": true, + "hasInstallScript": true, "license": "MIT", "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" + "dependencies": { + "node-gyp-build-optional-packages": "5.2.2" + }, + "bin": { + "download-msgpackr-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.4", + "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.4", + "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.4", + "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.4", + "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.4", + "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.4" } }, - "node_modules/ng-packagr/node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.7", - "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", - "cpu": [ - "arm64" - ], + "node_modules/multicast-dns": { + "version": "7.2.5", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" + "peer": true, + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" } }, - "node_modules/ng-packagr/node_modules/@esbuild/sunos-x64": { - "version": "0.27.7", - "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", - "cpu": [ - "x64" - ], + "node_modules/mute-stream": { + "version": "2.0.0", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], + "license": "ISC", "engines": { - "node": ">=18" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/ng-packagr/node_modules/@esbuild/win32-arm64": { - "version": "0.27.7", - "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", - "cpu": [ - "arm64" + "node_modules/nanoid": { + "version": "3.3.20", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } ], - "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, "engines": { - "node": ">=18" + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/ng-packagr/node_modules/@esbuild/win32-ia32": { - "version": "0.27.7", - "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", - "cpu": [ - "ia32" - ], + "node_modules/napi-postinstall": { + "version": "0.3.4", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "bin": { + "napi-postinstall": "lib/cli.js" + }, "engines": { - "node": ">=18" + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" } }, - "node_modules/ng-packagr/node_modules/@esbuild/win32-x64": { - "version": "0.27.7", - "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", - "cpu": [ - "x64" - ], + "node_modules/natural-compare": { + "version": "1.4.0", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/needle": { + "version": "3.5.0", + "integrity": "sha512-jaQyPKKk2YokHrEg+vFDYxXIHTCBgiZwSHOoVx/8V3GIBS8/VN6NdVRmg8q1ERtPkMvmOvebsgga4sAj5hls/w==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "win32" - ], + "dependencies": { + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, "engines": { - "node": ">=18" + "node": ">= 4.4.x" } }, - "node_modules/ng-packagr/node_modules/chokidar": { - "version": "4.0.3", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "node_modules/needle/node_modules/iconv-lite": { + "version": "0.6.3", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "readdirp": "^4.0.1" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" + "node": ">=0.10.0" } }, - "node_modules/ng-packagr/node_modules/esbuild": { - "version": "0.27.7", - "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "node_modules/negotiator": { + "version": "1.1.0", + "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", "dev": true, - "hasInstallScript": true, "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" + "dependencies": { + "content-type": "^2.1.0" }, "engines": { "node": ">=18" }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.7", - "@esbuild/android-arm": "0.27.7", - "@esbuild/android-arm64": "0.27.7", - "@esbuild/android-x64": "0.27.7", - "@esbuild/darwin-arm64": "0.27.7", - "@esbuild/darwin-x64": "0.27.7", - "@esbuild/freebsd-arm64": "0.27.7", - "@esbuild/freebsd-x64": "0.27.7", - "@esbuild/linux-arm": "0.27.7", - "@esbuild/linux-arm64": "0.27.7", - "@esbuild/linux-ia32": "0.27.7", - "@esbuild/linux-loong64": "0.27.7", - "@esbuild/linux-mips64el": "0.27.7", - "@esbuild/linux-ppc64": "0.27.7", - "@esbuild/linux-riscv64": "0.27.7", - "@esbuild/linux-s390x": "0.27.7", - "@esbuild/linux-x64": "0.27.7", - "@esbuild/netbsd-arm64": "0.27.7", - "@esbuild/netbsd-x64": "0.27.7", - "@esbuild/openbsd-arm64": "0.27.7", - "@esbuild/openbsd-x64": "0.27.7", - "@esbuild/openharmony-arm64": "0.27.7", - "@esbuild/sunos-x64": "0.27.7", - "@esbuild/win32-arm64": "0.27.7", - "@esbuild/win32-ia32": "0.27.7", - "@esbuild/win32-x64": "0.27.7" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/ng-packagr/node_modules/readdirp": { - "version": "4.1.2", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "node_modules/negotiator/node_modules/content-type": { + "version": "2.1.0", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", "dev": true, "license": "MIT", "engines": { - "node": ">= 14.18.0" + "node": ">=18" }, "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/node-addon-api": { - "version": "7.1.1", - "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "node_modules/neo-async": { + "version": "2.6.2", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true, "license": "MIT" }, - "node_modules/node-exports-info": { - "version": "1.6.2", - "integrity": "sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag==", + "node_modules/ng-packagr": { + "version": "21.0.1", + "integrity": "sha512-IZGxuF226GF0d8FOZIfPvHsyBl53PrDEg/IB2+CVamsm3r4+gUw3mBp27eygpowBpdVLG0Sm2IbUiH4aSspzyA==", "dev": true, "license": "MIT", "dependencies": { - "array.prototype.flatmap": "^1.3.3", - "es-errors": "^1.3.0", - "object.entries": "^1.1.9", - "semver": "^6.3.1" + "@ampproject/remapping": "^2.3.0", + "@rollup/plugin-json": "^6.1.0", + "@rollup/wasm-node": "^4.24.0", + "ajv": "^8.17.1", + "ansi-colors": "^4.1.3", + "browserslist": "^4.26.0", + "chokidar": "^4.0.1", + "commander": "^14.0.0", + "dependency-graph": "^1.0.0", + "esbuild": "^0.27.0", + "find-cache-directory": "^6.0.0", + "injection-js": "^2.4.0", + "jsonc-parser": "^3.3.1", + "less": "^4.2.0", + "ora": "^9.0.0", + "piscina": "^5.0.0", + "postcss": "^8.4.47", + "rollup-plugin-dts": "^6.2.0", + "rxjs": "^7.8.1", + "sass": "^1.81.0", + "tinyglobby": "^0.2.12" + }, + "bin": { + "ng-packagr": "src/cli/main.js" }, "engines": { - "node": ">= 0.4" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/node-exports-info/node_modules/semver": { - "version": "6.3.1", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "optionalDependencies": { + "rollup": "^4.24.0" + }, + "peerDependencies": { + "@angular/compiler-cli": "^21.0.0-next || ^21.0.0", + "tailwindcss": "^2.0.0 || ^3.0.0 || ^4.0.0", + "tslib": "^2.3.0", + "typescript": ">=5.9 <6.0" + }, + "peerDependenciesMeta": { + "tailwindcss": { + "optional": true + } } }, - "node_modules/node-gyp": { - "version": "12.4.0", - "integrity": "sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw==", + "node_modules/ng-packagr/node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", - "dependencies": { - "env-paths": "^2.2.0", - "exponential-backoff": "^3.1.1", - "graceful-fs": "^4.2.6", - "nopt": "^9.0.0", - "proc-log": "^6.0.0", - "semver": "^7.3.5", - "tar": "^7.5.4", - "tinyglobby": "^0.2.12", - "undici": "^6.25.0", - "which": "^6.0.0" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" - }, + "optional": true, + "os": [ + "aix" + ], "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": ">=18" } }, - "node_modules/node-gyp-build-optional-packages": { - "version": "5.2.2", - "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", + "node_modules/ng-packagr/node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", "optional": true, - "dependencies": { - "detect-libc": "^2.0.1" - }, - "bin": { - "node-gyp-build-optional-packages": "bin.js", - "node-gyp-build-optional-packages-optional": "optional.js", - "node-gyp-build-optional-packages-test": "build-test.js" + "os": [ + "android" + ], + "engines": { + "node": ">=18" } }, - "node_modules/node-gyp/node_modules/isexe": { - "version": "4.0.0", - "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "node_modules/ng-packagr/node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "BlueOak-1.0.0", + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=20" + "node": ">=18" } }, - "node_modules/node-gyp/node_modules/proc-log": { - "version": "6.1.0", - "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "node_modules/ng-packagr/node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], "dev": true, - "license": "ISC", + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": ">=18" } }, - "node_modules/node-gyp/node_modules/undici": { - "version": "6.29.0", - "integrity": "sha512-R+RODBqp6i2pPflGdq+xIOUkl+RNfGgHwoinecKu/JCuf2uO06cOKoDbI2P7Dn6KcswdKwrczbU6IYJ6K8X+wg==", + "node_modules/ng-packagr/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=18.17" + "node": ">=18" } }, - "node_modules/node-gyp/node_modules/which": { - "version": "6.0.1", - "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", + "node_modules/ng-packagr/node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^4.0.0" - }, - "bin": { - "node-which": "bin/which.js" - }, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": ">=18" } }, - "node_modules/node-int64": { - "version": "0.4.0", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-releases": { - "version": "2.0.58", - "integrity": "sha512-BdiKAgbfRlvfMq/XzO/AF9NPmaA8a+YpLAyAFTVFBj3gARrAIfWvNq7Q7NE4se3KgFu0KkrvQMxTS0XVnsGzHg==", + "node_modules/ng-packagr/node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { "node": ">=18" } }, - "node_modules/nopt": { - "version": "9.0.0", - "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", + "node_modules/ng-packagr/node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], "dev": true, - "license": "ISC", - "dependencies": { - "abbrev": "^4.0.0" - }, - "bin": { - "nopt": "bin/nopt.js" - }, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": ">=18" } }, - "node_modules/normalize-path": { - "version": "3.0.0", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "node_modules/ng-packagr/node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, - "node_modules/npm-bundled": { - "version": "4.0.0", - "integrity": "sha512-IxaQZDMsqfQ2Lz37VvyyEtKLe8FsRZuysmedy/N06TU1RyVppYKXrO4xIhR0F+7ubIBox6Q7nir6fQI3ej39iA==", + "node_modules/ng-packagr/node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "ISC", - "dependencies": { - "npm-normalize-package-bin": "^4.0.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=18" } }, - "node_modules/npm-install-checks": { - "version": "8.0.0", - "integrity": "sha512-ScAUdMpyzkbpxoNekQ3tNRdFI8SJ86wgKZSQZdUxT+bj0wVFpsEMWnkXP0twVe1gJyNF5apBWDJhhIbgrIViRA==", + "node_modules/ng-packagr/node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "semver": "^7.1.1" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": ">=18" } }, - "node_modules/npm-normalize-package-bin": { - "version": "4.0.0", - "integrity": "sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==", + "node_modules/ng-packagr/node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], "dev": true, - "license": "ISC", + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=18" } }, - "node_modules/npm-package-arg": { - "version": "13.0.1", - "integrity": "sha512-6zqls5xFvJbgFjB1B2U6yITtyGBjDBORB7suI4zA4T/sZ1OmkMFlaQSNB/4K0LtXNA1t4OprAFxPisadK5O2ag==", + "node_modules/ng-packagr/node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], "dev": true, - "license": "ISC", - "dependencies": { - "hosted-git-info": "^9.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^6.0.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": ">=18" } }, - "node_modules/npm-packlist": { - "version": "10.0.4", - "integrity": "sha512-uMW73iajD8hiH4ZBxEV3HC+eTnppIqwakjOYuvgddnalIw2lJguKviK1pcUJDlIWm1wSJkchpDZDSVVsZEYRng==", + "node_modules/ng-packagr/node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], "dev": true, - "license": "ISC", - "dependencies": { - "ignore-walk": "^8.0.0", - "proc-log": "^6.0.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": ">=18" } }, - "node_modules/npm-packlist/node_modules/proc-log": { - "version": "6.1.0", - "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "node_modules/ng-packagr/node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], "dev": true, - "license": "ISC", + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": ">=18" } }, - "node_modules/npm-pick-manifest": { - "version": "11.0.3", - "integrity": "sha512-buzyCfeoGY/PxKqmBqn1IUJrZnUi1VVJTdSSRPGI60tJdUhUoSQFhs0zycJokDdOznQentgrpf8LayEHyyYlqQ==", + "node_modules/ng-packagr/node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], "dev": true, - "license": "ISC", - "dependencies": { - "npm-install-checks": "^8.0.0", - "npm-normalize-package-bin": "^5.0.0", - "npm-package-arg": "^13.0.0", - "semver": "^7.3.5" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": ">=18" } }, - "node_modules/npm-pick-manifest/node_modules/npm-normalize-package-bin": { - "version": "5.0.0", - "integrity": "sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag==", + "node_modules/ng-packagr/node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], "dev": true, - "license": "ISC", + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": ">=18" } }, - "node_modules/npm-registry-fetch": { - "version": "19.1.1", - "integrity": "sha512-TakBap6OM1w0H73VZVDf44iFXsOS3h+L4wVMXmbWOQroZgFhMch0juN6XSzBNlD965yIKvWg2dfu7NSiaYLxtw==", + "node_modules/ng-packagr/node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "ISC", - "dependencies": { - "@npmcli/redact": "^4.0.0", - "jsonparse": "^1.3.1", - "make-fetch-happen": "^15.0.0", - "minipass": "^7.0.2", - "minipass-fetch": "^5.0.0", - "minizlib": "^3.0.1", - "npm-package-arg": "^13.0.0", - "proc-log": "^6.0.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": ">=18" } }, - "node_modules/npm-registry-fetch/node_modules/proc-log": { - "version": "6.1.0", - "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "node_modules/ng-packagr/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], "dev": true, - "license": "ISC", + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": ">=18" } }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "node_modules/ng-packagr/node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/nth-check": { - "version": "2.1.1", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "node_modules/ng-packagr/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/nwsapi": { - "version": "2.2.28", - "integrity": "sha512-IlVB7OS7qrOsVYlpnFIkETjMwT9jwvmocJmmM+GZU/PAB3uGi9Ezd7vcWhWBUnSc0ya4ppmQITOyP1ez9gg8cg==", - "dev": true, - "license": "MIT" - }, - "node_modules/object-assign": { - "version": "4.1.1", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "node_modules/ng-packagr/node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, - "node_modules/object-inspect": { - "version": "1.13.4", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "node_modules/ng-packagr/node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=18" } }, - "node_modules/object-keys": { - "version": "1.1.1", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "node_modules/ng-packagr/node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 0.4" + "node": ">=18" } }, - "node_modules/object.assign": { - "version": "4.1.7", - "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "node_modules/ng-packagr/node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0", - "has-symbols": "^1.1.0", - "object-keys": "^1.1.1" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=18" } }, - "node_modules/object.entries": { - "version": "1.1.9", - "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "node_modules/ng-packagr/node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.1.1" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 0.4" + "node": ">=18" } }, - "node_modules/object.fromentries": { - "version": "2.0.8", - "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "node_modules/ng-packagr/node_modules/chokidar": { + "version": "4.0.3", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0" + "readdirp": "^4.0.1" }, "engines": { - "node": ">= 0.4" + "node": ">= 14.16.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://paulmillr.com/funding/" } }, - "node_modules/object.groupby": { - "version": "1.0.3", - "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "node_modules/ng-packagr/node_modules/esbuild": { + "version": "0.27.7", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", "dev": true, + "hasInstallScript": true, "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2" + "bin": { + "esbuild": "bin/esbuild" }, "engines": { - "node": ">= 0.4" + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" } }, - "node_modules/object.values": { - "version": "1.2.1", - "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "node_modules/ng-packagr/node_modules/readdirp": { + "version": "4.1.2", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "dev": true, "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, "engines": { - "node": ">= 0.4" + "node": ">= 14.18.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, - "node_modules/obuf": { - "version": "1.1.2", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "node_modules/node-addon-api": { + "version": "7.1.1", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, - "node_modules/on-finished": { - "version": "2.4.1", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "node_modules/node-exports-info": { + "version": "1.6.2", + "integrity": "sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag==", "dev": true, "license": "MIT", "dependencies": { - "ee-first": "1.1.1" + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" }, "engines": { - "node": ">= 0.8" - } - }, - "node_modules/on-headers": { - "version": "1.1.0", - "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/once": { - "version": "1.4.0", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "node_modules/node-exports-info/node_modules/semver": { + "version": "6.3.1", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", - "dependencies": { - "wrappy": "1" + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/onetime": { - "version": "5.1.2", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "node_modules/node-gyp": { + "version": "12.4.0", + "integrity": "sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw==", "dev": true, "license": "MIT", "dependencies": { - "mimic-fn": "^2.1.0" + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "tar": "^7.5.4", + "tinyglobby": "^0.2.12", + "undici": "^6.25.0", + "which": "^6.0.0" }, - "engines": { - "node": ">=6" + "bin": { + "node-gyp": "bin/node-gyp.js" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/open": { - "version": "11.0.0", - "integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==", + "node_modules/node-gyp-build-optional-packages": { + "version": "5.2.2", + "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", "dev": true, "license": "MIT", - "peer": true, + "optional": true, "dependencies": { - "default-browser": "^5.4.0", - "define-lazy-prop": "^3.0.0", - "is-in-ssh": "^1.0.0", - "is-inside-container": "^1.0.0", - "powershell-utils": "^0.1.0", - "wsl-utils": "^0.3.0" + "detect-libc": "^2.0.1" }, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, + "node_modules/node-gyp/node_modules/isexe": { + "version": "4.0.0", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "dev": true, + "license": "BlueOak-1.0.0", "engines": { "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/optionator": { - "version": "0.9.4", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "node_modules/node-gyp/node_modules/undici": { + "version": "6.29.0", + "integrity": "sha512-R+RODBqp6i2pPflGdq+xIOUkl+RNfGgHwoinecKu/JCuf2uO06cOKoDbI2P7Dn6KcswdKwrczbU6IYJ6K8X+wg==", "dev": true, "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, "engines": { - "node": ">= 0.8.0" + "node": ">=18.17" } }, - "node_modules/ora": { - "version": "9.3.0", - "integrity": "sha512-lBX72MWFduWEf7v7uWf5DHp9Jn5BI8bNPGuFgtXMmr2uDz2Gz2749y3am3agSDdkhHPHYmmxEGSKH85ZLGzgXw==", + "node_modules/node-gyp/node_modules/which": { + "version": "6.0.1", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "chalk": "^5.6.2", - "cli-cursor": "^5.0.0", - "cli-spinners": "^3.2.0", - "is-interactive": "^2.0.0", - "is-unicode-supported": "^2.1.0", - "log-symbols": "^7.0.1", - "stdin-discarder": "^0.3.1", - "string-width": "^8.1.0" + "isexe": "^4.0.0" }, - "engines": { - "node": ">=20" + "bin": { + "node-which": "bin/which.js" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/ora/node_modules/chalk": { - "version": "5.6.2", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "node_modules/node-int64": { + "version": "0.4.0", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } + "license": "MIT" }, - "node_modules/ordered-binary": { - "version": "1.6.2", - "integrity": "sha512-BY+qgDArmYpvvmij141xbkB4Cw8RFLBQM5Zj4MODFb1SyXBRE/xO4Xj/QWbR7aUEBxbkjuNdsFVCL2TmbYrlNg==", + "node_modules/node-releases": { + "version": "2.0.58", + "integrity": "sha512-BdiKAgbfRlvfMq/XzO/AF9NPmaA8a+YpLAyAFTVFBj3gARrAIfWvNq7Q7NE4se3KgFu0KkrvQMxTS0XVnsGzHg==", "dev": true, "license": "MIT", - "optional": true + "engines": { + "node": ">=18" + } }, - "node_modules/own-keys": { - "version": "1.0.2", - "integrity": "sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==", + "node_modules/nopt": { + "version": "9.0.0", + "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "call-bound": "^1.0.4", - "get-intrinsic": "^1.3.0", - "object-keys": "^1.1.1", - "safe-push-apply": "^1.0.0" + "abbrev": "^4.0.0" }, - "engines": { - "node": ">= 0.4" + "bin": { + "nopt": "bin/nopt.js" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/p-limit": { - "version": "3.1.0", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "node_modules/normalize-path": { + "version": "3.0.0", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.10.0" } }, - "node_modules/p-locate": { + "node_modules/npm-bundled": { "version": "5.0.0", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "integrity": "sha512-JLSpbzh6UUXIEoqPsYBvVNVmyrjVZ1fzEFbqxKkTJQkWBO3xFzFT+KDnSKQWwOQNbuWRwt5LSD6HOTLGIWzfrw==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "p-limit": "^3.0.2" + "npm-normalize-package-bin": "^5.0.0" }, "engines": { - "node": ">=10" + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-install-checks": { + "version": "8.0.0", + "integrity": "sha512-ScAUdMpyzkbpxoNekQ3tNRdFI8SJ86wgKZSQZdUxT+bj0wVFpsEMWnkXP0twVe1gJyNF5apBWDJhhIbgrIViRA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/p-map": { - "version": "7.1.1", - "integrity": "sha512-6Lh3jQPLthYujSrlw6ccWXBGZTkoTCz27Hd92g79LH/kjAbgoEu0t442cCCNbegPSJsA0lwDX8z0bcKtkpA32g==", + "node_modules/npm-normalize-package-bin": { + "version": "5.0.0", + "integrity": "sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag==", "dev": true, - "license": "MIT", + "license": "ISC", "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/p-retry": { - "version": "6.2.1", - "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", + "node_modules/npm-package-arg": { + "version": "13.0.2", + "integrity": "sha512-IciCE3SY3uE84Ld8WZU23gAPPV9rIYod4F+rc+vJ7h7cwAJt9Vk6TVsK60ry7Uj3SRS3bqRRIGuTp9YVlk6WNA==", "dev": true, - "license": "MIT", - "peer": true, + "license": "ISC", "dependencies": { - "@types/retry": "0.12.2", - "is-network-error": "^1.0.0", - "retry": "^0.13.1" + "hosted-git-info": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^7.0.0" }, "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/p-retry/node_modules/retry": { - "version": "0.13.1", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "node_modules/npm-packlist": { + "version": "10.0.4", + "integrity": "sha512-uMW73iajD8hiH4ZBxEV3HC+eTnppIqwakjOYuvgddnalIw2lJguKviK1pcUJDlIWm1wSJkchpDZDSVVsZEYRng==", "dev": true, - "license": "MIT", - "peer": true, + "license": "ISC", + "dependencies": { + "ignore-walk": "^8.0.0", + "proc-log": "^6.0.0" + }, "engines": { - "node": ">= 4" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/p-try": { - "version": "2.2.0", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "node_modules/npm-pick-manifest": { + "version": "11.0.3", + "integrity": "sha512-buzyCfeoGY/PxKqmBqn1IUJrZnUi1VVJTdSSRPGI60tJdUhUoSQFhs0zycJokDdOznQentgrpf8LayEHyyYlqQ==", "dev": true, - "license": "MIT", + "license": "ISC", + "dependencies": { + "npm-install-checks": "^8.0.0", + "npm-normalize-package-bin": "^5.0.0", + "npm-package-arg": "^13.0.0", + "semver": "^7.3.5" + }, "engines": { - "node": ">=6" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, - "node_modules/pacote": { - "version": "21.0.3", - "integrity": "sha512-itdFlanxO0nmQv4ORsvA9K1wv40IPfB9OmWqfaJWvoJ30VKyHsqNgDVeG+TVhI7Gk7XW8slUy7cA9r6dF5qohw==", + "node_modules/npm-registry-fetch": { + "version": "19.1.1", + "integrity": "sha512-TakBap6OM1w0H73VZVDf44iFXsOS3h+L4wVMXmbWOQroZgFhMch0juN6XSzBNlD965yIKvWg2dfu7NSiaYLxtw==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/git": "^7.0.0", - "@npmcli/installed-package-contents": "^3.0.0", - "@npmcli/package-json": "^7.0.0", - "@npmcli/promise-spawn": "^8.0.0", - "@npmcli/run-script": "^10.0.0", - "cacache": "^20.0.0", - "fs-minipass": "^3.0.0", + "@npmcli/redact": "^4.0.0", + "jsonparse": "^1.3.1", + "make-fetch-happen": "^15.0.0", "minipass": "^7.0.2", + "minipass-fetch": "^5.0.0", + "minizlib": "^3.0.1", "npm-package-arg": "^13.0.0", - "npm-packlist": "^10.0.1", - "npm-pick-manifest": "^11.0.1", - "npm-registry-fetch": "^19.0.0", - "proc-log": "^5.0.0", - "promise-retry": "^2.0.1", - "sigstore": "^4.0.0", - "ssri": "^12.0.0", - "tar": "^7.4.3" - }, - "bin": { - "pacote": "bin/index.js" + "proc-log": "^6.0.0" }, "engines": { "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/parent-module": { - "version": "1.0.1", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "node_modules/npm-run-path": { + "version": "4.0.1", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "license": "MIT", "dependencies": { - "callsites": "^3.0.0" + "path-key": "^3.0.0" }, "engines": { - "node": ">=6" + "node": ">=8" } }, - "node_modules/parse-json": { - "version": "5.2.0", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "node_modules/nth-check": { + "version": "2.1.1", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" + "boolbase": "^1.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/fb55/nth-check?sponsor=1" } }, - "node_modules/parse-json/node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "node_modules/nwsapi": { + "version": "2.2.28", + "integrity": "sha512-IlVB7OS7qrOsVYlpnFIkETjMwT9jwvmocJmmM+GZU/PAB3uGi9Ezd7vcWhWBUnSc0ya4ppmQITOyP1ez9gg8cg==", "dev": true, "license": "MIT" }, - "node_modules/parse-node-version": { - "version": "1.0.1", - "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "node_modules/object-assign": { + "version": "4.1.1", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.10" + "node": ">=0.10.0" } }, - "node_modules/parse5": { - "version": "7.3.0", - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "node_modules/object-inspect": { + "version": "1.13.4", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "dev": true, "license": "MIT", - "dependencies": { - "entities": "^6.0.0" + "engines": { + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/parse5-html-rewriting-stream": { - "version": "8.0.0", - "integrity": "sha512-wzh11mj8KKkno1pZEu+l2EVeWsuKDfR5KNWZOTsslfUX8lPDZx77m9T0kIoAVkFtD1nx6YF8oh4BnPHvxMtNMw==", + "node_modules/object-keys": { + "version": "1.1.1", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true, "license": "MIT", - "dependencies": { - "entities": "^6.0.0", - "parse5": "^8.0.0", - "parse5-sax-parser": "^8.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" + "engines": { + "node": ">= 0.4" } }, - "node_modules/parse5-html-rewriting-stream/node_modules/entities": { - "version": "6.0.1", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "node_modules/object.assign": { + "version": "4.1.7", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, "engines": { - "node": ">=0.12" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/parse5-html-rewriting-stream/node_modules/parse5": { - "version": "8.0.1", - "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", + "node_modules/object.entries": { + "version": "1.1.9", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", "dev": true, "license": "MIT", "dependencies": { - "entities": "^8.0.0" + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" + "engines": { + "node": ">= 0.4" } }, - "node_modules/parse5-html-rewriting-stream/node_modules/parse5/node_modules/entities": { - "version": "8.1.0", - "integrity": "sha512-kxL7msIffSuh9aaFAMD7rxAIuTRMAHMeBtgHW2yUdWw732ZNh4MehkF2gdjvtdmikkaIP9bFDDJOPlsvm7avrA==", + "node_modules/object.fromentries": { + "version": "2.0.8", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, "engines": { - "node": ">=20.19.0" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/parse5-sax-parser": { - "version": "8.0.0", - "integrity": "sha512-/dQ8UzHZwnrzs3EvDj6IkKrD/jIZyTlB+8XrHJvcjNgRdmWruNdN9i9RK/JtxakmlUdPwKubKPTCqvbTgzGhrw==", + "node_modules/object.groupby": { + "version": "1.0.3", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", "dev": true, "license": "MIT", "dependencies": { - "parse5": "^8.0.0" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" + "engines": { + "node": ">= 0.4" } }, - "node_modules/parse5-sax-parser/node_modules/entities": { - "version": "8.1.0", - "integrity": "sha512-kxL7msIffSuh9aaFAMD7rxAIuTRMAHMeBtgHW2yUdWw732ZNh4MehkF2gdjvtdmikkaIP9bFDDJOPlsvm7avrA==", + "node_modules/object.values": { + "version": "1.2.1", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, "engines": { - "node": ">=20.19.0" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/parse5-sax-parser/node_modules/parse5": { - "version": "8.0.1", - "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", + "node_modules/obuf": { + "version": "1.1.2", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", "dev": true, "license": "MIT", - "dependencies": { - "entities": "^8.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } + "peer": true }, - "node_modules/parse5/node_modules/entities": { - "version": "6.0.1", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "node_modules/on-finished": { + "version": "2.4.1", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "engines": { + "node": ">= 0.8" } }, - "node_modules/parseurl": { - "version": "1.3.3", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "node_modules/on-headers": { + "version": "1.1.0", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.8" } }, - "node_modules/path-exists": { - "version": "4.0.0", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "node_modules/once": { + "version": "1.4.0", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" + "license": "ISC", + "dependencies": { + "wrappy": "1" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "node_modules/onetime": { + "version": "5.1.2", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/path-key": { - "version": "3.1.1", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "node_modules/open": { + "version": "11.0.0", + "integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==", "dev": true, "license": "MIT", + "peer": true, + "dependencies": { + "default-browser": "^5.4.0", + "define-lazy-prop": "^3.0.0", + "is-in-ssh": "^1.0.0", + "is-inside-container": "^1.0.0", + "powershell-utils": "^0.1.0", + "wsl-utils": "^0.3.0" + }, "engines": { - "node": ">=8" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/path-parse": { - "version": "1.0.7", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, - "node_modules/path-scurry": { - "version": "2.0.2", - "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "node_modules/optionator": { + "version": "0.9.4", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "MIT", "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" }, "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">= 0.8.0" } }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.5.3", - "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", + "node_modules/ora": { + "version": "9.3.0", + "integrity": "sha512-lBX72MWFduWEf7v7uWf5DHp9Jn5BI8bNPGuFgtXMmr2uDz2Gz2749y3am3agSDdkhHPHYmmxEGSKH85ZLGzgXw==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "MIT", + "dependencies": { + "chalk": "^5.6.2", + "cli-cursor": "^5.0.0", + "cli-spinners": "^3.2.0", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.1.0", + "log-symbols": "^7.0.1", + "stdin-discarder": "^0.3.1", + "string-width": "^8.1.0" + }, "engines": { - "node": "20 || >=22" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/path-to-regexp": { - "version": "8.4.2", - "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "node_modules/ora/node_modules/chalk": { + "version": "5.6.2", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "dev": true, "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/picocolors": { - "version": "1.1.1", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "node_modules/ordered-binary": { + "version": "1.6.2", + "integrity": "sha512-BY+qgDArmYpvvmij141xbkB4Cw8RFLBQM5Zj4MODFb1SyXBRE/xO4Xj/QWbR7aUEBxbkjuNdsFVCL2TmbYrlNg==", "dev": true, - "license": "ISC" + "license": "MIT", + "optional": true }, - "node_modules/picomatch": { - "version": "4.0.4", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "node_modules/own-keys": { + "version": "1.0.2", + "integrity": "sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==", "dev": true, "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, "engines": { - "node": ">=12" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/pirates": { - "version": "4.0.7", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "node_modules/p-limit": { + "version": "3.1.0", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, "engines": { - "node": ">= 6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/piscina": { - "version": "5.3.2", - "integrity": "sha512-vv7l/mM7B9WgrhDaudIqv1x6v4LOBRYYmpWGdWushhpbCmVHp11sUB2v4hj1CAeTGXVxuZFz6xOU1RalAiPKfQ==", + "node_modules/p-locate": { + "version": "5.0.0", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, "engines": { - "node": ">=20.x" + "node": ">=10" }, - "optionalDependencies": { - "@napi-rs/nice": "^1.0.4" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/pkce-challenge": { - "version": "5.0.1", - "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "node_modules/p-map": { + "version": "7.1.1", + "integrity": "sha512-6Lh3jQPLthYujSrlw6ccWXBGZTkoTCz27Hd92g79LH/kjAbgoEu0t442cCCNbegPSJsA0lwDX8z0bcKtkpA32g==", "dev": true, "license": "MIT", "engines": { - "node": ">=16.20.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "node_modules/p-retry": { + "version": "6.2.1", + "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "find-up": "^4.0.0" + "@types/retry": "0.12.2", + "is-network-error": "^1.0.0", + "retry": "^0.13.1" }, "engines": { - "node": ">=8" + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "4.1.0", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "node_modules/p-retry/node_modules/retry": { + "version": "0.13.1", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", "dev": true, "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, + "peer": true, "engines": { - "node": ">=8" + "node": ">= 4" } }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "5.0.0", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "node_modules/p-try": { + "version": "2.2.0", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true, "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, "engines": { - "node": ">=8" + "node": ">=6" } }, - "node_modules/pkg-dir/node_modules/p-limit": { - "version": "2.3.0", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "dev": true, - "license": "MIT", + "license": "BlueOak-1.0.0" + }, + "node_modules/pacote": { + "version": "21.5.1", + "integrity": "sha512-KvcJ9iy3crysCsgqc4+PknH/w6jkrp8JN36mpZBPwNaDRwTfMZD37YzRazNstiZUOhuF5pno9f78n9mEJBavwg==", + "dev": true, + "license": "ISC", "dependencies": { - "p-try": "^2.0.0" + "@gar/promise-retry": "^1.0.0", + "@npmcli/git": "^7.0.0", + "@npmcli/installed-package-contents": "^4.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "@npmcli/run-script": "^10.0.0", + "cacache": "^20.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^13.0.0", + "npm-packlist": "^10.0.1", + "npm-pick-manifest": "^11.0.1", + "npm-registry-fetch": "^19.0.0", + "proc-log": "^6.0.0", + "sigstore": "^4.0.0", + "ssri": "^13.0.0", + "tar": "^7.4.3" }, - "engines": { - "node": ">=6" + "bin": { + "pacote": "bin/index.js" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "4.1.0", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "node_modules/parent-module": { + "version": "1.0.1", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "license": "MIT", "dependencies": { - "p-limit": "^2.2.0" + "callsites": "^3.0.0" }, "engines": { - "node": ">=8" + "node": ">=6" } }, - "node_modules/pkijs": { - "version": "3.4.1", - "integrity": "sha512-Oo/NZcSWccq8KyoG7gLE9fnltgHns+pNCjCAp/WmjsUySi+sX7y4z4Xqu4fVb42CDHzRPl33fjzT15V1wvcyhA==", + "node_modules/parse-json": { + "version": "5.2.0", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, - "license": "BSD-3-Clause", - "peer": true, - "workspaces": [ - "website" - ], + "license": "MIT", "dependencies": { - "@noble/hashes": "1.8.0", - "asn1js": "^3.0.6", - "bytestreamjs": "^2.0.1", - "pvtsutils": "^1.3.6", - "pvutils": "^1.1.3", - "tslib": "^2.8.1" + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" }, "engines": { - "node": ">=16.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/possible-typed-array-names": { - "version": "1.1.0", - "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "node_modules/parse-json/node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse-node-version": { + "version": "1.0.1", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">= 0.10" } }, - "node_modules/postcss": { - "version": "8.5.23", - "integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==", + "node_modules/parse5": { + "version": "7.3.0", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.16", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" + "entities": "^6.0.0" }, - "engines": { - "node": "^10 || ^12 || >=14" + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/postcss-loader": { - "version": "8.2.0", - "integrity": "sha512-tHX+RkpsXVcc7st4dSdDGliI+r4aAQDuv+v3vFYHixb6YgjreG5AG4SEB0kDK8u2s6htqEEpKlkhSBUTvWKYnA==", + "node_modules/parse5-html-rewriting-stream": { + "version": "8.0.0", + "integrity": "sha512-wzh11mj8KKkno1pZEu+l2EVeWsuKDfR5KNWZOTsslfUX8lPDZx77m9T0kIoAVkFtD1nx6YF8oh4BnPHvxMtNMw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "cosmiconfig": "^9.0.0", - "jiti": "^2.5.1", - "semver": "^7.6.2" - }, - "engines": { - "node": ">= 18.12.0" + "entities": "^6.0.0", + "parse5": "^8.0.0", + "parse5-sax-parser": "^8.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "@rspack/core": "0.x || 1.x", - "postcss": "^7.0.0 || ^8.0.1", - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true - }, - "webpack": { - "optional": true - } + "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/postcss-media-query-parser": { - "version": "0.2.3", - "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", - "dev": true, - "license": "MIT" - }, - "node_modules/postcss-modules-extract-imports": { - "version": "3.1.0", - "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "node_modules/parse5-html-rewriting-stream/node_modules/entities": { + "version": "6.0.1", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", "dev": true, - "license": "ISC", - "peer": true, + "license": "BSD-2-Clause", "engines": { - "node": "^10 || ^12 || >= 14" + "node": ">=0.12" }, - "peerDependencies": { - "postcss": "^8.1.0" + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/postcss-modules-local-by-default": { - "version": "4.2.0", - "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", + "node_modules/parse5-html-rewriting-stream/node_modules/parse5": { + "version": "8.0.1", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "icss-utils": "^5.0.0", - "postcss-selector-parser": "^7.0.0", - "postcss-value-parser": "^4.1.0" + "entities": "^8.0.0" }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-html-rewriting-stream/node_modules/parse5/node_modules/entities": { + "version": "8.1.0", + "integrity": "sha512-kxL7msIffSuh9aaFAMD7rxAIuTRMAHMeBtgHW2yUdWw732ZNh4MehkF2gdjvtdmikkaIP9bFDDJOPlsvm7avrA==", + "dev": true, + "license": "BSD-2-Clause", "engines": { - "node": "^10 || ^12 || >= 14" + "node": ">=20.19.0" }, - "peerDependencies": { - "postcss": "^8.1.0" + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/postcss-modules-scope": { - "version": "3.2.1", - "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", + "node_modules/parse5-sax-parser": { + "version": "8.0.0", + "integrity": "sha512-/dQ8UzHZwnrzs3EvDj6IkKrD/jIZyTlB+8XrHJvcjNgRdmWruNdN9i9RK/JtxakmlUdPwKubKPTCqvbTgzGhrw==", "dev": true, - "license": "ISC", - "peer": true, + "license": "MIT", "dependencies": { - "postcss-selector-parser": "^7.0.0" + "parse5": "^8.0.0" }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-sax-parser/node_modules/entities": { + "version": "8.1.0", + "integrity": "sha512-kxL7msIffSuh9aaFAMD7rxAIuTRMAHMeBtgHW2yUdWw732ZNh4MehkF2gdjvtdmikkaIP9bFDDJOPlsvm7avrA==", + "dev": true, + "license": "BSD-2-Clause", "engines": { - "node": "^10 || ^12 || >= 14" + "node": ">=20.19.0" }, - "peerDependencies": { - "postcss": "^8.1.0" + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/postcss-modules-values": { - "version": "4.0.0", - "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "node_modules/parse5-sax-parser/node_modules/parse5": { + "version": "8.0.1", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", "dev": true, - "license": "ISC", - "peer": true, + "license": "MIT", "dependencies": { - "icss-utils": "^5.0.0" + "entities": "^8.0.0" }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", "engines": { - "node": "^10 || ^12 || >= 14" + "node": ">=0.12" }, - "peerDependencies": { - "postcss": "^8.1.0" + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/postcss-selector-parser": { - "version": "7.1.6", - "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "node_modules/parseurl": { + "version": "1.3.3", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, "engines": { - "node": ">=4" + "node": ">= 0.8" } }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "node_modules/path-exists": { + "version": "4.0.0", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "license": "MIT", - "peer": true + "engines": { + "node": ">=8" + } }, - "node_modules/powershell-utils": { - "version": "0.1.0", - "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==", + "node_modules/path-is-absolute": { + "version": "1.0.1", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true, "license": "MIT", - "peer": true, "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.10.0" } }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "node_modules/path-key": { + "version": "3.1.1", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.8.0" + "node": ">=8" } }, - "node_modules/prettier": { - "version": "3.9.10", - "integrity": "sha512-skGHTH1Yb9QvaTC4hWnRTzEYtCpApF0UMBPz+t/0ZPzP8ihefo/btKd+QA7pUE0fEwA1SbS/NmhyEcstdXOIdA==", + "node_modules/path-parse": { + "version": "1.0.7", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" }, "engines": { - "node": ">=14" + "node": "18 || 20 || >=22" }, "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/prettier-linter-helpers": { - "version": "1.0.1", - "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==", + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.5.3", + "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", "dev": true, - "license": "MIT", - "dependencies": { - "fast-diff": "^1.1.2" - }, + "license": "BlueOak-1.0.0", "engines": { - "node": ">=6.0.0" + "node": "20 || >=22" } }, - "node_modules/pretty-format": { - "version": "30.5.1", - "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==", + "node_modules/path-to-regexp": { + "version": "8.4.2", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/react-is-18": "npm:react-is@^18.3.1", - "@jest/react-is-19": "npm:react-is@^19.2.5", - "@jest/schemas": "30.5.0", - "ansi-styles": "^5.2.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "node_modules/picocolors": { + "version": "1.1.1", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/probe-image-size": { - "version": "7.4.0", - "integrity": "sha512-cdEprVtZxV+awMde9X+4jILBFYh4CARxVrQaMl4wY4YcPWbul9jntXrIW95NInBDyJwcVUP3U0T6yukN8rMBaQ==", + "node_modules/pirates": { + "version": "4.0.7", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/puzrin" - }, - { - "type": "github", - "url": "https://github.com/sponsors/nodeca" - } - ], "license": "MIT", - "optional": true, - "dependencies": { - "lodash.merge": "^4.6.2", - "needle": "^2.5.2", - "stream-parser": "~0.3.1" + "engines": { + "node": ">= 6" } }, - "node_modules/probe-image-size/node_modules/debug": { - "version": "3.2.7", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "node_modules/piscina": { + "version": "5.3.2", + "integrity": "sha512-vv7l/mM7B9WgrhDaudIqv1x6v4LOBRYYmpWGdWushhpbCmVHp11sUB2v4hj1CAeTGXVxuZFz6xOU1RalAiPKfQ==", "dev": true, "license": "MIT", - "optional": true, - "dependencies": { - "ms": "^2.1.1" + "engines": { + "node": ">=20.x" + }, + "optionalDependencies": { + "@napi-rs/nice": "^1.0.4" } }, - "node_modules/probe-image-size/node_modules/iconv-lite": { - "version": "0.4.24", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "node_modules/pkce-challenge": { + "version": "5.0.1", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", "dev": true, "license": "MIT", - "optional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, "engines": { - "node": ">=0.10.0" + "node": ">=16.20.0" } }, - "node_modules/probe-image-size/node_modules/needle": { - "version": "2.9.1", - "integrity": "sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ==", + "node_modules/pkg-dir": { + "version": "4.2.0", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "debug": "^3.2.6", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - }, - "bin": { - "needle": "bin/needle" + "find-up": "^4.0.0" }, "engines": { - "node": ">= 4.4.x" + "node": ">=8" } }, - "node_modules/proc-log": { - "version": "5.0.0", - "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, - "license": "ISC", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=8" } }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/promise-retry": { - "version": "2.0.1", - "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "license": "MIT", "dependencies": { - "err-code": "^2.0.2", - "retry": "^0.12.0" + "p-locate": "^4.1.0" }, "engines": { - "node": ">=10" + "node": ">=8" } }, - "node_modules/proxy-addr": { - "version": "2.0.8", - "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "license": "MIT", "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" + "p-try": "^2.0.0" }, "engines": { - "node": ">= 0.10" + "node": ">=6" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/prr": { - "version": "1.0.1", - "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/punycode": { - "version": "2.3.1", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, "engines": { - "node": ">=6" + "node": ">=8" } }, - "node_modules/pure-rand": { - "version": "7.0.1", - "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], - "license": "MIT" - }, - "node_modules/pvtsutils": { - "version": "1.3.6", - "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", + "node_modules/pkijs": { + "version": "3.4.1", + "integrity": "sha512-Oo/NZcSWccq8KyoG7gLE9fnltgHns+pNCjCAp/WmjsUySi+sX7y4z4Xqu4fVb42CDHzRPl33fjzT15V1wvcyhA==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "peer": true, + "workspaces": [ + "website" + ], "dependencies": { + "@noble/hashes": "1.8.0", + "asn1js": "^3.0.6", + "bytestreamjs": "^2.0.1", + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", "tslib": "^2.8.1" - } - }, - "node_modules/pvutils": { - "version": "1.2.0", - "integrity": "sha512-BbubeCEyTuQjVMakvJQ/Sxbc93F2pwmbsxONT/ZRrwU7Ua38d8unYTwXpTVLAKJ4BDuH9IGztCjQcd/N/39Dvg==", - "dev": true, - "license": "MIT", - "peer": true, + }, "engines": { "node": ">=16.0.0" } }, - "node_modules/qs": { - "version": "6.16.0", - "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", + "node_modules/playwright": { + "version": "1.64.1", + "integrity": "sha512-HCrVhHr5QQuwYoswEUGTb7gGp522yflbv/ST8nF4sFWGuLjQCt17Q/EubpGGSNKYn1tsTCPZxqHhHCfVURu7UA==", "dev": true, - "license": "BSD-3-Clause", + "license": "Apache-2.0", "dependencies": { - "es-define-property": "^1.0.1", - "side-channel": "^1.1.1" + "playwright-core": "1.64.1" + }, + "bin": { + "playwright": "cli.js" }, "engines": { - "node": ">=0.6" + "node": ">=20" + } + }, + "node_modules/playwright-core": { + "version": "1.64.1", + "integrity": "sha512-BC4s8y6SEVA6Q1rSxZTto8hI1KUI3xXlHGZgvGqP4IqPdI/a6FX9H29IDNz7vKo9l21b6LfYosScPL6M3xxyDQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=20" } }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.23", + "integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==", "dev": true, "funding": [ { - "type": "github", - "url": "https://github.com/sponsors/feross" + "type": "opencollective", + "url": "https://opencollective.com/postcss/" }, { - "type": "patreon", - "url": "https://www.patreon.com/feross" + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" }, { - "type": "consulting", - "url": "https://feross.org/support" + "type": "github", + "url": "https://github.com/sponsors/ai" } ], - "license": "MIT" - }, - "node_modules/range-parser": { - "version": "1.3.0", - "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/raw-body": { - "version": "3.0.2", - "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", - "dev": true, "license": "MIT", "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.7.0", - "unpipe": "~1.0.0" + "nanoid": "^3.3.16", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { - "node": ">= 0.10" + "node": "^10 || ^12 || >=14" } }, - "node_modules/readable-stream": { - "version": "3.6.2", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "node_modules/postcss-loader": { + "version": "8.2.0", + "integrity": "sha512-tHX+RkpsXVcc7st4dSdDGliI+r4aAQDuv+v3vFYHixb6YgjreG5AG4SEB0kDK8u2s6htqEEpKlkhSBUTvWKYnA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "cosmiconfig": "^9.0.0", + "jiti": "^2.5.1", + "semver": "^7.6.2" }, "engines": { - "node": ">= 6" + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } } }, - "node_modules/readdirp": { - "version": "5.1.1", - "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", + "node_modules/postcss-media-query-parser": { + "version": "0.2.3", + "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", "dev": true, - "license": "MIT", - "optional": true, + "license": "MIT" + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "dev": true, + "license": "ISC", "peer": true, "engines": { - "node": ">= 20.19.0" + "node": "^10 || ^12 || >= 14" }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" + "peerDependencies": { + "postcss": "^8.1.0" } }, - "node_modules/reflect-metadata": { - "version": "0.2.2", - "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/reflect.getprototypeof": { - "version": "1.0.10", - "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "node_modules/postcss-modules-local-by-default": { + "version": "4.2.0", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.7", - "get-proto": "^1.0.1", - "which-builtin-type": "^1.2.1" + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.1.0" }, "engines": { - "node": ">= 0.4" + "node": "^10 || ^12 || >= 14" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "postcss": "^8.1.0" } }, - "node_modules/regenerate": { - "version": "1.5.0", - "integrity": "sha512-1s+CxXjJ71i7LNVy8nYwg47XV4y6lAnOEhsMtSKq3WpmPfhl9LuKlTqKIn/3yUpE8lBz+W+TT88Bxyhnr0U4yw==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/regenerate-unicode-properties": { - "version": "10.3.0", - "integrity": "sha512-9ns8odR8e9q3otKeHj3DE80jZYKg6zVeZKE+zQmRKXFoVUcuiTGeO6iusDhPRoIQQPVPRvH+SfBhrTud4eczuw==", + "node_modules/postcss-modules-scope": { + "version": "3.2.1", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", "dev": true, - "license": "MIT", + "license": "ISC", "peer": true, "dependencies": { - "regenerate": "^1.4.2" + "postcss-selector-parser": "^7.0.0" }, "engines": { - "node": ">=4" + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" } }, - "node_modules/regex-parser": { - "version": "2.3.1", - "integrity": "sha512-yXLRqatcCuKtVHsWrNg0JL3l1zGfdXeEvDa0bdu4tCDQw0RpMDZsqbkyRTUnKMR0tXF627V2oEWjBEaEdqTwtQ==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.4", - "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", "dev": true, - "license": "MIT", + "license": "ISC", + "peer": true, "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-errors": "^1.3.0", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "set-function-name": "^2.0.2" + "icss-utils": "^5.0.0" }, "engines": { - "node": ">= 0.4" + "node": "^10 || ^12 || >= 14" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "postcss": "^8.1.0" } }, - "node_modules/regexpp": { - "version": "3.2.0", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "node_modules/postcss-safe-parser": { + "version": "7.1.0", + "integrity": "sha512-1WzZxRLaAFwEh6Do+zyGpjWV3nGJNxxhuh7Ubu/q1ICImMgZJnLwhoaEpRbG4pJppp2Y1ncL9ffA2f+LhrefQg==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", "engines": { - "node": ">=8" + "node": ">=18.0" }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" + "peerDependencies": { + "postcss": "^8.4.31" } }, - "node_modules/regexpu-core": { - "version": "6.5.3", - "integrity": "sha512-E8VgUoSK+LMQrLboDxavm76MTBGrQhx9RiNuomRmYnZ10Uw12HrI8fkxOOncktb2hBpzIZ9SV/347euQHG5z+g==", + "node_modules/postcss-selector-parser": { + "version": "7.1.6", + "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "regenerate": "^1.5.0", - "regenerate-unicode-properties": "^10.3.0", - "regjsgen": "^0.8.0", - "regjsparser": "^0.13.0", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.2.1" + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" }, "engines": { "node": ">=4" } }, - "node_modules/regjsgen": { - "version": "0.8.0", - "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true, "license": "MIT", "peer": true }, - "node_modules/regjsparser": { - "version": "0.13.4", - "integrity": "sha512-EWV80YgFaVgFrjGCAsV7oybp4GOxbls5RzBg8OB0nKEVXsBJR1JlV8Di1JhCROg3MzTfe1RI8yitIm1eOs68iA==", - "dev": true, - "license": "BSD-2-Clause", - "peer": true, - "dependencies": { - "jsesc": "~3.1.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "node_modules/powershell-utils": { + "version": "0.1.0", + "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==", "dev": true, "license": "MIT", + "peer": true, "engines": { - "node": ">=0.10.0" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/requires-port": { - "version": "1.0.0", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "node_modules/prelude-ls": { + "version": "1.2.1", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, "license": "MIT", - "peer": true + "engines": { + "node": ">= 0.8.0" + } }, - "node_modules/resolve": { - "version": "1.22.11", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "node_modules/prettier": { + "version": "3.9.10", + "integrity": "sha512-skGHTH1Yb9QvaTC4hWnRTzEYtCpApF0UMBPz+t/0ZPzP8ihefo/btKd+QA7pUE0fEwA1SbS/NmhyEcstdXOIdA==", "dev": true, "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, "bin": { - "resolve": "bin/resolve" + "prettier": "bin/prettier.cjs" }, "engines": { - "node": ">= 0.4" + "node": ">=14" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "node_modules/prettier-linter-helpers": { + "version": "1.0.1", + "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==", "dev": true, "license": "MIT", "dependencies": { - "resolve-from": "^5.0.0" + "fast-diff": "^1.1.2" }, "engines": { - "node": ">=8" + "node": ">=6.0.0" } }, - "node_modules/resolve-cwd/node_modules/resolve-from": { - "version": "5.0.0", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "node_modules/pretty-format": { + "version": "30.5.1", + "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==", "dev": true, "license": "MIT", + "dependencies": { + "@jest/react-is-18": "npm:react-is@^18.3.1", + "@jest/react-is-19": "npm:react-is@^19.2.5", + "@jest/schemas": "30.5.0", + "ansi-styles": "^5.2.0" + }, "engines": { - "node": ">=8" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/resolve-from": { - "version": "4.0.0", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "license": "MIT", - "peer": true, + "node": ">=10" + }, "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/resolve-url-loader": { - "version": "5.0.0", - "integrity": "sha512-uZtduh8/8srhBoMx//5bwqjQ+rfYOUq8zC9NrMUGtjBiGTtFJM42s58/36+hTqeqINcnYe08Nj3LkK9lW4N8Xg==", + "node_modules/primeng": { + "version": "21.1.10", + "integrity": "sha512-1dNoVrrmdif95iuPwcSyzS0MwBy4yrbB/44s4gp+UeoZU11G9+SNFd6P/3tiiun7bAh2vbe4Uf0juy7DMtnRbA==", "dev": true, - "license": "MIT", + "license": "SEE LICENSE IN LICENSE.md", "peer": true, "dependencies": { - "adjust-sourcemap-loader": "^4.0.0", - "convert-source-map": "^1.7.0", - "loader-utils": "^2.0.0", - "postcss": "^8.2.14", - "source-map": "0.6.1" + "@primeuix/motion": "^0.0.10", + "@primeuix/styled": "^0.7.4", + "@primeuix/styles": "^2.0.3", + "@primeuix/utils": "^0.7.2", + "tslib": "^2.3.0" }, - "engines": { - "node": ">=12" + "peerDependencies": { + "@angular/cdk": "^21.0.0", + "@angular/common": "^21.0.0", + "@angular/core": "^21.0.7", + "@angular/forms": "^21.0.0", + "@angular/platform-browser": "^21.0.0", + "@angular/router": "^21.0.0", + "rxjs": "^6.0.0 || ^7.8.1" } }, - "node_modules/resolve-url-loader/node_modules/loader-utils": { - "version": "2.0.4", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "node_modules/probe-image-size": { + "version": "7.4.0", + "integrity": "sha512-cdEprVtZxV+awMde9X+4jILBFYh4CARxVrQaMl4wY4YcPWbul9jntXrIW95NInBDyJwcVUP3U0T6yukN8rMBaQ==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", - "peer": true, + "optional": true, "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" + "lodash.merge": "^4.6.2", + "needle": "^2.5.2", + "stream-parser": "~0.3.1" } }, - "node_modules/resolve-url-loader/node_modules/source-map": { - "version": "0.6.1", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "node_modules/probe-image-size/node_modules/debug": { + "version": "3.2.7", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, - "license": "BSD-3-Clause", - "peer": true, - "engines": { - "node": ">=0.10.0" + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "^2.1.1" } }, - "node_modules/restore-cursor": { - "version": "5.1.0", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "node_modules/probe-image-size/node_modules/iconv-lite": { + "version": "0.4.24", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" + "safer-buffer": ">= 2.1.2 < 3" }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.10.0" } }, - "node_modules/restore-cursor/node_modules/onetime": { - "version": "7.0.0", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "node_modules/probe-image-size/node_modules/needle": { + "version": "2.9.1", + "integrity": "sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "mimic-function": "^5.0.0" + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" }, - "engines": { - "node": ">=18" + "bin": { + "needle": "bin/needle" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/retry": { - "version": "0.12.0", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "dev": true, - "license": "MIT", "engines": { - "node": ">= 4" + "node": ">= 4.4.x" } }, - "node_modules/reusify": { - "version": "1.1.0", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "node_modules/proc-log": { + "version": "6.1.0", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", "dev": true, - "license": "MIT", + "license": "ISC", "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/rfdc": { - "version": "1.4.1", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "node_modules/process-nextick-args": { + "version": "2.0.1", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, - "node_modules/rimraf": { - "version": "3.0.2", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", + "node_modules/proxy-addr": { + "version": "2.0.8", + "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "glob": "^7.1.3" + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" }, - "bin": { - "rimraf": "bin.js" + "engines": { + "node": ">= 0.10" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/rimraf/node_modules/balanced-match": { - "version": "1.0.2", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "node_modules/prr": { + "version": "1.0.1", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, - "node_modules/rimraf/node_modules/brace-expansion": { - "version": "1.1.21", - "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "node_modules/punycode": { + "version": "2.3.1", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "engines": { + "node": ">=6" } }, - "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "node_modules/pure-rand": { + "version": "7.0.1", + "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" }, - "node_modules/rimraf/node_modules/minimatch": { - "version": "3.1.5", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "node_modules/pvtsutils": { + "version": "1.3.6", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", "dev": true, - "license": "ISC", + "license": "MIT", + "peer": true, "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" + "tslib": "^2.8.1" } }, - "node_modules/rolldown": { - "version": "1.0.0-beta.47", - "integrity": "sha512-Mid74GckX1OeFAOYz9KuXeWYhq3xkXbMziYIC+ULVdUzPTG9y70OBSBQDQn9hQP8u/AfhuYw1R0BSg15nBI4Dg==", + "node_modules/pvutils": { + "version": "1.2.0", + "integrity": "sha512-BbubeCEyTuQjVMakvJQ/Sxbc93F2pwmbsxONT/ZRrwU7Ua38d8unYTwXpTVLAKJ4BDuH9IGztCjQcd/N/39Dvg==", "dev": true, "license": "MIT", + "peer": true, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/qs": { + "version": "6.16.0", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "@oxc-project/types": "=0.96.0", - "@rolldown/pluginutils": "1.0.0-beta.47" - }, - "bin": { - "rolldown": "bin/cli.mjs" + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=0.6" }, - "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-beta.47", - "@rolldown/binding-darwin-arm64": "1.0.0-beta.47", - "@rolldown/binding-darwin-x64": "1.0.0-beta.47", - "@rolldown/binding-freebsd-x64": "1.0.0-beta.47", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.47", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.47", - "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.47", - "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.47", - "@rolldown/binding-linux-x64-musl": "1.0.0-beta.47", - "@rolldown/binding-openharmony-arm64": "1.0.0-beta.47", - "@rolldown/binding-wasm32-wasi": "1.0.0-beta.47", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.47", - "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.47", - "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.47" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/rollup": { - "version": "4.64.6", - "integrity": "sha512-fX1Vy+UNKdXn+xKDnu/HBXbHVr1AY8cSdQj9BU4kLv5aFCMqGpvrVbyUMmjN3DdzWhSAuTbcX8PCNEVaumthYA==", + "node_modules/queue-microtask": { + "version": "1.2.3", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/range-parser": { + "version": "1.3.0", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", "dev": true, "license": "MIT", - "dependencies": { - "@types/estree": "1.0.9" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" + "node": ">= 0.6" }, - "optionalDependencies": { - "@napi-rs/lzma-linux-x64-gnu": "1.5.1", - "@rollup/rollup-android-arm-eabi": "4.64.6", - "@rollup/rollup-android-arm64": "4.64.6", - "@rollup/rollup-darwin-arm64": "4.64.6", - "@rollup/rollup-darwin-x64": "4.64.6", - "@rollup/rollup-freebsd-arm64": "4.64.6", - "@rollup/rollup-freebsd-x64": "4.64.6", - "@rollup/rollup-linux-arm-gnueabihf": "4.64.6", - "@rollup/rollup-linux-arm-musleabihf": "4.64.6", - "@rollup/rollup-linux-arm64-gnu": "4.64.6", - "@rollup/rollup-linux-arm64-musl": "4.64.6", - "@rollup/rollup-linux-loong64-gnu": "4.64.6", - "@rollup/rollup-linux-loong64-musl": "4.64.6", - "@rollup/rollup-linux-ppc64-gnu": "4.64.6", - "@rollup/rollup-linux-ppc64-musl": "4.64.6", - "@rollup/rollup-linux-riscv64-gnu": "4.64.6", - "@rollup/rollup-linux-riscv64-musl": "4.64.6", - "@rollup/rollup-linux-s390x-gnu": "4.64.6", - "@rollup/rollup-linux-x64-gnu": "4.64.6", - "@rollup/rollup-linux-x64-musl": "4.64.6", - "@rollup/rollup-openbsd-x64": "4.64.6", - "@rollup/rollup-openharmony-arm64": "4.64.6", - "@rollup/rollup-win32-arm64-msvc": "4.64.6", - "@rollup/rollup-win32-ia32-msvc": "4.64.6", - "@rollup/rollup-win32-x64-gnu": "4.64.6", - "@rollup/rollup-win32-x64-msvc": "4.64.6", - "fsevents": "~2.3.2" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/rollup-plugin-dts": { - "version": "6.5.1", - "integrity": "sha512-jODTXp3H7MK/Ur/ErtsrQ0G1GvaCmc3du+y5pNrdBMf6d7HlL2Nd/N6TkEr+f75CkUj01zEoEd7y2elH0eHi1Q==", + "node_modules/raw-body": { + "version": "3.0.2", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "dev": true, - "license": "LGPL-3.0-only", + "license": "MIT", "dependencies": { - "@jridgewell/remapping": "^2.3.5", - "@jridgewell/sourcemap-codec": "^1.5.5", - "convert-source-map": "^2.0.0", - "magic-string": "^0.30.21" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" }, "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/Swatinem" - }, - "optionalDependencies": { - "@babel/code-frame": "^8.0.0" - }, - "peerDependencies": { - "@typescript/typescript6": "^6", - "rollup": "^3 || ^4", - "typescript": "^4.5 || ^5 || ^6 || ^7" - }, - "peerDependenciesMeta": { - "@typescript/typescript6": { - "optional": true - } + "node": ">= 0.10" } }, - "node_modules/rollup-plugin-dts/node_modules/@babel/code-frame": { - "version": "8.0.6", - "integrity": "sha512-uMHhvmDmwFiLuiozaoRYTZHqMFuu3fem/X9JY78lohAuLoh8LSGGC8JqUzo2cVuBsBxkjMZNk56CE7ock8Xxkw==", + "node_modules/readable-stream": { + "version": "3.6.2", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "license": "MIT", - "optional": true, + "peer": true, "dependencies": { - "@babel/helper-validator-identifier": "^8.0.6", - "js-tokens": "^10.0.0" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" }, "engines": { - "node": "^22.18.0 || >=24.11.0" + "node": ">= 6" } }, - "node_modules/rollup-plugin-dts/node_modules/@babel/helper-validator-identifier": { - "version": "8.0.6", - "integrity": "sha512-HYx0YmuchRxRSpnjvcNffnPS8DCauKNmnumIuHG3jnQAVewRA7kfJGwF13ayEpSZs7s4R/oBH4eppuXcAQd5JQ==", + "node_modules/readdirp": { + "version": "5.1.1", + "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", "dev": true, "license": "MIT", - "optional": true, "engines": { - "node": "^22.18.0 || >=24.11.0" + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, - "node_modules/rollup-plugin-dts/node_modules/convert-source-map": { - "version": "2.0.0", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "node_modules/reflect-metadata": { + "version": "0.2.2", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", "dev": true, - "license": "MIT" + "license": "Apache-2.0" }, - "node_modules/rollup-plugin-dts/node_modules/js-tokens": { - "version": "10.0.0", - "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", "dev": true, "license": "MIT", - "optional": true + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/rollup-plugin-dts/node_modules/magic-string": { - "version": "0.30.21", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "node_modules/regenerate": { + "version": "1.5.0", + "integrity": "sha512-1s+CxXjJ71i7LNVy8nYwg47XV4y6lAnOEhsMtSKq3WpmPfhl9LuKlTqKIn/3yUpE8lBz+W+TT88Bxyhnr0U4yw==", "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } + "peer": true }, - "node_modules/router": { - "version": "2.2.0", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "node_modules/regenerate-unicode-properties": { + "version": "10.3.0", + "integrity": "sha512-9ns8odR8e9q3otKeHj3DE80jZYKg6zVeZKE+zQmRKXFoVUcuiTGeO6iusDhPRoIQQPVPRvH+SfBhrTud4eczuw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" + "regenerate": "^1.4.2" }, "engines": { - "node": ">= 18" + "node": ">=4" } }, - "node_modules/rrdom": { - "version": "0.1.7", - "integrity": "sha512-ZLd8f14z9pUy2Hk9y636cNv5Y2BMnNEY99wxzW9tD2BLDfe1xFxtLjB4q/xCBYo6HRe0wofzKzjm4JojmpBfFw==", + "node_modules/regex-parser": { + "version": "2.3.1", + "integrity": "sha512-yXLRqatcCuKtVHsWrNg0JL3l1zGfdXeEvDa0bdu4tCDQw0RpMDZsqbkyRTUnKMR0tXF627V2oEWjBEaEdqTwtQ==", "dev": true, "license": "MIT", - "dependencies": { - "rrweb-snapshot": "^2.0.0-alpha.4" - } + "peer": true }, - "node_modules/rrweb": { - "version": "2.0.0-alpha.4", - "integrity": "sha512-wEHUILbxDPcNwkM3m4qgPgXAiBJyqCbbOHyVoNEVBJzHszWEFYyTbrZqUdeb1EfmTRC2PsumCIkVcomJ/xcOzA==", + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", "dev": true, "license": "MIT", "dependencies": { - "@rrweb/types": "^2.0.0-alpha.4", - "@types/css-font-loading-module": "0.0.7", - "@xstate/fsm": "^1.4.0", - "base64-arraybuffer": "^1.0.1", - "fflate": "^0.4.4", - "mitt": "^3.0.0", - "rrdom": "^0.1.7", - "rrweb-snapshot": "^2.0.0-alpha.4" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/rrweb-cssom": { - "version": "0.8.0", - "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", - "dev": true, - "license": "MIT" - }, - "node_modules/rrweb-snapshot": { - "version": "2.1.7", - "integrity": "sha512-exIZ4dxX498CZszs4ji0KKn8lhX9sBPBdD431t0RQOqCTStAHrmdWKK2s8tWSL32iRS+AOafbMuf6TOIZxW6ug==", + "node_modules/regexpp": { + "version": "3.2.0", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "dev": true, "license": "MIT", - "dependencies": { - "postcss": "^8.4.38" + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" } }, - "node_modules/run-applescript": { - "version": "7.1.0", - "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "node_modules/regexpu-core": { + "version": "6.5.3", + "integrity": "sha512-E8VgUoSK+LMQrLboDxavm76MTBGrQhx9RiNuomRmYnZ10Uw12HrI8fkxOOncktb2hBpzIZ9SV/347euQHG5z+g==", "dev": true, "license": "MIT", "peer": true, - "engines": { - "node": ">=18" + "dependencies": { + "regenerate": "^1.5.0", + "regenerate-unicode-properties": "^10.3.0", + "regjsgen": "^0.8.0", + "regjsparser": "^0.13.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.2.1" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=4" } }, - "node_modules/run-parallel": { - "version": "1.2.0", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "node_modules/regjsgen": { + "version": "0.8.0", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], "license": "MIT", + "peer": true + }, + "node_modules/regjsparser": { + "version": "0.13.4", + "integrity": "sha512-EWV80YgFaVgFrjGCAsV7oybp4GOxbls5RzBg8OB0nKEVXsBJR1JlV8Di1JhCROg3MzTfe1RI8yitIm1eOs68iA==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, "dependencies": { - "queue-microtask": "^1.2.2" + "jsesc": "~3.1.0" + }, + "bin": { + "regjsparser": "bin/parser" } }, - "node_modules/rxjs": { - "version": "7.8.2", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" + "node_modules/require-directory": { + "version": "2.1.1", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" } }, - "node_modules/safe-array-concat": { - "version": "1.1.4", - "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", + "node_modules/require-from-string": { + "version": "2.0.2", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true, "license": "MIT", - "dependencies": { - "call-bind": "^1.0.9", - "call-bound": "^1.0.4", - "get-intrinsic": "^1.3.0", - "has-symbols": "^1.1.0", - "isarray": "^2.0.5" - }, "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.10.0" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "node_modules/requires-port": { + "version": "1.0.0", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], "license": "MIT", "peer": true }, - "node_modules/safe-push-apply": { - "version": "1.0.0", - "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "node_modules/resolve": { + "version": "1.22.11", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", "dev": true, "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "isarray": "^2.0.5" + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" }, "engines": { "node": ">= 0.4" @@ -20682,2686 +18937,2723 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/safe-regex-test": { - "version": "1.1.0", - "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "node_modules/resolve-cwd": { + "version": "3.0.0", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-regex": "^1.2.1" + "resolve-from": "^5.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "node_modules/sass": { - "version": "1.97.3", - "integrity": "sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg==", + "node_modules/resolve-from": { + "version": "4.0.0", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/resolve-url-loader": { + "version": "5.0.0", + "integrity": "sha512-uZtduh8/8srhBoMx//5bwqjQ+rfYOUq8zC9NrMUGtjBiGTtFJM42s58/36+hTqeqINcnYe08Nj3LkK9lW4N8Xg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "chokidar": "^4.0.0", - "immutable": "^5.0.2", - "source-map-js": ">=0.6.2 <2.0.0" - }, - "bin": { - "sass": "sass.js" + "adjust-sourcemap-loader": "^4.0.0", + "convert-source-map": "^1.7.0", + "loader-utils": "^2.0.0", + "postcss": "^8.2.14", + "source-map": "0.6.1" }, "engines": { - "node": ">=14.0.0" - }, - "optionalDependencies": { - "@parcel/watcher": "^2.4.1" + "node": ">=12" } }, - "node_modules/sass-loader": { - "version": "16.0.7", - "integrity": "sha512-w6q+fRHourZ+e+xA1kcsF27iGM6jdB8teexYCfdUw0sYgcDNeZESnDNT9sUmmPm3ooziwUJXGwZJSTF3kOdBfA==", + "node_modules/resolve-url-loader/node_modules/loader-utils": { + "version": "2.0.4", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "neo-async": "^2.6.2" + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" }, "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "@rspack/core": "0.x || ^1.0.0 || ^2.0.0-0", - "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", - "sass": "^1.3.0", - "sass-embedded": "*", - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true - }, - "node-sass": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "webpack": { - "optional": true - } + "node": ">=8.9.0" } }, - "node_modules/sass/node_modules/chokidar": { - "version": "4.0.3", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "node_modules/resolve-url-loader/node_modules/source-map": { + "version": "0.6.1", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", "dev": true, "license": "MIT", "dependencies": { - "readdirp": "^4.0.1" + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" }, "engines": { - "node": ">= 14.16.0" + "node": ">=18" }, "funding": { - "url": "https://paulmillr.com/funding/" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/sass/node_modules/readdirp": { - "version": "4.1.2", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "node_modules/restore-cursor/node_modules/onetime": { + "version": "7.0.0", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", "dev": true, "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, "engines": { - "node": ">= 14.18.0" + "node": ">=18" }, "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/sax": { - "version": "1.6.1", - "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", + "node_modules/reusify": { + "version": "1.1.0", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "dev": true, - "license": "BlueOak-1.0.0", - "optional": true, + "license": "MIT", "engines": { - "node": ">=11.0.0" + "iojs": ">=1.0.0", + "node": ">=0.10.0" } }, - "node_modules/saxes": { - "version": "6.0.0", - "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "node_modules/rfdc": { + "version": "1.4.1", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", "dev": true, - "license": "ISC", - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=v12.22.7" - } + "license": "MIT" }, - "node_modules/schema-utils": { - "version": "4.5.0", - "integrity": "sha512-zJlMCZ0cAR5p/Y4oVpRoqioDMJcGxaXRrQ/4rP4WyR84vc5z/DolXdbvXeDpTwbtocDFr2rhPqHPErDCUtz2kA==", + "node_modules/rimraf": { + "version": "3.0.2", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, - "license": "MIT", - "peer": true, + "license": "ISC", "dependencies": { - "@types/json-schema": "^7.0.15", - "ajv": "^8.20.0", - "ajv-formats": "^3.0.1", - "ajv-keywords": "^5.1.0" + "glob": "^7.1.3" }, - "engines": { - "node": ">= 10.13.0" + "bin": { + "rimraf": "bin.js" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/schema-utils/node_modules/ajv": { - "version": "8.20.0", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "node_modules/rimraf/node_modules/balanced-match": { + "version": "1.0.2", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } + "license": "MIT" }, - "node_modules/select-hose": { - "version": "2.0.0", - "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.21", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", - "peer": true + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } }, - "node_modules/selfsigned": { - "version": "5.5.0", - "integrity": "sha512-ftnu3TW4+3eBfLRFnDEkzGxSF/10BJBkaLJuBHZX0kiPS7bRdlpZGu6YGt4KngMkdTwJE6MbjavFpqHvqVt+Ew==", + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, - "license": "MIT", - "peer": true, + "license": "ISC", "dependencies": { - "@peculiar/x509": "^1.14.2", - "pkijs": "^3.3.3" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { - "node": ">=18" + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/semver": { - "version": "7.7.4", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.5", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "dependencies": { + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=10" + "node": "*" } }, - "node_modules/send": { - "version": "1.2.1", - "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "node_modules/rolldown": { + "version": "1.0.0-rc.4", + "integrity": "sha512-V2tPDUrY3WSevrvU2E41ijZlpF+5PbZu4giH+VpNraaadsJGHa4fR6IFwsocVwEXDoAdIv5qgPPxgrvKAOIPtA==", "dev": true, "license": "MIT", "dependencies": { - "debug": "^4.4.3", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.1", - "mime-types": "^3.0.2", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.2" + "@oxc-project/types": "=0.113.0", + "@rolldown/pluginutils": "1.0.0-rc.4" + }, + "bin": { + "rolldown": "bin/cli.mjs" }, "engines": { - "node": ">= 18" + "node": "^20.19.0 || >=22.12.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.4", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.4", + "@rolldown/binding-darwin-x64": "1.0.0-rc.4", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.4", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.4", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.4", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.4", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.4", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.4", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.4", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.4", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.4", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.4" } }, - "node_modules/serialize-javascript": { - "version": "7.1.2", - "integrity": "sha512-GL2BWwVa6JydKO6l/ljVgjAZF4QJ3S7dWDWi53s5GZT8PJzD2fNxi67HANQGKAqPrwEpL5ba7gUBGi0Ls/sEoQ==", + "node_modules/rollup": { + "version": "4.64.6", + "integrity": "sha512-fX1Vy+UNKdXn+xKDnu/HBXbHVr1AY8cSdQj9BU4kLv5aFCMqGpvrVbyUMmjN3DdzWhSAuTbcX8PCNEVaumthYA==", "dev": true, - "license": "BSD-3-Clause", - "peer": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, "engines": { - "node": ">=20.0.0" + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.64.6", + "@rollup/rollup-android-arm64": "4.64.6", + "@rollup/rollup-darwin-arm64": "4.64.6", + "@rollup/rollup-darwin-x64": "4.64.6", + "@rollup/rollup-freebsd-arm64": "4.64.6", + "@rollup/rollup-freebsd-x64": "4.64.6", + "@rollup/rollup-linux-arm-gnueabihf": "4.64.6", + "@rollup/rollup-linux-arm-musleabihf": "4.64.6", + "@rollup/rollup-linux-arm64-gnu": "4.64.6", + "@rollup/rollup-linux-arm64-musl": "4.64.6", + "@rollup/rollup-linux-loong64-gnu": "4.64.6", + "@rollup/rollup-linux-loong64-musl": "4.64.6", + "@rollup/rollup-linux-ppc64-gnu": "4.64.6", + "@rollup/rollup-linux-ppc64-musl": "4.64.6", + "@rollup/rollup-linux-riscv64-gnu": "4.64.6", + "@rollup/rollup-linux-riscv64-musl": "4.64.6", + "@rollup/rollup-linux-s390x-gnu": "4.64.6", + "@rollup/rollup-linux-x64-gnu": "4.64.6", + "@rollup/rollup-linux-x64-musl": "4.64.6", + "@rollup/rollup-openbsd-x64": "4.64.6", + "@rollup/rollup-openharmony-arm64": "4.64.6", + "@rollup/rollup-win32-arm64-msvc": "4.64.6", + "@rollup/rollup-win32-ia32-msvc": "4.64.6", + "@rollup/rollup-win32-x64-gnu": "4.64.6", + "@rollup/rollup-win32-x64-msvc": "4.64.6", + "fsevents": "~2.3.2" } }, - "node_modules/serve-index": { - "version": "1.9.2", - "integrity": "sha512-KDj11HScOaLmrPxl70KYNW1PksP4Nb/CLL2yvC+Qd2kHMPEEpfc4Re2e4FOay+bC/+XQl/7zAcWON3JVo5v3KQ==", + "node_modules/rollup-plugin-dts": { + "version": "6.5.1", + "integrity": "sha512-jODTXp3H7MK/Ur/ErtsrQ0G1GvaCmc3du+y5pNrdBMf6d7HlL2Nd/N6TkEr+f75CkUj01zEoEd7y2elH0eHi1Q==", "dev": true, - "license": "MIT", - "peer": true, + "license": "LGPL-3.0-only", "dependencies": { - "accepts": "~1.3.8", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.8.0", - "mime-types": "~2.1.35", - "parseurl": "~1.3.3" + "@jridgewell/remapping": "^2.3.5", + "@jridgewell/sourcemap-codec": "^1.5.5", + "convert-source-map": "^2.0.0", + "magic-string": "^0.30.21" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/Swatinem" + }, + "optionalDependencies": { + "@babel/code-frame": "^8.0.0" }, - "engines": { - "node": ">= 0.8.0" + "peerDependencies": { + "@typescript/typescript6": "^6", + "rollup": "^3 || ^4", + "typescript": "^4.5 || ^5 || ^6 || ^7" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "peerDependenciesMeta": { + "@typescript/typescript6": { + "optional": true + } } }, - "node_modules/serve-index/node_modules/accepts": { - "version": "1.3.8", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "node_modules/rollup-plugin-dts/node_modules/@babel/code-frame": { + "version": "8.0.6", + "integrity": "sha512-uMHhvmDmwFiLuiozaoRYTZHqMFuu3fem/X9JY78lohAuLoh8LSGGC8JqUzo2cVuBsBxkjMZNk56CE7ock8Xxkw==", "dev": true, "license": "MIT", - "peer": true, + "optional": true, "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "@babel/helper-validator-identifier": "^8.0.6", + "js-tokens": "^10.0.0" }, "engines": { - "node": ">= 0.6" + "node": "^22.18.0 || >=24.11.0" } }, - "node_modules/serve-index/node_modules/debug": { - "version": "2.6.9", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/rollup-plugin-dts/node_modules/@babel/helper-validator-identifier": { + "version": "8.0.6", + "integrity": "sha512-HYx0YmuchRxRSpnjvcNffnPS8DCauKNmnumIuHG3jnQAVewRA7kfJGwF13ayEpSZs7s4R/oBH4eppuXcAQd5JQ==", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "ms": "2.0.0" + "optional": true, + "engines": { + "node": "^22.18.0 || >=24.11.0" } }, - "node_modules/serve-index/node_modules/depd": { - "version": "1.1.2", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "node_modules/rollup-plugin-dts/node_modules/convert-source-map": { + "version": "2.0.0", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/rollup-plugin-dts/node_modules/js-tokens": { + "version": "10.0.0", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", "dev": true, "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.6" - } + "optional": true }, - "node_modules/serve-index/node_modules/http-errors": { - "version": "1.8.1", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "node_modules/router": { + "version": "2.2.0", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.1" + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" }, "engines": { - "node": ">= 0.6" + "node": ">= 18" } }, - "node_modules/serve-index/node_modules/mime-db": { - "version": "1.52.0", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "node_modules/rrdom": { + "version": "0.1.7", + "integrity": "sha512-ZLd8f14z9pUy2Hk9y636cNv5Y2BMnNEY99wxzW9tD2BLDfe1xFxtLjB4q/xCBYo6HRe0wofzKzjm4JojmpBfFw==", "dev": true, "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.6" + "dependencies": { + "rrweb-snapshot": "^2.0.0-alpha.4" } }, - "node_modules/serve-index/node_modules/mime-types": { - "version": "2.1.35", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "node_modules/rrweb": { + "version": "2.0.0-alpha.4", + "integrity": "sha512-wEHUILbxDPcNwkM3m4qgPgXAiBJyqCbbOHyVoNEVBJzHszWEFYyTbrZqUdeb1EfmTRC2PsumCIkVcomJ/xcOzA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" + "@rrweb/types": "^2.0.0-alpha.4", + "@types/css-font-loading-module": "0.0.7", + "@xstate/fsm": "^1.4.0", + "base64-arraybuffer": "^1.0.1", + "fflate": "^0.4.4", + "mitt": "^3.0.0", + "rrdom": "^0.1.7", + "rrweb-snapshot": "^2.0.0-alpha.4" } }, - "node_modules/serve-index/node_modules/ms": { - "version": "2.0.0", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/rrweb-snapshot": { + "version": "2.1.7", + "integrity": "sha512-exIZ4dxX498CZszs4ji0KKn8lhX9sBPBdD431t0RQOqCTStAHrmdWKK2s8tWSL32iRS+AOafbMuf6TOIZxW6ug==", "dev": true, "license": "MIT", - "peer": true + "dependencies": { + "postcss": "^8.4.38" + } }, - "node_modules/serve-index/node_modules/negotiator": { - "version": "0.6.3", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "node_modules/run-applescript": { + "version": "7.1.0", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", "dev": true, "license": "MIT", "peer": true, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/serve-index/node_modules/statuses": { - "version": "1.5.0", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "node_modules/run-parallel": { + "version": "1.2.0", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.6" + "dependencies": { + "queue-microtask": "^1.2.2" } }, - "node_modules/serve-static": { - "version": "2.2.1", - "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "node_modules/rxjs": { + "version": "7.8.2", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.4", + "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", "dev": true, "license": "MIT", "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" }, "engines": { - "node": ">= 18" + "node": ">=0.4" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/set-function-length": { - "version": "1.2.2", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "node_modules/safe-buffer": { + "version": "5.2.1", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } + "peer": true }, - "node_modules/set-function-name": { - "version": "2.0.2", - "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "node_modules/safe-push-apply": { + "version": "1.0.0", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", "dev": true, "license": "MIT", "dependencies": { - "define-data-property": "^1.1.4", "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" + "isarray": "^2.0.5" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/set-proto": { - "version": "1.0.0", - "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "node_modules/safe-regex-test": { + "version": "1.1.0", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", "dev": true, "license": "MIT", "dependencies": { - "dunder-proto": "^1.0.1", + "call-bound": "^1.0.2", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0" + "is-regex": "^1.2.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "node_modules/safer-buffer": { + "version": "2.1.2", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true, - "license": "ISC" + "license": "MIT" }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "node_modules/sass": { + "version": "1.97.3", + "integrity": "sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "kind-of": "^6.0.2" + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" }, "engines": { - "node": ">=8" + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" } }, - "node_modules/shebang-command": { - "version": "2.0.0", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "node_modules/sass-loader": { + "version": "16.0.7", + "integrity": "sha512-w6q+fRHourZ+e+xA1kcsF27iGM6jdB8teexYCfdUw0sYgcDNeZESnDNT9sUmmPm3ooziwUJXGwZJSTF3kOdBfA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "shebang-regex": "^3.0.0" + "neo-async": "^2.6.2" }, "engines": { - "node": ">=8" + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || ^1.0.0 || ^2.0.0-0", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", + "sass": "^1.3.0", + "sass-embedded": "*", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "webpack": { + "optional": true + } } }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "node_modules/sass/node_modules/chokidar": { + "version": "4.0.3", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, "engines": { - "node": ">=8" + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/shell-quote": { - "version": "1.12.0", - "integrity": "sha512-PcByqNyT/38F2kDNi006HAMRJaULuBzq/FOsw3qdZvX/GA9W/jamDaRskgHjubHiftXK5sIFxLNkvrXUwcof6Q==", + "node_modules/sass/node_modules/readdirp": { + "version": "4.1.2", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "dev": true, "license": "MIT", - "peer": true, "engines": { - "node": ">= 0.4" + "node": ">= 14.18.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, - "node_modules/shimmer": { - "version": "1.2.1", - "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==", + "node_modules/sax": { + "version": "1.6.1", + "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", "dev": true, - "license": "BSD-2-Clause" + "license": "BlueOak-1.0.0", + "optional": true, + "engines": { + "node": ">=11.0.0" + } }, - "node_modules/side-channel": { - "version": "1.1.1", - "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "node_modules/saxes": { + "version": "6.0.0", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4", - "side-channel-list": "^1.0.1", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" + "xmlchars": "^2.2.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=v12.22.7" } }, - "node_modules/side-channel-list": { - "version": "1.0.1", - "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "node_modules/schema-utils": { + "version": "4.5.0", + "integrity": "sha512-zJlMCZ0cAR5p/Y4oVpRoqioDMJcGxaXRrQ/4rP4WyR84vc5z/DolXdbvXeDpTwbtocDFr2rhPqHPErDCUtz2kA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4" + "@types/json-schema": "^7.0.15", + "ajv": "^8.20.0", + "ajv-formats": "^3.0.1", + "ajv-keywords": "^5.1.0" }, "engines": { - "node": ">= 0.4" + "node": ">= 10.13.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "node_modules/schema-utils/node_modules/ajv": { + "version": "8.20.0", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "node_modules/select-hose": { + "version": "2.0.0", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/selfsigned": { + "version": "5.5.0", + "integrity": "sha512-ftnu3TW4+3eBfLRFnDEkzGxSF/10BJBkaLJuBHZX0kiPS7bRdlpZGu6YGt4KngMkdTwJE6MbjavFpqHvqVt+Ew==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" + "@peculiar/x509": "^1.14.2", + "pkijs": "^3.3.3" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=18" } }, - "node_modules/signal-exit": { - "version": "4.1.0", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "node_modules/semver": { + "version": "7.7.4", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", - "engines": { - "node": ">=14" + "bin": { + "semver": "bin/semver.js" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">=10" } }, - "node_modules/sigstore": { - "version": "4.1.1", - "integrity": "sha512-endqECJkfhozrXMK5ngu/UAA0xVcVEFdnHJCElGaExypjW+HK5i6zu3NteLoaX/iFbRUbC3+DjttQs0GARr+5w==", + "node_modules/send": { + "version": "1.2.1", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@sigstore/bundle": "^4.0.0", - "@sigstore/core": "^3.2.1", - "@sigstore/protobuf-specs": "^0.5.0", - "@sigstore/sign": "^4.1.1", - "@sigstore/tuf": "^4.0.2", - "@sigstore/verify": "^3.1.1" + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" }, "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/slash": { - "version": "3.0.0", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "node_modules/serialize-javascript": { + "version": "7.1.2", + "integrity": "sha512-GL2BWwVa6JydKO6l/ljVgjAZF4QJ3S7dWDWi53s5GZT8PJzD2fNxi67HANQGKAqPrwEpL5ba7gUBGi0Ls/sEoQ==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.2", + "integrity": "sha512-KDj11HScOaLmrPxl70KYNW1PksP4Nb/CLL2yvC+Qd2kHMPEEpfc4Re2e4FOay+bC/+XQl/7zAcWON3JVo5v3KQ==", "dev": true, "license": "MIT", + "peer": true, + "dependencies": { + "accepts": "~1.3.8", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.8.0", + "mime-types": "~2.1.35", + "parseurl": "~1.3.3" + }, "engines": { - "node": ">=8" + "node": ">= 0.8.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/slice-ansi": { - "version": "8.0.0", - "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", + "node_modules/serve-index/node_modules/accepts": { + "version": "1.3.8", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "ansi-styles": "^6.2.3", - "is-fullwidth-code-point": "^5.1.0" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" }, "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" + "node": ">= 0.6" } }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "6.2.3", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "peer": true, + "dependencies": { + "ms": "2.0.0" } }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" + "node": ">= 0.6" } }, - "node_modules/sockjs": { - "version": "0.3.24", - "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.8.1", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "faye-websocket": "^0.11.3", - "uuid": "^8.3.2", - "websocket-driver": "^0.7.4" + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" } }, - "node_modules/sockjs/node_modules/uuid": { - "version": "8.3.2", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "node_modules/serve-index/node_modules/mime-db": { + "version": "1.52.0", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, "license": "MIT", "peer": true, - "bin": { - "uuid": "dist/bin/uuid" + "engines": { + "node": ">= 0.6" } }, - "node_modules/socks": { - "version": "2.8.10", - "integrity": "sha512-e0VyvkVTwVYViNovRkZ9aodhxVlyoMn7eJhVUPxZ+eK9P/7CBkxvvsBOHqFPEH416726W8tLXXXjKwqgTErrCQ==", + "node_modules/serve-index/node_modules/mime-types": { + "version": "2.1.35", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "ip-address": "^10.1.1", - "smart-buffer": "^4.2.0" + "mime-db": "1.52.0" }, "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" + "node": ">= 0.6" } }, - "node_modules/socks-proxy-agent": { - "version": "8.0.5", - "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true, "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" - }, - "engines": { - "node": ">= 14" - } + "peer": true }, - "node_modules/source-map": { - "version": "0.7.6", - "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "node_modules/serve-index/node_modules/negotiator": { + "version": "0.6.3", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", + "peer": true, "engines": { - "node": ">= 12" + "node": ">= 0.6" } }, - "node_modules/source-map-js": { - "version": "1.2.2", - "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", + "peer": true, "engines": { - "node": ">=0.10.0" + "node": ">= 0.6" } }, - "node_modules/source-map-loader": { - "version": "5.0.0", - "integrity": "sha512-k2Dur7CbSLcAH73sBcIkV5xjPV4SzqO1NJ7+XaQl8if3VODDUj3FNchNGpqgJSKbvUfJuhVdv8K2Eu8/TNl2eA==", + "node_modules/serve-static": { + "version": "2.2.1", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "iconv-lite": "^0.6.3", - "source-map-js": "^1.0.2" + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" }, "engines": { - "node": ">= 18.12.0" + "node": ">= 18" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.72.1" + "url": "https://opencollective.com/express" } }, - "node_modules/source-map-loader/node_modules/iconv-lite": { - "version": "0.6.3", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "node_modules/set-function-length": { + "version": "1.2.2", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, - "node_modules/source-map-support": { - "version": "0.5.21", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "node_modules/set-function-name": { + "version": "2.0.2", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", "dev": true, "license": "MIT", "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.5.0", - "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", - "dev": true, - "license": "CC-BY-3.0" - }, - "node_modules/spdx-expression-parse": { - "version": "4.0.0", - "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" + "node": ">= 0.4" } }, - "node_modules/spdx-license-ids": { - "version": "3.0.24", - "integrity": "sha512-cLS9TtWkIQFyLkJ3/5aFQAOHOSKTlOs/7WDut/XPSdjom1fJhUdkepeJAKXa9Y05+FabHd0sti+Et559vDtkpQ==", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/spdy": { - "version": "4.0.2", - "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "node_modules/set-proto": { + "version": "1.0.0", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "debug": "^4.1.0", - "handle-thing": "^2.0.0", - "http-deceiver": "^1.2.7", - "select-hose": "^2.0.0", - "spdy-transport": "^3.0.0" + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/spdy-transport": { - "version": "3.0.0", - "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "debug": "^4.1.0", - "detect-node": "^2.0.4", - "hpack.js": "^2.1.6", - "obuf": "^1.1.2", - "readable-stream": "^3.0.6", - "wbuf": "^1.7.3" + "node": ">= 0.4" } }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/ssri": { - "version": "12.0.0", - "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==", + "node_modules/setprototypeof": { + "version": "1.2.0", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } + "license": "ISC" }, - "node_modules/stack-utils": { - "version": "2.0.6", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "node_modules/shallow-clone": { + "version": "3.0.1", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "escape-string-regexp": "^2.0.0" + "kind-of": "^6.0.2" }, "engines": { - "node": ">=10" + "node": ">=8" } }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { + "node_modules/shebang-command": { "version": "2.0.0", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, "engines": { "node": ">=8" } }, - "node_modules/statuses": { - "version": "2.0.2", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "node_modules/shebang-regex": { + "version": "3.0.0", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">=8" } }, - "node_modules/stdin-discarder": { - "version": "0.3.2", - "integrity": "sha512-eCPu1qRxPVkl5605OTWF8Wz40b4Mf45NY5LQmVPQ599knfs5QhASUm9GbJ5BDMDOXgrnh0wyEdvzmL//YMlw0A==", + "node_modules/shell-quote": { + "version": "1.12.0", + "integrity": "sha512-PcByqNyT/38F2kDNi006HAMRJaULuBzq/FOsw3qdZvX/GA9W/jamDaRskgHjubHiftXK5sIFxLNkvrXUwcof6Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { - "node": ">=18" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/stop-iteration-iterator": { - "version": "1.1.0", - "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "node_modules/shimmer": { + "version": "1.2.1", + "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/side-channel": { + "version": "1.1.1", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "internal-slot": "^1.1.0" + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/stream-parser": { - "version": "0.3.1", - "integrity": "sha512-bJ/HgKq41nlKvlhccD5kaCr/P+Hu0wPNKPJOH7en+YrJu/9EgqUF+88w5Jb6KNcjOFMhfX4B2asfeAtIGuHObQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "debug": "2" - } - }, - "node_modules/stream-parser/node_modules/debug": { - "version": "2.6.9", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/side-channel-list": { + "version": "1.0.1", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "ms": "2.0.0" + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/stream-parser/node_modules/ms": { - "version": "2.0.0", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "node_modules/side-channel-map": { + "version": "1.0.1", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "safe-buffer": "~5.2.0" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/string-length": { - "version": "4.0.2", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "dev": true, "license": "MIT", "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" }, "engines": { - "node": ">=10" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/string-width": { - "version": "8.3.0", - "integrity": "sha512-ZbmZM0JCihQN91dWnxoipT2KOEyHqEyfRXUyjuRhW8b/xnqPDoq4gWEVApTVa9db2wN8mmoikgFBbjh71+cGeQ==", + "node_modules/signal-exit": { + "version": "4.1.0", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.5.0", - "strip-ansi": "^7.1.2" - }, + "license": "ISC", "engines": { - "node": ">=20" + "node": ">=14" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/sigstore": { + "version": "4.1.1", + "integrity": "sha512-endqECJkfhozrXMK5ngu/UAA0xVcVEFdnHJCElGaExypjW+HK5i6zu3NteLoaX/iFbRUbC3+DjttQs0GARr+5w==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.2.1", + "@sigstore/protobuf-specs": "^0.5.0", + "@sigstore/sign": "^4.1.1", + "@sigstore/tuf": "^4.0.2", + "@sigstore/verify": "^3.1.1" }, "engines": { - "node": ">=8" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { + "node_modules/slash": { "version": "3.0.0", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "6.4.0", - "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", + "node_modules/slice-ansi": { + "version": "8.0.0", + "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", "dev": true, "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.3", + "is-fullwidth-code-point": "^5.1.0" + }, "engines": { - "node": ">=12" + "node": ">=20" }, "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "7.2.0", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/string.prototype.trim": { - "version": "1.2.11", - "integrity": "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==", + "node_modules/smart-buffer": { + "version": "4.2.0", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", "dev": true, "license": "MIT", - "dependencies": { - "call-bind": "^1.0.9", - "call-bound": "^1.0.4", - "define-data-property": "^1.1.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.24.2", - "es-object-atoms": "^1.1.2", - "has-property-descriptors": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 6.0.0", + "npm": ">= 3.0.0" } }, - "node_modules/string.prototype.trimend": { - "version": "1.0.10", - "integrity": "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==", + "node_modules/sockjs": { + "version": "0.3.24", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "call-bind": "^1.0.9", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" } }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.8", - "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "node_modules/sockjs/node_modules/uuid": { + "version": "8.3.2", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", "dev": true, "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peer": true, + "bin": { + "uuid": "dist/bin/uuid" } }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/socks": { + "version": "2.8.10", + "integrity": "sha512-e0VyvkVTwVYViNovRkZ9aodhxVlyoMn7eJhVUPxZ+eK9P/7CBkxvvsBOHqFPEH416726W8tLXXXjKwqgTErrCQ==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "ip-address": "^10.1.1", + "smart-buffer": "^4.2.0" }, "engines": { - "node": ">=8" + "node": ">= 10.0.0", + "npm": ">= 3.0.0" } }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" }, "engines": { - "node": ">=8" + "node": ">= 14" } }, - "node_modules/strip-bom": { - "version": "4.0.0", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "node_modules/source-map": { + "version": "0.7.6", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "engines": { - "node": ">=8" + "node": ">= 12" } }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "node_modules/source-map-js": { + "version": "1.2.2", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "engines": { - "node": ">=6" + "node": ">=0.10.0" } }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "node_modules/source-map-loader": { + "version": "5.0.0", + "integrity": "sha512-k2Dur7CbSLcAH73sBcIkV5xjPV4SzqO1NJ7+XaQl8if3VODDUj3FNchNGpqgJSKbvUfJuhVdv8K2Eu8/TNl2eA==", "dev": true, "license": "MIT", + "peer": true, + "dependencies": { + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.2" + }, "engines": { - "node": ">=8" + "node": ">= 18.12.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.72.1" } }, - "node_modules/supports-color": { - "version": "7.2.0", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/source-map-loader/node_modules/iconv-lite": { + "version": "0.6.3", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "has-flag": "^4.0.0" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "node_modules/source-map-support": { + "version": "0.5.21", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.10.0" } }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", "dev": true, - "license": "MIT" + "license": "CC-BY-3.0" }, - "node_modules/synckit": { - "version": "0.12.3", - "integrity": "sha512-U+/AXT6LWsxnj2vz9HeUuPWBNgdWxjyIEjEPdbJARC9txmgPzo2VEdqnFo4hHaNyMPtycyVJrSaQYeQv/PoyyA==", + "node_modules/spdx-expression-parse": { + "version": "4.0.0", + "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", "dev": true, "license": "MIT", "dependencies": { - "@dual-bundle/import-meta-resolve": "^4.2.1", - "@pkgr/core": "^0.3.6" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/synckit" + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" } }, - "node_modules/tapable": { - "version": "2.3.3", - "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "node_modules/spdx-license-ids": { + "version": "3.0.24", + "integrity": "sha512-cLS9TtWkIQFyLkJ3/5aFQAOHOSKTlOs/7WDut/XPSdjom1fJhUdkepeJAKXa9Y05+FabHd0sti+Et559vDtkpQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/spdy": { + "version": "4.0.2", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", "dev": true, "license": "MIT", "peer": true, - "engines": { - "node": ">=6" + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" + "engines": { + "node": ">=6.0.0" } }, - "node_modules/tar": { - "version": "7.5.22", - "integrity": "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==", + "node_modules/spdy-transport": { + "version": "3.0.0", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "MIT", + "peer": true, "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" } }, - "node_modules/tar/node_modules/yallist": { - "version": "5.0.0", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "node_modules/sprintf-js": { + "version": "1.0.3", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "BSD-3-Clause" + }, + "node_modules/ssri": { + "version": "13.0.1", + "integrity": "sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, "engines": { - "node": ">=18" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/terser": { - "version": "5.46.0", - "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", + "node_modules/stack-utils": { + "version": "2.0.6", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", "dev": true, - "license": "BSD-2-Clause", - "peer": true, + "license": "MIT", "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.15.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" + "escape-string-regexp": "^2.0.0" }, "engines": { "node": ">=10" } }, - "node_modules/terser-webpack-plugin": { - "version": "5.6.1", - "integrity": "sha512-201R5j+sJpK8nFWwKVyNfZot8FaJbLZDq5evriVzbV1wDtSXDjRUDRfJzHpAaxFDMEhsZL1QkeqM61wgsS3KaQ==", + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "jest-worker": "^27.4.5", - "schema-utils": "^4.3.0", - "terser": "^5.31.1" - }, "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@minify-html/node": { - "optional": true - }, - "@swc/core": { - "optional": true - }, - "@swc/css": { - "optional": true - }, - "@swc/html": { - "optional": true - }, - "clean-css": { - "optional": true - }, - "cssnano": { - "optional": true - }, - "csso": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "html-minifier-terser": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "postcss": { - "optional": true - }, - "uglify-js": { - "optional": true - } + "node": ">=8" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" } }, - "node_modules/terser-webpack-plugin/node_modules/jest-worker": { - "version": "27.5.1", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "node_modules/stdin-discarder": { + "version": "0.3.2", + "integrity": "sha512-eCPu1qRxPVkl5605OTWF8Wz40b4Mf45NY5LQmVPQ599knfs5QhASUm9GbJ5BDMDOXgrnh0wyEdvzmL//YMlw0A==", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, "engines": { - "node": ">= 10.13.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/terser-webpack-plugin/node_modules/supports-color": { - "version": "8.1.1", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "has-flag": "^4.0.0" + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "node": ">= 0.4" } }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "node_modules/stream-parser": { + "version": "0.3.1", + "integrity": "sha512-bJ/HgKq41nlKvlhccD5kaCr/P+Hu0wPNKPJOH7en+YrJu/9EgqUF+88w5Jb6KNcjOFMhfX4B2asfeAtIGuHObQ==", "dev": true, "license": "MIT", - "peer": true + "optional": true, + "dependencies": { + "debug": "2" + } }, - "node_modules/test-exclude": { - "version": "7.0.2", - "integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==", + "node_modules/stream-parser/node_modules/debug": { + "version": "2.6.9", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, - "license": "ISC", + "license": "MIT", + "optional": true, "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^10.4.1", - "minimatch": "^10.2.2" - }, - "engines": { - "node": ">=18" + "ms": "2.0.0" } }, - "node_modules/test-exclude/node_modules/balanced-match": { - "version": "1.0.2", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "node_modules/stream-parser/node_modules/ms": { + "version": "2.0.0", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true }, - "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "2.1.7", - "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "node_modules/string_decoder": { + "version": "1.3.0", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "balanced-match": "^1.0.0" + "safe-buffer": "~5.2.0" } }, - "node_modules/test-exclude/node_modules/glob": { - "version": "10.5.0", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "node_modules/string-length": { + "version": "4.0.2", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">=10" } }, - "node_modules/test-exclude/node_modules/glob/node_modules/minimatch": { - "version": "9.0.9", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "node_modules/string-width": { + "version": "8.3.0", + "integrity": "sha512-ZbmZM0JCihQN91dWnxoipT2KOEyHqEyfRXUyjuRhW8b/xnqPDoq4gWEVApTVa9db2wN8mmoikgFBbjh71+cGeQ==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "brace-expansion": "^2.0.2" + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=20" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/test-exclude/node_modules/lru-cache": { - "version": "10.4.3", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/test-exclude/node_modules/path-scurry": { - "version": "1.11.1", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "MIT", "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=8" } }, - "node_modules/text-table": { - "version": "0.2.0", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, "license": "MIT" }, - "node_modules/thingies": { - "version": "2.6.1", - "integrity": "sha512-cV/CMGTK3M4MlnJ/0At6ismOw/A0EEniDNScajjz/Br3c1sqE72YD01rGpPTKwd27wAxI5Pr+6+0w8yofzFRYw==", + "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, "license": "MIT", - "peer": true, "engines": { - "node": ">=10.18" + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.4.0", + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "^2" + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/thunky": { - "version": "1.1.0", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.2.0", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "license": "MIT", - "peer": true + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "node_modules/string.prototype.trim": { + "version": "1.2.11", + "integrity": "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==", "dev": true, "license": "MIT", "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-object-atoms": "^1.1.2", + "has-property-descriptors": "^1.0.2", + "safe-regex-test": "^1.1.0" }, "engines": { - "node": ">=12.0.0" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/tldts": { - "version": "7.4.18", - "integrity": "sha512-somxA8xKAUzlAxl0TX/jMSYIbRNFIVGnI96pVGJPeMc5BsHmfVXn6mOJT6U++ELnl04eGfzpfcEuZe8WiASkXg==", + "node_modules/string.prototype.trimend": { + "version": "1.0.10", + "integrity": "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "tldts-core": "^7.4.18" + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.2" }, - "bin": { - "tldts": "bin/cli.js" + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/tldts-core": { - "version": "7.4.18", - "integrity": "sha512-KI2PKvCmvQI/xD6ypPqkU0i7oxNXR/O58RLRaHR4svsgVUQ2JE/XC8BtMbG6PSehD27I8sZJRcC1ASnCy6lp3g==", + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", "dev": true, "license": "MIT", - "peer": true + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "node_modules/strip-ansi": { + "version": "6.0.1", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "is-number": "^7.0.0" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=8.0" + "node": ">=8" } }, - "node_modules/toidentifier": { - "version": "1.0.1", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, "engines": { - "node": ">=0.6" + "node": ">=8" } }, - "node_modules/tough-cookie": { - "version": "6.0.2", - "integrity": "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==", + "node_modules/strip-bom": { + "version": "4.0.0", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true, - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "tldts": "^7.0.5" - }, + "license": "MIT", "engines": { - "node": ">=16" + "node": ">=8" } }, - "node_modules/tr46": { - "version": "7.0.0", - "integrity": "sha512-BcDSDW+SIGWVOns1Zm6Yg91q9AEj7eaID0QDY9oIqJD1A8dscNwMcl9Sms2DeuWCNov1X0/oXanHGYQBSLwZsg==", + "node_modules/strip-final-newline": { + "version": "2.0.0", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "punycode": "^2.3.1" - }, "engines": { - "node": "^22.14.0 || >=24.0.0" + "node": ">=6" } }, - "node_modules/tree-dump": { - "version": "1.1.0", - "integrity": "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==", + "node_modules/strip-json-comments": { + "version": "3.1.1", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, - "license": "Apache-2.0", - "peer": true, + "license": "MIT", "engines": { - "node": ">=10.0" + "node": ">=8" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/tree-kill": { - "version": "1.2.2", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "node_modules/supports-color": { + "version": "7.2.0", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", - "peer": true, - "bin": { - "tree-kill": "cli.js" + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/ts-api-utils": { - "version": "2.5.0", - "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, "license": "MIT", "engines": { - "node": ">=18.12" + "node": ">= 0.4" }, - "peerDependencies": { - "typescript": ">=4.8.4" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/ts-jest": { - "version": "29.4.14", - "integrity": "sha512-+943G6wCX5E3VpzTQhBGBFntCmdT+QAkTvUlp03edTONFBvqskuXPhpyX16xdUotO4P5HQKyppldSWjocoltLQ==", + "node_modules/symbol-tree": { + "version": "3.2.4", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/synckit": { + "version": "0.12.3", + "integrity": "sha512-U+/AXT6LWsxnj2vz9HeUuPWBNgdWxjyIEjEPdbJARC9txmgPzo2VEdqnFo4hHaNyMPtycyVJrSaQYeQv/PoyyA==", "dev": true, "license": "MIT", "dependencies": { - "bs-logger": "^0.2.6", - "fast-json-stable-stringify": "^2.1.0", - "handlebars": "^4.7.9", - "json5": "^2.2.3", - "lodash.memoize": "^4.1.2", - "make-error": "^1.3.6", - "semver": "^7.8.5", - "type-fest": "^4.41.0", - "yargs-parser": "^21.1.1" - }, - "bin": { - "ts-jest": "cli.js" + "@dual-bundle/import-meta-resolve": "^4.2.1", + "@pkgr/core": "^0.3.6" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + "node": "^14.18.0 || >=16.0.0" }, - "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <9", - "@jest/transform": "^29.0.0 || ^30.0.0", - "@jest/types": "^29.0.0 || ^30.0.0", - "babel-jest": "^29.0.0 || ^30.0.0", - "jest": "^29.0.0 || ^30.0.0", - "jest-util": "^29.0.0 || ^30.0.0", - "typescript": ">=4.3 <7" + "funding": { + "url": "https://opencollective.com/synckit" + } + }, + "node_modules/tapable": { + "version": "2.3.3", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@jest/transform": { - "optional": true - }, - "@jest/types": { - "optional": true - }, - "babel-jest": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jest-util": { - "optional": true - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "node_modules/ts-jest/node_modules/semver": { - "version": "7.8.5", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "node_modules/tar": { + "version": "7.5.22", + "integrity": "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" } }, - "node_modules/ts-jest/node_modules/type-fest": { - "version": "4.41.0", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", "dev": true, - "license": "(MIT OR CC0-1.0)", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=16" + "node": ">=18" + } + }, + "node_modules/terser": { + "version": "5.46.0", + "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" } }, - "node_modules/ts-node": { - "version": "10.9.2", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "node_modules/terser-webpack-plugin": { + "version": "5.6.1", + "integrity": "sha512-201R5j+sJpK8nFWwKVyNfZot8FaJbLZDq5evriVzbV1wDtSXDjRUDRfJzHpAaxFDMEhsZL1QkeqM61wgsS3KaQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "terser": "^5.31.1" }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" + "webpack": "^5.1.0" }, "peerDependenciesMeta": { + "@minify-html/node": { + "optional": true + }, "@swc/core": { "optional": true }, - "@swc/wasm": { + "@swc/css": { + "optional": true + }, + "@swc/html": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "cssnano": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "html-minifier-terser": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "postcss": { + "optional": true + }, + "uglify-js": { "optional": true } } }, - "node_modules/tsconfig-paths": { - "version": "4.2.0", - "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "node_modules/terser-webpack-plugin/node_modules/jest-worker": { + "version": "27.5.1", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "json5": "^2.2.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" }, "engines": { - "node": ">=6" - } - }, - "node_modules/tsconfig-paths/node_modules/strip-bom": { - "version": "3.0.0", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" + "node": ">= 10.13.0" } }, - "node_modules/tslib": { - "version": "2.8.1", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, - "node_modules/tsyringe": { - "version": "4.10.0", - "integrity": "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==", + "node_modules/terser-webpack-plugin/node_modules/supports-color": { + "version": "8.1.1", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "tslib": "^1.9.3" + "has-flag": "^4.0.0" }, "engines": { - "node": ">= 6.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/tsyringe/node_modules/tslib": { - "version": "1.14.1", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true, - "license": "0BSD", + "license": "MIT", "peer": true }, - "node_modules/tuf-js": { - "version": "4.1.0", - "integrity": "sha512-50QV99kCKH5P/Vs4E2Gzp7BopNV+KzTXqWeaxrfu5IQJBOULRsTIS9seSsOVT8ZnGXzCyx55nYWAi4qJzpZKEQ==", + "node_modules/test-exclude": { + "version": "7.0.2", + "integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@tufjs/models": "4.1.0", - "debug": "^4.4.3", - "make-fetch-happen": "^15.0.1" + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^10.2.2" }, "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": ">=18" } }, - "node_modules/type-check": { - "version": "0.4.0", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "node_modules/test-exclude/node_modules/balanced-match": { + "version": "1.0.2", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } + "license": "MIT" }, - "node_modules/type-detect": { - "version": "4.0.8", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "2.1.7", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", "dev": true, "license": "MIT", - "engines": { - "node": ">=4" + "dependencies": { + "balanced-match": "^1.0.0" } }, - "node_modules/type-fest": { - "version": "0.20.2", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "node_modules/test-exclude/node_modules/glob": { + "version": "10.5.0", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/type-is": { - "version": "2.1.0", - "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "node_modules/test-exclude/node_modules/glob/node_modules/minimatch": { + "version": "9.0.9", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "content-type": "^2.0.0", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" + "brace-expansion": "^2.0.2" }, "engines": { - "node": ">= 18" + "node": ">=16 || 14 >=14.17" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/type-is/node_modules/content-type": { - "version": "2.1.0", - "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "node_modules/test-exclude/node_modules/lru-cache": { + "version": "10.4.3", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } + "license": "ISC" }, - "node_modules/typed-array-buffer": { - "version": "1.0.3", - "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "node_modules/test-exclude/node_modules/path-scurry": { + "version": "1.11.1", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, - "license": "MIT", + "license": "BlueOak-1.0.0", "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.14" + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/typed-array-byte-length": { - "version": "1.0.3", - "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "node_modules/text-table": { + "version": "0.2.0", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/thingies": { + "version": "2.6.1", + "integrity": "sha512-cV/CMGTK3M4MlnJ/0At6ismOw/A0EEniDNScajjz/Br3c1sqE72YD01rGpPTKwd27wAxI5Pr+6+0w8yofzFRYw==", "dev": true, "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.14" - }, + "peer": true, "engines": { - "node": ">= 0.4" + "node": ">=10.18" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "^2" } }, - "node_modules/typed-array-byte-offset": { - "version": "1.0.5", - "integrity": "sha512-0FHJvLPqZ7KJzp17O13jfsAjsqazgrxBu2zEK95PmUz8lv2+GjRuxUInCr2Rk9Dms3ihN21zJ929ZO43yJ95QQ==", + "node_modules/thunky": { + "version": "1.1.0", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, "license": "MIT", "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.9", - "for-each": "^0.3.5", - "gopd": "^1.2.0", - "is-typed-array": "^1.1.15", - "reflect.getprototypeof": "^1.0.10" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "engines": { - "node": ">= 0.4" + "node": ">=12.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/typed-array-length": { - "version": "1.0.8", - "integrity": "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==", + "node_modules/tldts": { + "version": "7.4.18", + "integrity": "sha512-somxA8xKAUzlAxl0TX/jMSYIbRNFIVGnI96pVGJPeMc5BsHmfVXn6mOJT6U++ELnl04eGfzpfcEuZe8WiASkXg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "call-bind": "^1.0.9", - "for-each": "^0.3.5", - "gopd": "^1.2.0", - "is-typed-array": "^1.1.15", - "possible-typed-array-names": "^1.1.0", - "reflect.getprototypeof": "^1.0.10" - }, - "engines": { - "node": ">= 0.4" + "tldts-core": "^7.4.18" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "bin": { + "tldts": "bin/cli.js" } }, - "node_modules/typed-assert": { - "version": "1.0.9", - "integrity": "sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg==", + "node_modules/tldts-core": { + "version": "7.4.18", + "integrity": "sha512-KI2PKvCmvQI/xD6ypPqkU0i7oxNXR/O58RLRaHR4svsgVUQ2JE/XC8BtMbG6PSehD27I8sZJRcC1ASnCy6lp3g==", "dev": true, "license": "MIT", "peer": true }, - "node_modules/typescript": { - "version": "5.9.3", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "node_modules/to-regex-range": { + "version": "5.0.1", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "license": "MIT", + "peer": true, + "dependencies": { + "is-number": "^7.0.0" }, "engines": { - "node": ">=14.17" + "node": ">=8.0" } }, - "node_modules/uglify-js": { - "version": "3.19.3", - "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "node_modules/toidentifier": { + "version": "1.0.1", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "dev": true, - "license": "BSD-2-Clause", - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, + "license": "MIT", "engines": { - "node": ">=0.8.0" + "node": ">=0.6" } }, - "node_modules/unbox-primitive": { - "version": "1.1.0", - "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "node_modules/tough-cookie": { + "version": "6.0.2", + "integrity": "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", + "peer": true, "dependencies": { - "call-bound": "^1.0.3", - "has-bigints": "^1.0.2", - "has-symbols": "^1.1.0", - "which-boxed-primitive": "^1.1.1" + "tldts": "^7.0.5" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=16" } }, - "node_modules/undici": { - "version": "7.16.0", - "integrity": "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==", + "node_modules/tr46": { + "version": "7.0.0", + "integrity": "sha512-BcDSDW+SIGWVOns1Zm6Yg91q9AEj7eaID0QDY9oIqJD1A8dscNwMcl9Sms2DeuWCNov1X0/oXanHGYQBSLwZsg==", "dev": true, "license": "MIT", + "peer": true, + "dependencies": { + "punycode": "^2.3.1" + }, "engines": { - "node": ">=20.18.1" + "node": "^22.14.0 || >=24.0.0" } }, - "node_modules/undici-types": { - "version": "6.21.0", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.1", - "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "node_modules/tree-dump": { + "version": "1.1.0", + "integrity": "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "peer": true, "engines": { - "node": ">=4" + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "node_modules/tree-kill": { + "version": "1.2.2", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", "dev": true, "license": "MIT", "peer": true, - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" + "bin": { + "tree-kill": "cli.js" } }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.2.1", - "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", + "node_modules/ts-api-utils": { + "version": "2.5.0", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, "license": "MIT", - "peer": true, "engines": { - "node": ">=4" + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" } }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.2.0", - "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", + "node_modules/ts-jest": { + "version": "29.4.14", + "integrity": "sha512-+943G6wCX5E3VpzTQhBGBFntCmdT+QAkTvUlp03edTONFBvqskuXPhpyX16xdUotO4P5HQKyppldSWjocoltLQ==", "dev": true, "license": "MIT", - "peer": true, + "dependencies": { + "bs-logger": "^0.2.6", + "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.9", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.8.5", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, "engines": { - "node": ">=4" + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <9", + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", + "typescript": ">=4.3 <7" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jest-util": { + "optional": true + } } }, - "node_modules/unpipe": { - "version": "1.0.0", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "node_modules/ts-jest/node_modules/semver": { + "version": "7.8.5", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, - "license": "MIT", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, "engines": { - "node": ">= 0.8" + "node": ">=10" } }, - "node_modules/unrs-resolver": { - "version": "1.12.2", - "integrity": "sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==", + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.41.0", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", "dev": true, - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "napi-postinstall": "^0.3.4" + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" }, "funding": { - "url": "https://opencollective.com/unrs-resolver" - }, - "optionalDependencies": { - "@unrs/resolver-binding-android-arm-eabi": "1.12.2", - "@unrs/resolver-binding-android-arm64": "1.12.2", - "@unrs/resolver-binding-darwin-arm64": "1.12.2", - "@unrs/resolver-binding-darwin-x64": "1.12.2", - "@unrs/resolver-binding-freebsd-x64": "1.12.2", - "@unrs/resolver-binding-linux-arm-gnueabihf": "1.12.2", - "@unrs/resolver-binding-linux-arm-musleabihf": "1.12.2", - "@unrs/resolver-binding-linux-arm64-gnu": "1.12.2", - "@unrs/resolver-binding-linux-arm64-musl": "1.12.2", - "@unrs/resolver-binding-linux-loong64-gnu": "1.12.2", - "@unrs/resolver-binding-linux-loong64-musl": "1.12.2", - "@unrs/resolver-binding-linux-ppc64-gnu": "1.12.2", - "@unrs/resolver-binding-linux-riscv64-gnu": "1.12.2", - "@unrs/resolver-binding-linux-riscv64-musl": "1.12.2", - "@unrs/resolver-binding-linux-s390x-gnu": "1.12.2", - "@unrs/resolver-binding-linux-x64-gnu": "1.12.2", - "@unrs/resolver-binding-linux-x64-musl": "1.12.2", - "@unrs/resolver-binding-openharmony-arm64": "1.12.2", - "@unrs/resolver-binding-wasm32-wasi": "1.12.2", - "@unrs/resolver-binding-win32-arm64-msvc": "1.12.2", - "@unrs/resolver-binding-win32-ia32-msvc": "1.12.2", - "@unrs/resolver-binding-win32-x64-msvc": "1.12.2" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/update-browserslist-db": { - "version": "1.3.4", - "integrity": "sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A==", + "node_modules/ts-node": { + "version": "10.9.2", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], "license": "MIT", "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" }, "bin": { - "update-browserslist-db": "cli.js" + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" }, "peerDependencies": { - "browserslist": ">= 4.21.0" + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } } }, - "node_modules/uri-js": { - "version": "4.4.1", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "node_modules/tsconfig-paths": { + "version": "4.2.0", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "punycode": "^2.1.0" + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true, "license": "MIT", - "peer": true, "engines": { - "node": ">= 0.4.0" + "node": ">=4" } }, - "node_modules/uuid": { - "version": "9.0.1", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "node_modules/tslib": { + "version": "2.8.1", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tsyringe": { + "version": "4.10.0", + "integrity": "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==", "dev": true, - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" + "peer": true, + "dependencies": { + "tslib": "^1.9.3" + }, + "engines": { + "node": ">= 6.0.0" } }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "node_modules/tsyringe/node_modules/tslib": { + "version": "1.14.1", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true, - "license": "MIT" + "license": "0BSD", + "peer": true }, - "node_modules/v8-to-istanbul": { - "version": "9.3.0", - "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "node_modules/tuf-js": { + "version": "4.1.0", + "integrity": "sha512-50QV99kCKH5P/Vs4E2Gzp7BopNV+KzTXqWeaxrfu5IQJBOULRsTIS9seSsOVT8ZnGXzCyx55nYWAi4qJzpZKEQ==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" + "@tufjs/models": "4.1.0", + "debug": "^4.4.3", + "make-fetch-happen": "^15.0.1" }, "engines": { - "node": ">=10.12.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/v8-to-istanbul/node_modules/convert-source-map": { - "version": "2.0.0", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "node_modules/type-check": { + "version": "0.4.0", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } }, - "node_modules/validate-npm-package-name": { - "version": "6.0.2", - "integrity": "sha512-IUoow1YUtvoBBC06dXs8bR8B9vuA3aJfmQNKMoaPG/OFsPmoQvw8xh+6Ye25Gx9DQhoEom3Pcu9MKHerm/NpUQ==", + "node_modules/type-detect": { + "version": "4.0.8", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true, - "license": "ISC", + "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=4" } }, - "node_modules/vary": { - "version": "1.1.2", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "node_modules/type-fest": { + "version": "0.20.2", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, - "license": "MIT", + "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">= 0.8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/vite": { - "version": "7.2.2", - "integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==", + "node_modules/type-is": { + "version": "2.1.0", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.5.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">= 18" }, "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "lightningcss": "^1.21.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/vite/node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", - "cpu": [ - "ppc64" - ], + "node_modules/type-is/node_modules/content-type": { + "version": "2.1.0", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "aix" - ], "engines": { "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/vite/node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", - "cpu": [ - "arm" - ], + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, "engines": { - "node": ">=18" + "node": ">= 0.4" } }, - "node_modules/vite/node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", - "cpu": [ - "arm64" - ], + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, "engines": { - "node": ">=18" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/vite/node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", - "cpu": [ - "x64" - ], + "node_modules/typed-array-byte-offset": { + "version": "1.0.5", + "integrity": "sha512-0FHJvLPqZ7KJzp17O13jfsAjsqazgrxBu2zEK95PmUz8lv2+GjRuxUInCr2Rk9Dms3ihN21zJ929ZO43yJ95QQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.9", + "for-each": "^0.3.5", + "gopd": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.10" + }, "engines": { - "node": ">=18" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/vite/node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", - "cpu": [ - "arm64" - ], + "node_modules/typed-array-length": { + "version": "1.0.8", + "integrity": "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "call-bind": "^1.0.9", + "for-each": "^0.3.5", + "gopd": "^1.2.0", + "is-typed-array": "^1.1.15", + "possible-typed-array-names": "^1.1.0", + "reflect.getprototypeof": "^1.0.10" + }, "engines": { - "node": ">=18" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/vite/node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", - "cpu": [ - "x64" - ], + "node_modules/typed-assert": { + "version": "1.0.9", + "integrity": "sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "peer": true + }, + "node_modules/typescript": { + "version": "5.9.3", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, "engines": { - "node": ">=18" + "node": ">=14.17" } }, - "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", - "cpu": [ - "arm64" - ], + "node_modules/uglify-js": { + "version": "3.19.3", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", "optional": true, - "os": [ - "freebsd" - ], + "bin": { + "uglifyjs": "bin/uglifyjs" + }, "engines": { - "node": ">=18" + "node": ">=0.8.0" } }, - "node_modules/vite/node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", - "cpu": [ - "x64" - ], + "node_modules/unbox-primitive": { + "version": "1.1.0", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, "engines": { - "node": ">=18" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/vite/node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", - "cpu": [ - "arm" - ], + "node_modules/undici": { + "version": "7.30.1", + "integrity": "sha512-03KrMbjIFDWQR5RNPrSw3bd1r12JpyFQl9/yxjUbJtqkjmllqC5lFjZh6ujeuJCKr8EOQvRW872Oy/az+nLYxg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">=18" + "node": ">=20.18.1" } }, - "node_modules/vite/node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", - "cpu": [ - "arm64" - ], + "node_modules/undici-types": { + "version": "6.21.0", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "peer": true, "engines": { - "node": ">=18" + "node": ">=4" } }, - "node_modules/vite/node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", - "cpu": [ - "ia32" - ], + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "peer": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, "engines": { - "node": ">=18" + "node": ">=4" } }, - "node_modules/vite/node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "cpu": [ - "loong64" - ], + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.1", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "peer": true, "engines": { - "node": ">=18" + "node": ">=4" } }, - "node_modules/vite/node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", - "cpu": [ - "mips64el" - ], + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.2.0", + "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "peer": true, "engines": { - "node": ">=18" + "node": ">=4" } }, - "node_modules/vite/node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", - "cpu": [ - "ppc64" - ], + "node_modules/unpipe": { + "version": "1.0.0", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">=18" + "node": ">= 0.8" } }, - "node_modules/vite/node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", - "cpu": [ - "riscv64" - ], + "node_modules/unrs-resolver": { + "version": "1.12.2", + "integrity": "sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==", "dev": true, + "hasInstallScript": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "dependencies": { + "napi-postinstall": "^0.3.4" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.12.2", + "@unrs/resolver-binding-android-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-x64": "1.12.2", + "@unrs/resolver-binding-freebsd-x64": "1.12.2", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-arm64-musl": "1.12.2", + "@unrs/resolver-binding-linux-loong64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-loong64-musl": "1.12.2", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-musl": "1.12.2", + "@unrs/resolver-binding-linux-s390x-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-musl": "1.12.2", + "@unrs/resolver-binding-openharmony-arm64": "1.12.2", + "@unrs/resolver-binding-wasm32-wasi": "1.12.2", + "@unrs/resolver-binding-win32-arm64-msvc": "1.12.2", + "@unrs/resolver-binding-win32-ia32-msvc": "1.12.2", + "@unrs/resolver-binding-win32-x64-msvc": "1.12.2" } }, - "node_modules/vite/node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", - "cpu": [ - "s390x" + "node_modules/update-browserslist-db": { + "version": "1.3.4", + "integrity": "sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } ], - "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" } }, - "node_modules/vite/node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", - "cpu": [ - "x64" - ], + "node_modules/uri-js": { + "version": "4.4.1", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" } }, - "node_modules/vite/node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", - "cpu": [ - "arm64" - ], + "node_modules/util-deprecate": { + "version": "1.0.2", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } + "peer": true }, - "node_modules/vite/node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", - "cpu": [ - "x64" - ], + "node_modules/utils-merge": { + "version": "1.0.1", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], + "peer": true, "engines": { - "node": ">=18" + "node": ">= 0.4.0" } }, - "node_modules/vite/node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", - "cpu": [ - "arm64" - ], + "node_modules/uuid": { + "version": "9.0.1", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", - "cpu": [ - "x64" + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" ], - "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" + "bin": { + "uuid": "dist/bin/uuid" } }, - "node_modules/vite/node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", - "cpu": [ - "arm64" - ], + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } + "license": "MIT" }, - "node_modules/vite/node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", - "cpu": [ - "x64" - ], + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, "engines": { - "node": ">=18" + "node": ">=10.12.0" } }, - "node_modules/vite/node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", - "cpu": [ - "arm64" - ], + "node_modules/v8-to-istanbul/node_modules/convert-source-map": { + "version": "2.0.0", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } + "license": "MIT" }, - "node_modules/vite/node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", - "cpu": [ - "ia32" - ], + "node_modules/validate-npm-package-name": { + "version": "7.0.2", + "integrity": "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "license": "ISC", "engines": { - "node": ">=18" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/vite/node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", - "cpu": [ - "x64" - ], + "node_modules/vary": { + "version": "1.1.2", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], "engines": { - "node": ">=18" + "node": ">= 0.8" } }, - "node_modules/vite/node_modules/esbuild": { - "version": "0.25.12", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "node_modules/vite": { + "version": "7.3.7", + "integrity": "sha512-AHEzTORMbi7/7aXp7w7KZ5+DCUdSeLsJz0grY8xHpc9DHdYb0vQu6DqJfm2tOw3gDExNgPgnJTXxM6syYFHaCA==", "dev": true, - "hasInstallScript": true, "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0 || ^0.28.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, "bin": { - "esbuild": "bin/esbuild" + "vite": "bin/vite.js" }, "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } } }, "node_modules/w3c-xmlserializer": { @@ -23378,8 +21670,8 @@ } }, "node_modules/watchpack": { - "version": "2.4.4", - "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", + "version": "2.5.1", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", "dev": true, "license": "MIT", "dependencies": { @@ -23407,6 +21699,14 @@ "license": "MIT", "optional": true }, + "node_modules/web-fragments": { + "version": "0.8.2", + "integrity": "sha512-VyvTGiTdnnUuUIHHKNxyuvODl44GCLhiiGNjWHhuuLaTN12ET1ICWX46Boa/ROCcOIff38FYlczPXHDDR8EpGg==", + "dev": true, + "dependencies": { + "htmlrewriter": "npm:wf-htmlrewriter@0.0.13" + } + }, "node_modules/web-vitals": { "version": "4.2.4", "integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==", @@ -23560,6 +21860,19 @@ } } }, + "node_modules/webpack-dev-server/node_modules/@types/express": { + "version": "4.17.25", + "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "^1" + } + }, "node_modules/webpack-dev-server/node_modules/accepts": { "version": "1.3.8", "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", @@ -24458,6 +22771,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/xhr2": { + "version": "0.2.1", + "integrity": "sha512-sID0rrVCqkVNUn8t6xuv9+6FViXjUVXq8H5rWOH2rz9fDNQEd4g0EA2XlcEdJXRz5BMEn4O1pJFdT+z4YHhoWw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/xml-name-validator": { "version": "5.0.0", "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", @@ -24613,8 +22935,8 @@ } }, "node_modules/zod": { - "version": "4.1.13", - "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", + "version": "4.6.6", + "integrity": "sha512-4UmzQUF5fxvfcYHlbjBmejU+w+hbDnWFx4aS8kBx7nPBHcO2SqPbpAG8snbdg+Xse4pRefR98hgjFgIMCI/UhA==", "dev": true, "license": "MIT", "funding": { diff --git a/package.json b/package.json index 0c44ab7..fe83534 100644 --- a/package.json +++ b/package.json @@ -5,34 +5,50 @@ "ng": "ng", "build": "ng build --configuration=production --project=ngx-ui-watchtower", "watch": "ng build ngx-ui-watchtower --watch --configuration development", + "lab": "node projects/watchtower-lab/server/dev.mjs", + "lab:build": "ng build watchtower-lab-fragment && ng build watchtower-lab", + "e2e": "npm run lab:build && playwright test", + "e2e:ui": "npm run lab:build && playwright test --ui", + "smoke": "node projects/watchtower-consumer-smoke/smoke.mjs", "test": "ng test --project=ngx-ui-watchtower", "test:coverage": "ng test --project=ngx-ui-watchtower --coverage --coverage-reporters text-summary --coverage-reporters lcov --json --output-file=./coverage/report.json", "lint": "eslint \"**/*.ts\" --max-warnings=0", "format": "prettier --write \"**/*.{ts,html}\"", "format:check": "prettier --check \"**/*.{ts,html}\"", - "typecheck": "tsc -p projects/ngx-ui-watchtower/tsconfig.lib.json --noEmit && tsc -p projects/ngx-ui-watchtower/tsconfig.spec.json --noEmit && tsc -p projects/ngx-ui-watchtower/rum/tsconfig.lib.json --noEmit && tsc -p projects/ngx-ui-watchtower/rum/tsconfig.spec.json --noEmit" + "typecheck": "tsc -p projects/ngx-ui-watchtower/tsconfig.lib.json --noEmit && tsc -p projects/ngx-ui-watchtower/tsconfig.spec.json --noEmit && tsc -p projects/ngx-ui-watchtower/rum/tsconfig.lib.json --noEmit && tsc -p projects/ngx-ui-watchtower/rum/tsconfig.spec.json --noEmit && tsc -p projects/ngx-ui-watchtower/event-inspector/tsconfig.lib.json --noEmit && tsc -p projects/ngx-ui-watchtower/event-inspector/tsconfig.spec.json --noEmit && tsc -p playwright/tsconfig.json" }, "private": true, "dependencies": { - "@angular/common": "~21.0.9", - "@angular/compiler": "~21.0.9", - "@angular/core": "~21.0.9", - "@angular/platform-browser": "~21.0.9", - "@angular/platform-browser-dynamic": "~21.0.9", + "@angular/common": "~21.2.26", + "@angular/compiler": "~21.2.26", + "@angular/core": "~21.2.26", + "@angular/platform-browser": "~21.2.26", + "@angular/platform-browser-dynamic": "~21.2.26", "rxjs": "~7.8.2", "tslib": "^2.8.1", "zone.js": "~0.16.1" }, "devDependencies": { "@angular-builders/jest": "^21.0.4", - "@angular/build": "~21.0.6", - "@angular/cli": "~21.0.6", - "@angular/compiler-cli": "~21.0.9", + "@angular/animations": "~21.2.26", + "@angular/build": "~21.2.29", + "@angular/cli": "~21.2.29", + "@angular/compiler-cli": "~21.2.26", + "@angular/forms": "~21.2.26", + "@angular/platform-server": "~21.2.26", + "@angular/router": "~21.2.26", + "@angular/ssr": "~21.2.29", + "@axe-core/playwright": "4.14.0", + "@e965/xlsx": "^0.20.3", + "@playwright/test": "^1.64.1", + "@types/express": "^5.0.6", "@types/jest": "^30.0.0", + "@types/lodash-es": "^4.17.12", "@types/node": "^22.10.10", "@typescript-eslint/eslint-plugin": "^8.67.0", "@typescript-eslint/parser": "^8.67.0", "aws-rum-web": "^3.2.1", + "cps-ui-kit": "^21.33.0", "eslint": "^8.57.1", "eslint-config-prettier": "^10.1.8", "eslint-config-standard": "^17.1.0", @@ -41,12 +57,16 @@ "eslint-plugin-prettier": "^5.2.6", "eslint-plugin-promise": "^6.6.0", "eslint-plugin-standard": "^5.0.0", + "express": "^5.3.0", "jest": "^30.3.0", "jest-environment-jsdom": "^30.3.0", "jest-preset-angular": "^17.0.0", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", "ng-packagr": "~21.0.1", "prettier": "^3.4.2", - "typescript": "~5.9.3" + "typescript": "~5.9.3", + "web-fragments": "^0.8.2" }, "engines": { "node": ">=22.0.0" diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..14ba994 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,43 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** Wide enough for the lab beside a pane. */ +const VIEWPORT = { width: 1440, height: 900 }; + +/** + * The Watchtower Lab's suite: the real library and aws-rum-web in Chromium + * and WebKit, against the lab's mock backend. `npm run e2e` builds the lab + * first; its server then renders the shell, serves the fragment and answers + * as the backend, on one port beside `npm run lab`'s. + */ +export default defineConfig({ + testDir: 'playwright', + // The mock backend's state is shared — what it received, its broker mode, + // its telemetries — so tests run one at a time. + workers: 1, + fullyParallel: false, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 1 : 0, + reporter: process.env.CI ? [['list'], ['html', { open: 'never' }]] : 'list', + use: { + baseURL: 'http://localhost:4390', + trace: 'on-first-retry' + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'], viewport: VIEWPORT } + }, + { + name: 'webkit', + use: { ...devices['Desktop Safari'], viewport: VIEWPORT } + } + ], + webServer: { + command: 'node dist/watchtower-lab/server/server.mjs', + env: { PORT: '4390' }, + url: 'http://localhost:4390/__lab/state', + // Never one already running: it would serve whatever build it started + // with, not the one just made. + reuseExistingServer: false + } +}); diff --git a/playwright/a11y.spec.ts b/playwright/a11y.spec.ts new file mode 100644 index 0000000..1e53b56 --- /dev/null +++ b/playwright/a11y.spec.ts @@ -0,0 +1,156 @@ +import AxeBuilder from '@axe-core/playwright'; +import { expect, openLab, Page, press, test } from './lab'; + +/** WCAG 2.2 at levels A and AA. */ +const WCAG = ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'wcag22aa']; + +const PAGES = [ + 'scenarios', + 'bi', + 'logging', + 'identity', + 'privacy', + 'lifecycle', + 'iframe', + 'web-fragment', + 'inspector', + 'wire', + 'settings', + ...[ + 'app-identity', + 'delivery', + 'scenarios', + 'bi-events', + 'logging', + 'privacy', + 'event-inspector', + 'rum-broker', + 'app-monitor', + 'debug-flags', + 'json', + 'effective' + ].map((section) => `settings/${section}`) +]; + +/** Each violation as its rule and where it was found. */ +async function violations(page: Page, include?: string): Promise { + const builder = new AxeBuilder({ page }).withTags(WCAG); + const { violations } = await ( + include ? builder.include(include) : builder + ).analyze(); + return violations.map( + (v) => `${v.id}: ${v.nodes.map((n) => n.target.join(' ')).join(', ')}` + ); +} + +/** The text-to-background contrast of the first element `selector` finds. */ +function contrastOf(page: Page, selector: string): Promise { + return page + .locator(selector) + .first() + .evaluate((el) => { + const rgb = (color: string) => + (color.match(/[\d.]+/g) ?? []).map(Number).slice(0, 4); + let bg: number[] = [255, 255, 255]; + for (let node: Element | null = el; node; node = node.parentElement) { + const [r, g, b, a = 1] = rgb(getComputedStyle(node).backgroundColor); + if (a > 0.5) { + bg = [r, g, b]; + break; + } + } + const luminance = ([r, g, b]: number[]) => + [r, g, b] + .map((v) => v / 255) + .map((v) => (v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4)) + .reduce((sum, v, i) => sum + v * [0.2126, 0.7152, 0.0722][i], 0); + const [l1, l2] = [ + luminance(rgb(getComputedStyle(el).color)), + luminance(bg) + ]; + return (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05); + }); +} + +for (const path of PAGES) { + test(`${path}: no WCAG 2.2 A or AA violations`, async ({ page }) => { + await openLab(page, path); + expect(await violations(page)).toEqual([]); + }); +} + +test("Settings' search, with its results open: no violations", async ({ + page +}) => { + await openLab(page, 'settings/logging'); + await page.getByRole('combobox', { name: 'Find a setting' }).fill('redact'); + await expect(page.getByRole('option').first()).toBeVisible(); + // Once it has faded in: mid-fade, its text is too faint to measure. + await expect(page.locator('.cps-menu-container')).toHaveCSS('opacity', '1'); + expect(await violations(page)).toEqual([]); +}); + +test('Settings with settings modified and changes not applied, marked in its list, its fields and its bar: no violations', async ({ + page +}) => { + await openLab(page, 'settings/scenarios', { scenarios: { maxSteps: 7 } }); + await page + .locator('cps-switch', { hasText: 'Lifecycle step events' }) + .locator('label.cps-switch') + .click(); + await expect( + page.getByRole('region', { name: 'Changes not applied' }) + ).toBeVisible(); + expect(await violations(page)).toEqual([]); +}); + +test('the Event Inspector with events, rows open, a filter being built with its help, and the Export menu open: no violations, and readable colours', async ({ + page +}) => { + await page.addInitScript(() => + localStorage.setItem( + 'ngx-ui-watchtower.eventInspector', + JSON.stringify({ dock: 'right' }) + ) + ); + await openLab(page, 'inspector'); + await page.locator('.lab-header-button').click(); + await press(page, 'One of each kind'); + await press(page, 'An unknown event type'); + await press(page, 'A 300,000-character payload'); + const timeline = page.locator('uwt-inspector-timeline'); + for (const row of ['lab_huge_payload', '→ RUM']) { + await timeline + .locator('tbody tr', { hasText: row }) + .getByRole('button', { name: 'Expand row' }) + .click(); + } + await page.getByRole('button', { name: 'Add filter' }).click(); + await page.getByText('How filters work').click(); + await expect(timeline).toContainText('Large payload: showing the first'); + await expect(timeline).toContainText('Mirrored from'); + // Past the new-row tint, so contrast is measured on the usual rows. + await expect(page.locator('.uwt-inspector-timeline__fresh')).toHaveCount(0, { + timeout: 10_000 + }); + + expect(await violations(page, 'uwt-inspector-pane')).toEqual([]); + // axe can't always settle contrast on striped rows; measured directly. + for (const tone of ['success', 'warn', 'error']) { + expect( + await contrastOf(page, `.uwt-inspector-timeline__tone--${tone}`) + ).toBeGreaterThanOrEqual(4.5); + } + for (const text of [ + '.uwt-inspector-json__notice', + '.uwt-inspector-timeline__link', + '.uwt-inspector-filter-bar__help summary' + ]) { + expect(await contrastOf(page, text)).toBeGreaterThanOrEqual(4.5); + } + + await timeline.getByRole('button', { name: 'Export' }).click(); + // Once it has faded in: mid-fade, its text is too faint to measure. + await expect(page.locator('.cps-menu-container')).toHaveCSS('opacity', '1'); + expect(await violations(page, '.cps-menu-container')).toEqual([]); +}); diff --git a/playwright/bi.spec.ts b/playwright/bi.spec.ts new file mode 100644 index 0000000..eb35ccd --- /dev/null +++ b/playwright/bi.spec.ts @@ -0,0 +1,63 @@ +import { expect, named, openLab, press, sessionStarted, test } from './lab'; + +test.beforeEach(async ({ page }) => { + await openLab(page, 'bi'); + await sessionStarted(page); +}); + +test('a tracked event arrives as com.uwt.bi', async ({ page, backend }) => { + await press(page, 'Track a click'); + const events = await backend.rumUntil( + (e) => named(e, 'lab_button_clicked').length > 0 + ); + expect(named(events, 'lab_button_clicked')[0].type).toBe('com.uwt.bi'); +}); + +test('two identical events at once arrive as one', async ({ + page, + backend +}) => { + await press(page, 'Fire twice at once'); + // Tracked after it, so once this arrives the pair has had its chance. + await press(page, 'Track a click'); + const events = await backend.rumUntil( + (e) => named(e, 'lab_button_clicked').length > 0 + ); + expect(named(events, 'lab_double_fired')).toHaveLength(1); +}); + +test('clicking again within 400 ms adds nothing; after it, a second event', async ({ + page, + backend +}) => { + // Two clicks, milliseconds apart. + await page + .getByRole('button', { name: 'Track a click', exact: true }) + .dblclick(); + await page.waitForTimeout(600); + await press(page, 'Track a click'); + // Tracked last, so once this arrives the clicks have had their chance. + await press(page, 'Fire twice at once'); + + const events = await backend.rumUntil( + (e) => named(e, 'lab_double_fired').length > 0 + ); + expect(named(events, 'lab_button_clicked')).toHaveLength(2); +}); + +test('an event tied to a feature and a scenario carries both', async ({ + page, + backend +}) => { + await press(page, 'Feature, tied to a scenario'); + const events = await backend.rumUntil( + (e) => + named(e, 'lab_feature_used').length > 0 && + named(e, 'lab-happy-path').length > 0 + ); + expect(named(events, 'lab_feature_used')[0].details).toMatchObject({ + feature: 'theme-switcher', + scenarioId: named(events, 'lab-happy-path')[0].details.scenarioId, + metadata: { theme: 'dark' } + }); +}); diff --git a/playwright/broker.spec.ts b/playwright/broker.spec.ts new file mode 100644 index 0000000..6405943 --- /dev/null +++ b/playwright/broker.spec.ts @@ -0,0 +1,43 @@ +import { expect, openLab, press, test } from './lab'; + +for (const mode of ['null', 'fail'] as const) { + test(`broker "${mode}": the app carries on, and nothing reaches the dataplane`, async ({ + page, + backend + }) => { + await backend.setBrokerMode(mode); + const sent: string[] = []; + page.on('request', (r) => { + if (r.url().includes('/appmonitors/')) { + sent.push(r.url()); + } + }); + + await openLab(page, 'bi'); + await press(page, 'Track a click'); + // RUM would have sent within two of its 2-second dispatches. + await page.waitForTimeout(4500); + + expect(sent).toEqual([]); + await expect(page.locator('lab-root main h1')).toHaveText('BI events'); + }); +} + +test('broker "short-expiry": credentials are refreshed within seconds, without a tight loop', async ({ + page, + backend +}) => { + await backend.setBrokerMode('short-expiry'); + let inits = 0; + page.on('request', (r) => { + if (new URL(r.url()).pathname === '/rum/init') { + inits++; + } + }); + + await openLab(page, 'bi'); + await page.waitForTimeout(12_000); + + expect(inits).toBeGreaterThanOrEqual(2); + expect(inits).toBeLessThanOrEqual(4); +}); diff --git a/playwright/channel.spec.ts b/playwright/channel.spec.ts new file mode 100644 index 0000000..b191a95 --- /dev/null +++ b/playwright/channel.spec.ts @@ -0,0 +1,51 @@ +import { expect, named, openLab, press, test, watchErrors } from './lab'; + +test("on a shared channel, one tab's host leads for every tab, and another takes over when it closes", async ({ + context, + backend, + errors +}) => { + // Both Event Inspectors open as panes, beside their fragments. + await context.addInitScript(() => + localStorage.setItem( + 'ngx-ui-watchtower.eventInspector', + JSON.stringify({ dock: 'right' }) + ) + ); + const tabs = [await context.newPage(), await context.newPage()]; + tabs.forEach((tab) => watchErrors(tab, errors)); + for (const tab of tabs) { + await openLab(tab, 'web-fragment?channel=shared'); + await expect(tab.locator('web-fragment h1')).toHaveText( + 'Embedded application' + ); + await tab.locator('.lab-header-button').click(); + await expect(tab.locator('uwt-inspector')).toBeVisible(); + } + const [first, second] = tabs; + await expect(first.locator('.lab-header')).toContainText('channel: shared'); + + // The second tab's fragment is heard by the first tab's host, the leader. + await press(second.locator('web-fragment'), 'Track a BI event'); + await expect(first.locator('uwt-inspector')).toContainText( + 'fragment_button_clicked' + ); + await backend.rumUntil((e) => named(e, 'fragment_button_clicked').length > 0); + // Past the inspector's 250 ms refresh: the follower would show it by now. + await second.waitForTimeout(500); + await expect(second.locator('uwt-inspector')).not.toContainText( + 'fragment_button_clicked' + ); + + // With the leader's tab gone, the second tab's host takes over. + await first.close(); + await press(second.locator('web-fragment'), 'Run a scenario'); + await expect(second.locator('uwt-inspector')).toContainText( + 'fragment-journey' + ); + const events = await backend.rumUntil( + (e) => named(e, 'fragment-journey').length > 0 + ); + expect(named(events, 'fragment_button_clicked')).toHaveLength(1); + expect(named(events, 'fragment-journey')).toHaveLength(1); +}); diff --git a/playwright/debug.spec.ts b/playwright/debug.spec.ts new file mode 100644 index 0000000..04adb47 --- /dev/null +++ b/playwright/debug.spec.ts @@ -0,0 +1,60 @@ +import { expect, navigate, openLab, press, settingsSection, test } from './lab'; + +test('debug flags print what is sent, without a reload', async ({ page }) => { + const lines: string[] = []; + page.on('console', (m) => lines.push(m.text())); + await openLab(page, 'settings/debug-flags'); + for (const flag of ['debugScenario', 'debugBI', 'debugLogger']) { + await page + .locator('cps-switch', { hasText: flag }) + .locator('label.cps-switch') + .click(); + await expect + .poll(() => page.evaluate((f) => localStorage.getItem(f), flag)) + .toBe('true'); + } + + await navigate(page, 'BI events'); + await press(page, 'Track a click'); + await navigate(page, 'Scenarios'); + await press(page, 'Complete, with 3 steps'); + await navigate(page, 'Logging'); + await press(page, 'warn'); + + await expect + .poll(() => lines) + .toEqual( + expect.arrayContaining([ + expect.stringMatching( + /^\[watchtower-lab\]\[bi\] lab_button_clicked -> com\.uwt\.bi/ + ), + expect.stringMatching( + /^\[watchtower-lab\]\[scenario\] lab-happy-path .*-> com\.uwt\.scenario/ + ), + expect.stringMatching(/^\[watchtower-lab\]\[lab\] A lab warning/) + ]) + ); +}); + +test('debugLogger with a list prints only those loggers', async ({ page }) => { + const lines: string[] = []; + page.on('console', (m) => lines.push(m.text())); + await page.addInitScript(() => localStorage.setItem('debugLogger', 'noisy')); + await openLab(page, 'logging'); + + await press(page, 'warn'); + await press(page, 'warn (written)'); + await expect + .poll(() => lines) + .toContainEqual( + expect.stringMatching(/^\[watchtower-lab\]\[noisy\] A noisy warning/) + ); + expect(lines.some((l) => l.includes('A lab warning'))).toBe(false); + + // A list isn't "on" for every logger: the Settings switch says so. + await navigate(page, 'Settings'); + await settingsSection(page, 'Debug flags'); + await expect( + page.getByRole('switch', { name: 'debugLogger' }) + ).not.toBeChecked(); +}); diff --git a/playwright/destination.spec.ts b/playwright/destination.spec.ts new file mode 100644 index 0000000..d6bc8e9 --- /dev/null +++ b/playwright/destination.spec.ts @@ -0,0 +1,51 @@ +import { expect, navigate, openLab, press, test } from './lab'; + +test('a custom destination is started once, given every event classified, and RUM never loads', async ({ + page, + backend +}) => { + const rum: string[] = []; + page.on('request', (r) => { + if (/\/rum\/init|\/appmonitors\//.test(r.url())) { + rum.push(r.url()); + } + }); + await openLab(page, 'bi?destination=custom'); + await expect(page.locator('.lab-header')).toContainText( + 'destination: custom' + ); + + await press(page, 'Track a click'); + await navigate(page, 'Scenarios'); + await press(page, 'Fail with an error'); + await navigate(page, 'Logging'); + await press(page, 'error, with an Error'); + + await expect + .poll(async () => (await backend.custom()).map((e) => e.kind).sort()) + .toEqual(['bi', 'error', 'init', 'scenario']); + const types = (await backend.custom()).map((e) => e.eventType); + expect(types).toContain('com.uwt.bi'); + expect(types).toContain('com.uwt.scenario'); + // Logs keep their own route. + expect((await backend.logs()).map((r) => r.message)).toEqual([ + 'Saving the order failed' + ]); + expect(rum).toEqual([]); +}); + +test('a custom destination still gets the scenario abandoned as the page goes', async ({ + page, + backend +}) => { + await openLab(page, 'lifecycle?destination=custom'); + await press(page, 'Start a scenario and reload'); + await expect + .poll( + async () => + (await backend.custom()).find( + (e) => e.payload?.scenarioName === 'lab-abandoned' + )?.payload + ) + .toMatchObject({ status: 'abandoned' }); +}); diff --git a/playwright/fragment.spec.ts b/playwright/fragment.spec.ts new file mode 100644 index 0000000..331a76c --- /dev/null +++ b/playwright/fragment.spec.ts @@ -0,0 +1,167 @@ +import { + expect, + FrameLocator, + Locator, + named, + openLab, + Page, + press, + sessionStarted, + test +} from './lab'; + +const CARLA = '5c8e2f14-1b7d-4a96-9e3c-8d2b7f4a6c10'; + +/** The same fragment build, composed into the shell two ways. */ +const COMPOSITIONS: { + name: string; + path: string; + fragment: (page: Page) => FrameLocator | Locator; +}[] = [ + { + name: 'in a plain iframe', + path: 'iframe', + fragment: (page) => page.frameLocator('iframe.lab-fragment') + }, + { + name: 'as a Web Fragment', + path: 'web-fragment', + // Its page is in the 's shadow root, which locators pierce. + fragment: (page) => page.locator('web-fragment') + } +]; + +for (const { name, path, fragment: locate } of COMPOSITIONS) { + test(`the fragment ${name} sends through the shell: once, under its own name, in the shell's session`, async ({ + page, + backend + }) => { + // The Event Inspector opens as a pane, beside the fragment rather than on it. + await page.addInitScript(() => + localStorage.setItem( + 'ngx-ui-watchtower.eventInspector', + JSON.stringify({ dock: 'right' }) + ) + ); + await openLab(page, path); + await sessionStarted(page); + const fragment = locate(page); + await expect(fragment.locator('h1')).toHaveText('Embedded application'); + // Open first: the Event Inspector shows what arrives from then on. + await page.locator('.lab-header-button').click(); + + await press(fragment, 'Track a BI event'); + await press(fragment, 'Run a scenario'); + await press(fragment, 'Log a warning'); + + const events = await backend.rumUntil( + (e) => + named(e, 'fragment_button_clicked').length > 0 && + named(e, 'fragment-journey').length > 0 + ); + const clicked = named(events, 'fragment_button_clicked'); + expect(clicked).toHaveLength(1); + expect(clicked[0].details.application).toBe('Embedded application'); + expect(named(events, 'fragment-journey')[0].details.status).toBe('success'); + await expect(fragment.locator('p').first()).toContainText( + clicked[0].user.sessionId!.slice(0, 8) + ); + + await expect + .poll(async () => (await backend.logs()).map((r) => r.application)) + .toContain('Embedded application'); + await press(fragment, 'Query its logs'); + await expect(fragment.locator('.lab-readout')).toContainText( + /[1-9]\d* record\(s\), answered by the shell/ + ); + + await expect(page.locator('uwt-inspector')).toContainText( + '↪ Embedded application' + ); + + // Its errors are mirrored to RUM by the shell, named for the fragment. + await press(fragment, 'Log an error'); + await backend.rumUntil((e) => + e.some( + (event) => + event.type === 'com.amazon.rum.js_error_event' && + JSON.stringify(event.details).includes('[Embedded application] Error') + ) + ); + + // Signing in or out in the fragment applies to the shell. + await press(fragment, 'Sign in as Carla'); + await expect(page.locator('.lab-status')).toContainText(CARLA); + await page.locator('.lab-header .lab-user').hover(); + await expect(page.locator('.cps-tooltip-content')).toHaveText( + `Signed in as Carla (${CARLA})` + ); + await press(fragment, 'Sign out'); + await expect(page.locator('.lab-status')).not.toContainText(CARLA); + }); +} + +/** What a person sees of the embedded application's box: its size, edge and background. */ +async function embeddedLook(page: Page, path: string) { + await openLab(page, path); + const viaIframe = path === 'iframe'; + const host = page.locator(viaIframe ? 'iframe.lab-fragment' : 'web-fragment'); + await expect(host).toBeVisible(); + await expect( + (viaIframe ? page.frameLocator('iframe.lab-fragment') : host).locator('h1') + ).toHaveText('Embedded application'); + + const box = (await host.boundingBox())!; + const edge = await host.evaluate((el) => { + const style = getComputedStyle(el); + return { + border: `${style.borderTopWidth} ${style.borderTopStyle} ${style.borderTopColor}`, + radius: style.borderRadius, + background: style.backgroundColor + }; + }); + // In an iframe the app paints its own page over the frame's background. + const own = viaIframe + ? await page + .frameLocator('iframe.lab-fragment') + .locator('body') + .evaluate((body) => getComputedStyle(body).backgroundColor) + : 'rgba(0, 0, 0, 0)'; + return { + width: Math.round(box.width), + height: Math.round(box.height), + border: edge.border, + radius: edge.radius, + background: own === 'rgba(0, 0, 0, 0)' ? edge.background : own + }; +} + +test('the embedded application looks the same in a plain iframe and as a Web Fragment', async ({ + page +}) => { + const iframe = await embeddedLook(page, 'iframe'); + const webFragment = await embeddedLook(page, 'web-fragment'); + + expect(webFragment).toEqual(iframe); +}); + +test('the fragment on its own, with no shell, carries on and sends nothing', async ({ + page +}) => { + // Its own requests, not the backend's: a page that closed before this + // test may still be sending. + const sent: string[] = []; + page.on('request', (r) => { + if (r.method() === 'POST') { + sent.push(r.url()); + } + }); + await page.goto('/fragment-frame/'); + await expect(page.locator('h1')).toHaveText('Embedded application'); + await press(page, 'Track a BI event'); + await press(page, 'Log a warning'); + + await page.waitForTimeout(2000); + await expect(page.locator('p').first()).toContainText('Session — · user —'); + expect(sent).toEqual([]); +}); diff --git a/playwright/frame.spec.ts b/playwright/frame.spec.ts new file mode 100644 index 0000000..b8d65e3 --- /dev/null +++ b/playwright/frame.spec.ts @@ -0,0 +1,202 @@ +import { expect, Locator, openLab, press, sessionStarted, test } from './lab'; + +const PAGES = [ + 'scenarios', + 'bi', + 'logging', + 'identity', + 'privacy', + 'lifecycle', + 'iframe', + 'web-fragment', + 'inspector', + 'wire', + 'settings', + ...[ + 'app-identity', + 'delivery', + 'scenarios', + 'bi-events', + 'logging', + 'privacy', + 'event-inspector', + 'rum-broker', + 'app-monitor', + 'debug-flags', + 'json', + 'effective' + ].map((section) => `settings/${section}`) +]; + +test('a page lists what it sent beside its buttons, ticked once it arrives, and says so', async ({ + page +}) => { + await openLab(page, 'bi'); + await sessionStarted(page); + const results = page.locator('lab-results'); + await expect(results).toContainText('Nothing yet.'); + + await press(page, 'Track a click'); + const sent = results.locator('li', { hasText: 'lab_button_clicked' }); + await expect(sent).toContainText('BI'); + await expect(sent).toContainText('✓ received', { timeout: 10_000 }); + await expect(results.getByRole('status')).toHaveText( + 'BI lab_button_clicked: received.' + ); + + // Beside the buttons, not below them. + const button = (await page + .getByRole('button', { name: 'Track a click' }) + .boundingBox())!; + const column = (await results.boundingBox())!; + expect(column.x).toBeGreaterThan(button.x + button.width); + expect(column.y).toBeLessThan(button.y + button.height); +}); + +test('an error is ticked once it arrives, not for an earlier one with the same message', async ({ + page +}) => { + await openLab(page, 'logging'); + await sessionStarted(page); + const error = page.locator('lab-results li', { + hasText: 'Connection reset' + }); + await press(page, 'error, with an Error'); + await expect(error).toContainText('✓ received', { timeout: 10_000 }); + + // Now nothing leaves the page, so the same error is sent, never received. + await openLab(page, 'logging?destination=noop'); + await press(page, 'error, with an Error'); + await expect(error).toContainText('sent'); + await page.waitForTimeout(3000); + await expect(error).not.toContainText('✓ received'); +}); + +test('a narrower header folds its chips behind Run settings', async ({ + page +}) => { + await page.setViewportSize({ width: 1024, height: 768 }); + await openLab(page, 'bi'); + const header = page.locator('.lab-header'); + const panel = header.locator('.lab-run-settings__panel'); + await expect(header.locator('.lab-chips')).toBeHidden(); + await expect(panel).toBeHidden(); + + await header.getByText('Run settings').click(); + await expect(panel).toBeVisible(); + await expect(panel).toContainText('broker: ok'); +}); + +test('under 900px the nav folds behind a button naming the page', async ({ + page +}) => { + await page.setViewportSize({ width: 800, height: 700 }); + await openLab(page, 'bi'); + const fold = page.getByRole('button', { name: /^Pages/ }); + const links = page.locator('.lab-nav').getByRole('link'); + await expect(fold).toContainText('BI events'); + await expect(links.first()).toBeHidden(); + + await fold.click(); + await expect(fold).toHaveAttribute('aria-expanded', 'true'); + await page.keyboard.press('Escape'); + await expect(links.first()).toBeHidden(); + await expect(fold).toBeFocused(); + + await fold.click(); + await page.locator('.lab-nav').getByRole('link', { name: 'Logging' }).click(); + await expect(page.locator('lab-root main h1')).toHaveText('Logging'); + await expect(links.first()).toBeHidden(); + await expect(fold).toContainText('Logging'); +}); + +test('Escape folds the nav without taking focus from a field elsewhere', async ({ + page +}) => { + await page.setViewportSize({ width: 800, height: 700 }); + await openLab(page, 'logging'); + await page.getByRole('button', { name: /^Pages/ }).click(); + const field = page.locator('lab-logging-page textarea').first(); + await field.focus(); + + await page.keyboard.press('Escape'); + await expect(page.locator('.lab-nav').getByRole('link').first()).toBeHidden(); + await expect(field).toBeFocused(); +}); + +test('on the narrowest phone, the header and Run settings stay on the screen', async ({ + page, + backend +}) => { + await backend.setTelemetries([ + 'errors', + 'performance', + 'interaction', + 'http' + ]); + await page.setViewportSize({ width: 320, height: 700 }); + await openLab(page, 'bi'); + const right = async (locator: Locator) => { + const box = (await locator.boundingBox())!; + return box.x + box.width; + }; + + expect(await right(page.locator('.lab-header-button'))).toBeLessThanOrEqual( + 320 + ); + await page.locator('.lab-run-settings > summary').click(); + expect( + await right(page.locator('.lab-run-settings__panel')) + ).toBeLessThanOrEqual(320); +}); + +test('with the custom destination, what the page sent is ticked once its sink has it', async ({ + page +}) => { + await openLab(page, 'bi?destination=custom'); + await press(page, 'Track a click'); + await expect( + page.locator('lab-results li', { hasText: 'lab_button_clicked' }) + ).toContainText('✓ received', { timeout: 10_000 }); +}); + +test('the status bar stays on one line, as label and value pairs', async ({ + page +}) => { + await page.setViewportSize({ width: 800, height: 700 }); + await openLab(page, 'bi'); + const status = page.locator('.lab-status'); + for (const label of [ + 'RUM events:', + 'from the library:', + 'batches:', + 'logs:', + 'session:', + 'user:' + ]) { + await expect(status).toContainText(label); + } + await sessionStarted(page); + // Every pair on the one line, and inside the bar: the ids give way first. + const bar = (await status.boundingBox())!; + const pairs = await status + .locator('span') + .evaluateAll((spans) => + spans.map((s) => s.getBoundingClientRect()).map((r) => [r.top, r.right]) + ); + for (const [top, right] of pairs) { + expect(top).toBeCloseTo(pairs[0][0], 0); + expect(right).toBeLessThanOrEqual(bar.x + bar.width + 0.5); + } +}); + +test('at phone width, no page is wider than the screen', async ({ page }) => { + await page.setViewportSize({ width: 390, height: 844 }); + for (const path of PAGES) { + await openLab(page, path); + expect( + await page.evaluate(() => document.documentElement.scrollWidth), + path + ).toBeLessThanOrEqual(390); + } +}); diff --git a/playwright/identity.spec.ts b/playwright/identity.spec.ts new file mode 100644 index 0000000..6875a0a --- /dev/null +++ b/playwright/identity.spec.ts @@ -0,0 +1,89 @@ +import { + expect, + navigate, + openLab, + press, + RumEvent, + sessionStarted, + test +} from './lab'; + +const ANNA = '3f2504e0-4f89-41d3-9a0c-0305e82c3301'; +const BEN = '9b2d1c6e-7a53-4d0e-8f3b-2c1a5e6d7f80'; + +const tracked = (events: RumEvent[]) => + events.filter((e) => e.details.eventName === 'lab_button_clicked'); + +test.beforeEach(async ({ page }) => { + await openLab(page, 'identity'); + await sessionStarted(page); + await press(page, 'Sign in as Anna'); + await expect(page.locator('.lab-status')).toContainText(ANNA); +}); + +test('after signing in, events carry the user and signedIn: true', async ({ + page, + backend +}) => { + await navigate(page, 'BI events'); + await press(page, 'Track a click'); + + const events = await backend.rumUntil((e) => tracked(e).length > 0); + expect(tracked(events)[0].user.userId).toBe(ANNA); + expect(tracked(events)[0].metadata.signedIn).toBe(true); + await expect(page.locator('.lab-header')).toContainText('Signed in'); +}); + +test('signing in as someone else moves the next events to them', async ({ + page, + backend +}) => { + await press(page, 'Sign in as Ben'); + await expect(page.locator('.lab-status')).toContainText(BEN); + + await navigate(page, 'BI events'); + await press(page, 'Track a click'); + const events = await backend.rumUntil((e) => tracked(e).length > 0); + expect(tracked(events)[0].user.userId).toBe(BEN); +}); + +for (const signOut of ['Sign out (undefined)', "Sign out ('')"]) { + test(`${signOut}: a new session, and events no longer carry the user`, async ({ + page, + backend + }) => { + const before = await sessionStarted(page); + await press(page, signOut); + await expect(page.locator('.lab-status')).not.toContainText(ANNA); + await expect(page.locator('.lab-status')).not.toContainText(before); + await expect(page.locator('.lab-header')).toContainText('Signed out'); + + // Signing out again, with nobody signed in, changes nothing. + const after = await sessionStarted(page); + await press(page, signOut); + await page.waitForTimeout(1500); + await expect(page.locator('.lab-status')).toContainText(after); + + await navigate(page, 'BI events'); + await press(page, 'Track a click'); + const events = await backend.rumUntil((e) => tracked(e).length > 0); + expect(tracked(events)[0].user.userId).not.toBe(ANNA); + expect(tracked(events)[0].metadata.signedIn).toBe(false); + }); +} + +test('the header shows who is signed in, from any page, and is no link', async ({ + page +}) => { + await navigate(page, 'BI events'); + const user = page.locator('.lab-header .lab-user'); + await expect(user).toHaveText('Signed in'); + await user.hover(); + await expect(page.locator('.cps-tooltip-content')).toHaveText( + `Signed in as Anna (${ANNA})` + ); + // A status, not a link; the run's chips beside it are links into Settings. + await expect( + page.locator('.lab-header').getByRole('link', { name: /Signed (in|out)/ }) + ).toHaveCount(0); +}); diff --git a/playwright/inspector-tools.spec.ts b/playwright/inspector-tools.spec.ts new file mode 100644 index 0000000..bafcc8c --- /dev/null +++ b/playwright/inspector-tools.spec.ts @@ -0,0 +1,435 @@ +import { readFile } from 'node:fs/promises'; +import { expect, Locator, openLab, Page, press, showKind, test } from './lab'; + +/** Opens the lab's Event Inspector page, and the inspector in `dock`. */ +async function openInspector(page: Page, dock = 'right'): Promise { + // Only the first time: a reload keeps what the inspector remembered. + await page.addInitScript((d) => { + const key = 'ngx-ui-watchtower.eventInspector'; + if (!localStorage.getItem(key)) { + localStorage.setItem(key, JSON.stringify({ dock: d })); + } + }, dock); + await openLab(page, 'inspector'); + await page.locator('.lab-header-button').click(); + await expect(page.locator('uwt-inspector')).toBeVisible(); +} + +const timeline = (page: Page) => page.locator('uwt-inspector-timeline'); +/** The timeline's rows, without an open row's detail. */ +const rows = (page: Page) => + timeline(page).locator('tbody tr:not(.cps-table-row-expanded-content)'); + +/** Opens the Export menu and chooses one of its items. */ +async function exportAs( + page: Page, + item: 'Copy JSON' | 'Download JSON' +): Promise { + await timeline(page).getByRole('button', { name: 'Export' }).click(); + await page.getByRole('menuitem', { name: new RegExp(`^${item}`) }).click(); + // cps-menu ignores the button while it is still closing. + await expect(page.getByRole('menu')).toBeHidden(); +} + +test('pause holds new events back until Resume, and Clear empties it', async ({ + page +}) => { + await openInspector(page); + const inspector = page.locator('uwt-inspector'); + const events = timeline(page); + + await page + .locator('cps-switch', { hasText: 'Pause live updates' }) + .locator('label.cps-switch') + .click(); + await press(page, 'An unknown event type'); + await expect(inspector).toContainText('1 new event held back while paused.'); + await expect(events).not.toContainText('com.example.custom'); + + await press(page, 'Resume'); + await expect(events).toContainText('com.example.custom'); + + await page.getByRole('button', { name: 'Clear captured events' }).click(); + await expect(inspector).toContainText('Use the app to generate events'); + await expect(events).not.toContainText('com.example.custom'); +}); + +test('one search and set of filters narrow every kind, until cleared; the kind toggle narrows to one', async ({ + page +}) => { + await openInspector(page); + await press(page, 'One of each kind'); + await press(page, 'An unknown event type'); + const events = timeline(page); + await expect(events).toContainText('com.example.custom'); + await expect(events).toContainText('lab-happy-path'); + + const search = page.getByRole('textbox', { name: 'Search events' }); + await search.fill('each-kind'); + await expect(events).not.toContainText('com.example.custom'); + await expect(events).toContainText('lab_button_clicked'); + await search.fill(''); + await expect(events).toContainText('com.example.custom'); + + await page.getByRole('button', { name: 'Add filter' }).click(); + const builder = page.getByRole('group', { name: 'New filter' }); + await builder.getByRole('combobox', { name: 'Field' }).click(); + await page.getByRole('option', { name: /^Event type/ }).click(); + await builder.getByRole('textbox', { name: 'Value' }).fill('com.example'); + await builder.getByRole('button', { name: 'Add', exact: true }).click(); + await expect(events).not.toContainText('lab_button_clicked'); + await expect(events).not.toContainText('lab-happy-path'); + await expect(events).toContainText('com.example.custom'); + + await page.getByRole('button', { name: 'Clear filters' }).click(); + await expect(events).toContainText('lab_button_clicked'); + + await showKind(page, 'Scenarios'); + await expect(rows(page)).toHaveCount(1); + await expect(events).toContainText('lab-happy-path'); + await showKind(page, 'All'); + await expect(events).toContainText('lab_button_clicked'); +}); + +test('Export saves every kind, or the kind shown, as a versioned file', async ({ + page +}) => { + await openInspector(page); + await press(page, 'One of each kind'); + await expect(timeline(page)).toContainText('lab_button_clicked'); + + let download = page.waitForEvent('download'); + await exportAs(page, 'Download JSON'); + let file = await download; + expect(file.suggestedFilename()).toMatch( + /^ngx-ui-watchtower-events-watchtower-lab-all-\d{8}-\d{6}\.json$/ + ); + const all = JSON.parse(await readFile(await file.path(), 'utf8')); + expect(all).toMatchObject({ formatVersion: 2, section: 'all' }); + expect(all.count.captured).toBeGreaterThan(1); + + await showKind(page, 'BI'); + download = page.waitForEvent('download'); + await exportAs(page, 'Download JSON'); + file = await download; + expect(file.suggestedFilename()).toContain('-bi-'); + const bi = JSON.parse(await readFile(await file.path(), 'utf8')); + expect(bi).toMatchObject({ + formatVersion: 2, + section: 'bi', + count: { captured: 1, droppedOldest: 0 } + }); + expect(bi.events[0].payload.eventName).toBe('lab_button_clicked'); +}); + +test('Copy JSON puts the kind shown on the clipboard', async ({ + page, + context, + browserName +}) => { + test.skip( + browserName !== 'chromium', + 'Playwright grants clipboard access in Chromium only' + ); + await context.grantPermissions(['clipboard-read', 'clipboard-write']); + await openInspector(page); + await press(page, 'One of each kind'); + await expect(timeline(page)).toContainText('lab_button_clicked'); + + await showKind(page, 'BI'); + await exportAs(page, 'Copy JSON'); + await expect( + timeline(page).getByRole('button', { name: 'Copied' }) + ).toBeVisible(); + const copied = await page.evaluate(() => navigator.clipboard.readText()); + expect(JSON.parse(copied)).toMatchObject({ + section: 'bi', + count: { captured: 1, droppedOldest: 0 } + }); +}); + +test('pages 25 rows at a time, and offers the newest when more arrive', async ({ + page +}) => { + await openInspector(page); + await press(page, 'A burst of 600 BI events'); + const events = timeline(page); + await expect(rows(page)).toHaveCount(25); + + await events.getByRole('button', { name: /next page/i }).click(); + // Once the burst is no longer new, so only the next event counts. + await expect(events).not.toContainText('new at the top', { timeout: 5000 }); + await press(page, 'An unknown event type'); + // Exactly one: "11 new" would contain it too. + await expect(events).toContainText(/(^|\D)1 new at the top of the list\./); + await events.getByRole('button', { name: 'Go to newest' }).click(); + await expect(rows(page).first()).toContainText('com.example.custom'); +}); + +test('the transparency slider makes the dialog see-through', async ({ + page +}) => { + await openInspector(page, 'dialog'); + const dialog = page.locator('.cps-dialog'); + await page.getByRole('slider', { name: 'Transparency' }).focus(); + await page.keyboard.press('ArrowRight'); + await expect + .poll(() => dialog.evaluate((e) => getComputedStyle(e).opacity)) + .toBe('0.95'); +}); + +test('a pane resizes by keyboard and by drag, and keeps its size', async ({ + page +}) => { + await openInspector(page); + const pane = page.locator('uwt-inspector-pane'); + const width = async () => Math.round((await pane.boundingBox())!.width); + const start = await width(); + + const edge = page.getByRole('separator', { + name: 'Resize the Event Inspector' + }); + await edge.focus(); + await page.keyboard.press('ArrowLeft'); + await expect.poll(width).toBe(start + 32); + + const box = (await edge.boundingBox())!; + const y = box.y + 200; + await page.mouse.move(box.x + box.width / 2, y); + await page.mouse.down(); + await page.mouse.move(box.x + box.width / 2 - 100, y, { steps: 5 }); + await page.mouse.up(); + await expect.poll(width).toBe(start + 132); + + await page.reload(); + await page.locator('.lab-header-button').click(); + await expect.poll(width).toBe(start + 132); +}); + +test('a blocked window falls back to the dialog, and says so once', async ({ + page +}) => { + const warnings: string[] = []; + page.on('console', (m) => m.type() === 'warning' && warnings.push(m.text())); + await page.addInitScript(() => { + window.open = () => null; + }); + await openLab(page, 'inspector'); + + await press(page, 'Separate window'); + await expect(page.locator('.cps-dialog uwt-inspector')).toBeVisible(); + expect( + warnings.filter((w) => w.includes('blocked the Event Inspector window')) + ).toHaveLength(1); +}); + +test('a mirrored error links to its log event, which links back', async ({ + page +}) => { + await openInspector(page); + await press(page, 'One of each kind'); + await rows(page) + .filter({ hasText: '→ RUM' }) + .getByRole('button', { name: 'Expand row' }) + .click(); + const link = timeline(page).getByRole('button', { + name: /^log event #\d+$/ + }); + const log = (await link.textContent())!.match(/\d+/)![0]; + + await link.click(); + await expect( + page.getByRole('region', { name: `Payload of event #${log}` }) + ).toBeFocused(); + await expect(timeline(page)).toContainText(/Mirrored to RUM as\s*event #\d+/); +}); + +test('an open row filters by its own values; the builder names fields, finds them by path, explains itself and closes on Add', async ({ + page +}) => { + await openInspector(page); + await press(page, 'One of each kind'); + const events = timeline(page); + await rows(page) + .filter({ hasText: 'lab_button_clicked' }) + .getByRole('button', { name: 'Expand row' }) + .click(); + await page + .getByRole('group', { name: 'Filter by' }) + .getByRole('button', { name: 'Event name: lab_button_clicked' }) + .click(); + await expect( + page.getByRole('button', { + name: 'Remove filter eventName equals "lab_button_clicked"' + }) + ).toBeVisible(); + await expect(events).not.toContainText('lab-happy-path'); + await page.getByRole('button', { name: 'Clear filters' }).click(); + + await page.getByRole('button', { name: 'Add filter' }).click(); + const builder = page.getByRole('group', { name: 'New filter' }); + await builder.getByRole('combobox', { name: 'Field' }).click(); + await page.keyboard.type('scenarioName'); + // Found by its path, shown by its name — named in full, as the list may + // not have caught up with the typing yet. + await page + .getByRole('option', { name: 'Scenario scenarioName', exact: true }) + .click(); + await builder.getByRole('textbox', { name: 'Value' }).fill('happy'); + + await page.getByText('How filters work').click(); + await expect(page.getByText('shows that event only')).toBeVisible(); + + await builder.getByRole('button', { name: 'Add', exact: true }).click(); + await expect(builder).toBeHidden(); + await expect(page.getByRole('button', { name: 'Add filter' })).toBeFocused(); + await expect(events).toContainText('lab-happy-path'); + await expect(events).not.toContainText('lab_button_clicked'); +}); + +test('captureBeforeOpen shows what was sent before opening, and keeps on while closed, until Clear', async ({ + page +}) => { + await page.addInitScript(() => + localStorage.setItem( + 'ngx-ui-watchtower.eventInspector', + JSON.stringify({ dock: 'right' }) + ) + ); + await openLab(page, 'inspector', { inspector: { captureBeforeOpen: 50 } }); + await press(page, 'One of each kind'); + + await page.locator('.lab-header-button').click(); + await expect(timeline(page)).toContainText('lab_button_clicked'); + + await press(page, 'Close'); + await press(page, 'An unknown event type'); + await page.locator('.lab-header-button').click(); + await expect(timeline(page)).toContainText('com.example.custom'); + await expect(timeline(page)).toContainText('lab_button_clicked'); + + await page.getByRole('button', { name: 'Clear captured events' }).click(); + await press(page, 'Close'); + await page.locator('.lab-header-button').click(); + await expect(page.locator('uwt-inspector')).toContainText('0 events'); +}); + +test('in forced colours, the dock, the kind shown, Pause and solid buttons keep their state and edges', async ({ + page, + browserName +}) => { + test.skip( + browserName !== 'chromium', + 'Playwright emulates forced colours in Chromium only' + ); + await page.emulateMedia({ forcedColors: 'active' }); + await openInspector(page); + const css = (locator: Locator, property: string, pseudo?: string) => + locator.evaluate( + (e, [p, ps]) => getComputedStyle(e, ps).getPropertyValue(p as string), + [property, pseudo] as const + ); + + // The system's selection colour, as this page resolves it. + const highlight = await page.evaluate(() => { + const probe = document.body.appendChild(document.createElement('div')); + probe.style.cssText = 'forced-color-adjust: none; background: Highlight'; + const colour = getComputedStyle(probe).backgroundColor; + probe.remove(); + return colour; + }); + + const dock = (side: string) => page.locator(`[data-dock="${side}"]`); + expect(await css(dock('right'), 'background-color')).toBe(highlight); + expect(await css(dock('left'), 'background-color')).not.toBe(highlight); + const kinds = page.locator('uwt-inspector cps-button-toggle label'); + expect(await css(kinds.nth(0), 'background-color')).toBe(highlight); + expect(await css(kinds.nth(1), 'background-color')).not.toBe(highlight); + const knob = page.locator('uwt-inspector .cps-switch-slider'); + expect(parseFloat(await css(knob, 'border-top-width', '::before'))).toBe(7); + // The pane's edge shows focus, which its colours alone did. + const edge = page.getByRole('separator', { + name: 'Resize the Event Inspector' + }); + await edge.focus(); + // From the keyboard, as focus shows: one step each way, so no resize. + await page.keyboard.press('ArrowLeft'); + await page.keyboard.press('ArrowRight'); + expect(await css(edge, 'outline-style')).toBe('solid'); + + await page.getByRole('button', { name: 'Add filter' }).click(); + const add = page + .getByRole('group', { name: 'New filter' }) + .getByRole('button', { name: 'Add', exact: true }); + expect(parseFloat(await css(add, 'border-top-width'))).toBeGreaterThan(0); +}); + +for (const dock of ['right', 'bottom', 'dialog']) { + test(`an open list closes when the page scrolls, instead of drifting away from its field, docked ${dock}`, async ({ + page + }) => { + await page.addInitScript( + (d) => + localStorage.setItem( + 'ngx-ui-watchtower.eventInspector', + JSON.stringify({ dock: d }) + ), + dock + ); + // A page long enough to scroll, under the fixed inspector. + await openLab(page, 'scenarios'); + await page.locator('.lab-header-button').click(); + await expect(page.locator('uwt-inspector')).toBeVisible(); + await page.getByRole('button', { name: 'Add filter' }).click(); + const builder = page.getByRole('group', { name: 'New filter' }); + const list = page.locator('.cps-menu-container'); + + for (const opener of [ + builder.getByRole('combobox', { name: 'Field' }), + builder.getByRole('combobox', { name: 'Condition' }), + timeline(page).getByRole('button', { name: 'Export' }) + ]) { + await opener.click(); + await expect(list).toBeVisible(); + await page.mouse.move(300, 400); + // The kit starts listening once the list has finished opening. + await expect(async () => { + await page.mouse.wheel(0, 100); + await expect(list).toBeHidden({ timeout: 300 }); + }).toPass(); + await page.evaluate(() => window.scrollTo(0, 0)); + } + }); +} + +for (const dock of ['right', 'left', 'bottom', 'dialog']) { + test(`wheeling over the inspector, docked ${dock}, does not scroll the app under it`, async ({ + page + }) => { + await page.addInitScript( + (d) => + localStorage.setItem( + 'ngx-ui-watchtower.eventInspector', + JSON.stringify({ dock: d }) + ), + dock + ); + // A page long enough to scroll. + await openLab(page, 'scenarios'); + await page.locator('.lab-header-button').click(); + await expect(page.locator('uwt-inspector')).toBeVisible(); + + const parts = ['.uwt-inspector__body', '.uwt-inspector__header']; + for (const part of parts) { + const found = page.locator(part).first(); + if (!(await found.count())) { + continue; // The dialog's header is cps-dialog's. + } + const box = (await found.boundingBox())!; + await page.mouse.move(box.x + box.width / 2, box.y + 10); + await page.mouse.wheel(0, 200); + await page.waitForTimeout(250); + expect(await page.evaluate(() => scrollY)).toBe(0); + } + }); +} diff --git a/playwright/inspector.spec.ts b/playwright/inspector.spec.ts new file mode 100644 index 0000000..4a15793 --- /dev/null +++ b/playwright/inspector.spec.ts @@ -0,0 +1,542 @@ +import { expect, openLab, Page, press, showKind, test } from './lab'; + +/** Holds Shift and presses `keys` — by default the shortcut, Shift+E+I. */ +async function shortcut(page: Page, keys = ['KeyE', 'KeyI']): Promise { + await page.keyboard.down('Shift'); + for (const key of keys) { + await page.keyboard.press(key); + } + await page.keyboard.up('Shift'); +} + +/** The value of one of the pane's inset variables on ``. */ +const inset = (page: Page, side: string) => + page.evaluate( + (s) => + getComputedStyle(document.documentElement).getPropertyValue( + `--uwt-inspector-inset-${s}` + ), + side + ); + +test.beforeEach(async ({ page }) => { + await openLab(page, 'inspector'); +}); + +test('opens from the header as a dialog, with the mark and the transparency slider', async ({ + page +}) => { + await page.locator('.lab-header-button').click(); + const dialog = page.locator('.cps-dialog'); + await expect(dialog.locator('uwt-inspector')).toBeVisible(); + await expect( + dialog.locator('.cps-dialog-header-icon uwt-inspector-logo') + ).toHaveCount(1); + await expect(dialog.locator('cps-slider')).toBeVisible(); + + // The title sits by the mark on the left, as in a pane or the window. + const header = (await dialog.locator('.cps-dialog-header').boundingBox())!; + const title = (await dialog + .locator('.cps-dialog-header-title') + .boundingBox())!; + expect(title.x - header.x).toBeLessThan(header.width / 4); +}); + +test("Escape closes the dialog from inside it, and leaves it open for the app's own", async ({ + page +}) => { + await page.locator('.lab-header-button').click(); + const inspector = page.locator('.cps-dialog uwt-inspector'); + await expect(inspector).toBeVisible(); + + await page.locator('.lab-nav a').first().focus(); + await page.keyboard.press('Escape'); + await expect(inspector).toBeVisible(); + + await inspector.locator('.uwt-inspector-search input').focus(); + await page.keyboard.press('Escape'); + await expect(page.locator('uwt-inspector')).toHaveCount(0); +}); + +test('a phone-sized screen offers only the dialog and the window, and opens a remembered pane as a dialog', async ({ + page +}) => { + await press(page, 'Right pane'); + await expect(page.locator('uwt-inspector-pane')).toBeVisible(); + await press(page, 'Close'); + + await page.setViewportSize({ width: 390, height: 844 }); + await shortcut(page); + await expect(page.locator('.cps-dialog uwt-inspector')).toBeVisible(); + await expect(page.locator('uwt-inspector-pane')).toHaveCount(0); + // Too narrow for the buttons: a menu, with the same two to offer. + await page.locator('.uwt-inspector-dock__trigger').click(); + await expect(page.locator('[role="menuitemradio"]')).toHaveCount(2); + await page.getByRole('button', { name: 'Close dialog' }).click(); + await expect(page.locator('uwt-inspector')).toHaveCount(0); + + // Kept for a wider screen. + await page.setViewportSize({ width: 1280, height: 720 }); + await shortcut(page); + await expect(page.locator('uwt-inspector-pane')).toBeVisible(); +}); + +test('the Where it opens buttons show where it is, with Close apart', async ({ + page +}) => { + const lab = page.locator('lab-inspector-page'); + const pressed = lab.locator('[aria-pressed="true"]'); + const close = lab.getByRole('button', { name: 'Close', exact: true }); + await expect(pressed).toHaveCount(0); + await expect(close).toBeDisabled(); + + await press(page, 'Right pane'); + await expect(pressed).toHaveText('Right pane'); + await expect(close).toBeEnabled(); + + // Moved from the inspector's own Dock buttons, the page follows. + await page.locator('uwt-inspector [data-dock="bottom"]').click(); + await expect(pressed).toHaveText('Bottom pane'); + + await close.click(); + await expect(pressed).toHaveCount(0); +}); + +test('the Dialog button moves it from a pane to the dialog', async ({ + page +}) => { + await press(page, 'Right pane'); + await expect(page.locator('uwt-inspector-pane')).toBeVisible(); + + await press(page, 'Dialog'); + await expect(page.locator('.cps-dialog uwt-inspector')).toBeVisible(); + await expect(page.locator('uwt-inspector-pane')).toHaveCount(0); +}); + +test('the inspector focused by the shortcut shows the focus ring, not the browser default outline', async ({ + page +}) => { + await shortcut(page); + const inspector = page.locator('.cps-dialog uwt-inspector'); + await expect(inspector).toBeFocused(); + + const look = await inspector.evaluate((el) => { + const style = getComputedStyle(el); + const ring = getComputedStyle(el, '::before'); + return { + outline: style.outlineStyle, + ring: ring.content !== 'none' && parseFloat(ring.borderTopWidth) > 0 + }; + }); + expect(look).toEqual({ outline: 'none', ring: true }); + + // The ring spans the inspector, and must leave its controls clickable. + await inspector.getByRole('button', { name: 'Clear' }).click(); +}); + +test('Shift+E+I opens it, brings focus back, and closes it from inside — never while typing', async ({ + page +}) => { + const inspector = page.locator('uwt-inspector'); + await shortcut(page, ['KeyI']); + await shortcut(page, ['KeyE', 'KeyX', 'KeyI']); + await expect(inspector).toHaveCount(0); + + await shortcut(page); + await expect(inspector).toBeVisible(); + + // Focus lands on the inspector itself, not a field, so it closes next. + const dialog = page.locator('.cps-dialog uwt-inspector'); + await expect(dialog).toBeFocused(); + await shortcut(page); + await expect(inspector).toHaveCount(0); + + // In its search field, Shift+E+I is typing. + await shortcut(page); + const search = page.locator('.uwt-inspector-search input').first(); + await search.focus(); + await shortcut(page); + await expect(search).toHaveValue('EI'); + await expect(inspector).toBeVisible(); + + // From the app, it brings focus back, and then closes. + await page.mouse.click(300, 100); + await expect(dialog).not.toBeFocused(); + await shortcut(page); + await expect(dialog).toBeFocused(); + await shortcut(page); + await expect(inspector).toHaveCount(0); +}); + +for (const { label, side } of [ + { label: 'Left pane', side: 'left' }, + { label: 'Right pane', side: 'right' }, + { label: 'Bottom pane', side: 'bottom' } +]) { + test(`${label}: its own header, no slider, and its size published as --uwt-inspector-inset-${side}`, async ({ + page + }) => { + await press(page, label); + const pane = page.locator('uwt-inspector-pane'); + await expect(pane).toBeVisible(); + await expect( + pane.locator('.uwt-inspector__brand uwt-inspector-logo') + ).toHaveCount(1); + await expect(pane.locator('cps-slider')).toHaveCount(0); + + const box = (await pane.boundingBox())!; + const size = side === 'bottom' ? box.height : box.width; + expect(Math.round(parseFloat(await inset(page, side)))).toBe( + Math.round(size) + ); + + await press(page, 'Close'); + await expect(pane).toHaveCount(0); + expect(await inset(page, side)).toBe(''); + }); +} + +test('the lab opts its fixed header and status bar in to the insets, and out', async ({ + page +}) => { + const right = async (selector: string) => { + const box = (await page.locator(selector).boundingBox())!; + return Math.round(box.x + box.width); + }; + + await press(page, 'Right pane'); + const paneLeft = Math.round( + (await page.locator('uwt-inspector-pane').boundingBox())!.x + ); + expect(await right('.lab-header')).toBe(paneLeft); + + await page.locator('lab-inspector-page cps-switch').click(); + await expect + .poll(() => right('.lab-header')) + .toBe(page.viewportSize()!.width); + + await page.locator('lab-inspector-page cps-switch').click(); + await press(page, 'Bottom pane'); + const paneTop = Math.round( + (await page.locator('uwt-inspector-pane').boundingBox())!.y + ); + const status = (await page.locator('.lab-status').boundingBox())!; + expect(Math.round(status.y + status.height)).toBe(paneTop); +}); + +test('a separate window, with its own header and no slider', async ({ + page +}) => { + const popup = page.waitForEvent('popup'); + await press(page, 'Separate window'); + const window = await popup; + + await expect( + window.locator('.uwt-inspector__brand uwt-inspector-logo') + ).toHaveCount(1); + await expect(window).toHaveTitle('Event Inspector · watchtower-lab'); + // Its own icon, which the browser can draw. + const icon = window.locator('link[rel="icon"]'); + await expect(icon).toHaveCount(1); + expect( + await icon.evaluate( + (link: HTMLLinkElement) => + new Promise((resolve) => { + const image = new Image(); + image.onload = () => resolve(image.naturalWidth); + image.onerror = () => resolve(0); + image.src = link.href; + }) + ) + ).toBeGreaterThan(0); + await expect(window.locator('cps-slider')).toHaveCount(0); +}); + +for (const dock of ['dialog', 'right pane', 'separate window']) { + test(`${dock}: what it shows is said by an info icon in its header`, async ({ + page + }) => { + let where: Page = page; + if (dock === 'dialog') { + await shortcut(page); + } else if (dock === 'right pane') { + await press(page, 'Right pane'); + } else { + const popup = page.waitForEvent('popup'); + await press(page, 'Separate window'); + where = await popup; + } + + const icon = where + .locator('.cps-dialog-header, .uwt-inspector__header') + .getByTestId('cps-info-circle'); + await expect(icon).toBeVisible(); + await expect(where.locator('body')).not.toContainText( + 'telemetry destinations, after redaction' + ); + + await icon.hover(); + const tip = where.locator('.cps-tooltip-content'); + await expect(tip).toContainText('telemetry destinations, after redaction'); + await expect(tip).toContainText("RUM's own page views"); + }); +} + +test('a dialog opened after menus were used still shows its tooltips above itself', async ({ + page +}) => { + await shortcut(page); + // Before cps-ui-kit 21.33, each menu opened left its z-index counter + // higher, and the next dialog was stacked above its tooltips, which sit at + // a fixed 2000. + const exportButton = page + .locator('.cps-dialog') + .getByRole('button', { name: /Export/ }); + for (let i = 0; i < 3; i++) { + await exportButton.click(); + await expect(exportButton).toHaveAttribute('aria-expanded', 'true'); + // The kit listens for a click away only once the menu has opened. + await expect(async () => { + await page.locator('.cps-dialog-header-title').click(); + await expect(exportButton).toHaveAttribute('aria-expanded', 'false', { + timeout: 500 + }); + }).toPass(); + } + await page.getByRole('button', { name: 'Dock right' }).click(); + await expect(page.locator('uwt-inspector-pane')).toBeVisible(); + await expect(page.locator('.cps-dialog')).toHaveCount(0); + await page + .locator('uwt-inspector-pane') + .getByRole('button', { name: 'Dialog', exact: true }) + .click(); + await expect(page.locator('.cps-dialog uwt-inspector')).toBeVisible(); + + await page + .locator('.cps-dialog-header') + .getByTestId('cps-info-circle') + .hover(); + await expect(page.locator('.cps-tooltip-content')).toBeVisible(); + // Seen, not just there: what is at its centre is the tooltip itself. + await expect + .poll(() => + page.evaluate(() => { + const rect = document + .querySelector('.cps-tooltip')! + .getBoundingClientRect(); + return !!document + .elementFromPoint(rect.x + rect.width / 2, rect.y + rect.height / 2) + ?.closest('.cps-tooltip'); + }) + ) + .toBe(true); +}); + +test("a key press after a click on the dialog's header leaves what is inside it clickable", async ({ + page +}) => { + await page.locator('.lab-header-button').click(); + const dialog = page.locator('.cps-dialog'); + await expect(dialog.locator('uwt-inspector')).toBeVisible(); + + // On the header's bare part, to the right of the title. + await dialog + .locator('.cps-dialog-header') + .click({ position: { x: 350, y: 20 } }); + // A key press makes the dialog's focus a keyboard focus, and cps-dialog + // draws a ring around it that would cover, and take the clicks of, all it + // holds. + await page.keyboard.press('ArrowDown'); + await expect(dialog).toBeFocused(); + + await dialog + .getByRole('button', { name: 'Dock right' }) + .click({ timeout: 3000 }); + await expect(page.locator('uwt-inspector-pane')).toBeVisible(); +}); + +test('Esc closes the dialog from its header, not only from its content', async ({ + page +}) => { + await page.locator('.lab-header-button').click(); + const dialog = page.locator('.cps-dialog'); + await expect(dialog.locator('uwt-inspector')).toBeVisible(); + + await dialog + .locator('.cps-dialog-header') + .click({ position: { x: 350, y: 20 } }); + await expect(dialog).toBeFocused(); + await page.keyboard.press('Escape'); + + await expect(page.locator('uwt-inspector')).toHaveCount(0); +}); + +test('an Esc that closes the inspector is marked handled, so notifications stay; one it leaves alone is not', async ({ + page +}) => { + // What cps-ui-kit's notifications read: they close on an Esc that reaches + // the window unhandled. + await page.evaluate(() => { + (window as unknown as { escapes: boolean[] }).escapes = []; + window.addEventListener('keydown', (e) => { + if (e.key === 'Escape') { + (window as unknown as { escapes: boolean[] }).escapes.push( + e.defaultPrevented + ); + } + }); + }); + await page.locator('.lab-header-button').click(); + const dialog = page.locator('.cps-dialog'); + await expect(dialog.locator('uwt-inspector')).toBeVisible(); + + // In the app, outside the inspector: left alone, still open. + await page.locator('.lab-nav a').first().focus(); + await page.keyboard.press('Escape'); + await expect(dialog.locator('uwt-inspector')).toBeVisible(); + + await dialog.locator('.uwt-inspector-search input').focus(); + await page.keyboard.press('Escape'); + await expect(page.locator('uwt-inspector')).toHaveCount(0); + + expect( + await page.evaluate( + () => (window as unknown as { escapes: boolean[] }).escapes + ) + ).toEqual([false, true]); +}); + +test('reloading a separate window reopens the inspector in it: the window stays, the history starts again', async ({ + page +}) => { + const popup = page.waitForEvent('popup'); + await press(page, 'Separate window'); + const window = await popup; + await press(page, 'One of each kind'); + + const status = window.locator('.uwt-inspector__status'); + await expect(status).toContainText(/[1-9]\d* events?/); + // The reload gives it a new, empty document; this mark does not survive it. + await window.evaluate(() => (document.body.dataset.before = 'reload')); + let closed = false; + window.on('close', () => (closed = true)); + + await window.evaluate(() => location.reload()).catch(() => undefined); + + await expect(window.locator('.uwt-inspector__header')).toBeVisible(); + await expect(window.locator('body')).not.toHaveAttribute('data-before', /./); + // As closing it and opening it again: what it had captured is gone… + await expect(status).toContainText(/\b0 events/); + expect(closed).toBe(false); + // …and it captures from here. + await press(page, 'One of each kind'); + await expect(status).toContainText(/[1-9]\d* events?/); + + // Still the inspector's own window: closing it closes the inspector. + await window.close(); + await expect(page.locator('uwt-inspector')).toHaveCount(0); +}); + +test('reopens where it was last, after a reload', async ({ page }) => { + await press(page, 'Right pane'); + await expect(page.locator('uwt-inspector-pane')).toBeVisible(); + + await page.reload(); + await page.locator('.lab-header-button').click(); + const pane = (await page.locator('uwt-inspector-pane').boundingBox())!; + expect(Math.round(pane.x + pane.width)).toBe(page.viewportSize()!.width); +}); + +test('lists what is sent, keeping the newest 500 events of each kind', async ({ + page +}) => { + // A pane, so the page's buttons stay clear of it. + await press(page, 'Right pane'); + await press(page, 'One of each kind'); + await press(page, 'A burst of 600 BI events'); + await press(page, 'A 300,000-character payload'); + await press(page, 'Odd shapes'); + await press(page, 'An unknown event type'); + + const inspector = page.locator('uwt-inspector'); + await expect(inspector).toContainText(/ · \d+ oldest dropped/); + await expect(inspector.locator('cps-button-toggle')).toContainText('BI 500'); +}); + +test('shows every kind in one timeline, and any shape — an unrecognised type, odd values, a huge payload cut short', async ({ + page +}) => { + const timeline = page.locator('uwt-inspector-timeline'); + const row = (text: string) => + timeline.locator('tbody tr:not(.cps-table-row-expanded-content)', { + hasText: text + }); + + await press(page, 'Right pane'); + await press(page, 'One of each kind'); + await press(page, 'An unknown event type'); + await press(page, 'Odd shapes'); + + await expect(row('lab-happy-path')).toContainText('Scenario'); + await expect(row('A warning, from the Event Inspector page')).toContainText( + 'Log' + ); + await expect(row('→ RUM')).toContainText('Error'); + await expect(row('com.example.custom')).toContainText( + 'unrecognised event type' + ); + + await showKind(page, 'Logs'); + await expect(timeline).not.toContainText('lab-happy-path'); + await expect(timeline).toContainText('→ RUM'); + await showKind(page, 'All'); + + const expand = (eventName: string) => + row(eventName).getByRole('button', { name: 'Expand row' }).click(); + + await expand('lab_odd_shapes'); + await expect(timeline).toContainText('Zürich · 東京 · 🚀'); + await expect(timeline.locator('.uwt-inspector-timeline__error')).toHaveCount( + 0 + ); + + await press(page, 'A 300,000-character payload'); + await expand('lab_huge_payload'); + await expect(timeline).toContainText('Large payload: showing the first'); +}); + +test('a pane at its default width shows the dock buttons; dragged narrow, a menu of them', async ({ + page +}) => { + await press(page, 'Right pane'); + const pane = page.locator('uwt-inspector-pane'); + await expect(pane.locator('[data-dock]')).toHaveCount(5); + await expect(pane.locator('.uwt-inspector-dock__trigger')).toHaveCount(0); + + // Down to its minimum, 30rem, by the keyboard: the handle's arrow away + // from the app narrows it, and stops there. + await pane.getByRole('separator').focus(); + for (let i = 0; i < 4; i++) { + await page.keyboard.press('ArrowRight'); + } + await expect + .poll(async () => Math.round((await pane.boundingBox())!.width)) + .toBe(480); + const trigger = pane.locator('.uwt-inspector-dock__trigger'); + await expect(trigger).toBeVisible(); + await expect(pane.locator('[data-dock]')).toHaveCount(0); + await expect(trigger).toHaveAccessibleName('Dock position: Dock right'); + + await trigger.click(); + const items = page.getByRole('menuitemradio'); + await expect(items).toHaveCount(5); + await expect( + page.getByRole('menuitemradio', { name: 'Dock right' }) + ).toHaveAttribute('aria-checked', 'true'); + + await page.getByRole('menuitemradio', { name: 'Dock bottom' }).click(); + await expect(page.locator('uwt-inspector-pane')).toHaveAttribute( + 'class', + /uwt-inspector-pane--bottom/ + ); +}); diff --git a/playwright/lab.ts b/playwright/lab.ts new file mode 100644 index 0000000..8ef5a3a --- /dev/null +++ b/playwright/lab.ts @@ -0,0 +1,269 @@ +import { + APIRequestContext, + expect, + FrameLocator, + Locator, + Page, + test as base +} from '@playwright/test'; + +/** One RUM event as the mock dataplane received it, with its batch's user. */ +export interface RumEvent { + batch: number; + user: { sessionId?: string; userId?: string }; + type: string; + metadata: Record; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + details: Record; +} + +/** One log record as the mock log backend stored it. */ +export interface LogRecord { + application: string; + logger: string; + level: string; + message: string; + context?: string; + metadata?: Record; + error?: { name: string; message: string; stack?: string }; +} + +export type BrokerMode = + | 'ok' + | 'null' + | 'fail' + | 'short-expiry' + | 'flaky' + | 'invalid' + | 'null-on-refresh' + | 'no-credentials-on-refresh'; +export type Telemetry = + 'errors' | 'performance' | 'interaction' | 'http' | 'replay'; + +/** The lab's mock backend: what it received, and how its broker answers. */ +export class LabBackend { + private readonly request: APIRequestContext; + + constructor(request: APIRequestContext) { + this.request = request; + } + + async rum(): Promise { + return (await (await this.request.get('/__lab/rum')).json()).events; + } + + async logs(): Promise { + return (await this.request.get('/__lab/logs')).json(); + } + + /** What the lab's custom sink was given, with `?destination=custom`. */ + async custom(): Promise< + { kind: string; eventType?: string; payload?: Record }[] + > { + return (await this.request.get('/__lab/custom')).json(); + } + + async state(): Promise<{ + brokerMode: BrokerMode; + brokerCalls: number; + brokerConfig: Record; + telemetries: Telemetry[] | null; + /** Asked of the lab's destinations while the server rendered. */ + serverSends: string[]; + }> { + return (await this.request.get('/__lab/state')).json(); + } + + /** Waits until the RUM events received satisfy `done`, and returns them. */ + async rumUntil( + done: (events: RumEvent[]) => boolean, + timeout = 10_000 + ): Promise { + let events: RumEvent[] = []; + await expect + .poll(async () => done((events = await this.rum())), { timeout }) + .toBe(true); + return events; + } + + async reset(): Promise { + await this.control('/__lab/reset'); + } + + /** How the broker answers the next `/rum/init`. */ + async setBrokerMode(mode: BrokerMode): Promise { + await this.control('/__lab/broker-mode', { mode }); + } + + /** Laid over the broker's app monitor config. */ + async setBrokerConfig(config: Record): Promise { + await this.control('/__lab/broker-config', { config }); + } + + /** RUM's own telemetries the broker names; `null` for the library's default. */ + async setTelemetries(telemetries: Telemetry[] | null): Promise { + await this.control('/__lab/telemetries', { telemetries }); + } + + /** + * Posts to a control route, and fails if it refuses: a refused reset or + * setting would leave the last test's, failing tests that come later. + */ + private async control(path: string, data?: object): Promise { + const response = await this.request.post(path, data ? { data } : {}); + if (!response.ok()) { + throw new Error(`${path} answered ${response.status()}`); + } + } +} + +/** The library's events for one scenario or BI event name. */ +export function named(events: RumEvent[], name: string): RumEvent[] { + return events.filter( + (e) => + e.type.startsWith('com.uwt.') && + (e.details.scenarioName === name || e.details.eventName === name) + ); +} + +export const test = base.extend<{ + backend: LabBackend; + errors: string[]; +}>({ + // Every test starts from the broker's defaults and an empty backend. + backend: [ + async ({ request }, use) => { + const backend = new LabBackend(request); + await backend.setBrokerMode('ok'); + await backend.setBrokerConfig({}); + await backend.setTelemetries(null); + await backend.reset(); + await use(backend); + }, + { auto: true } + ], + + // Page and console errors fail the test — a test's other tabs too, once + // given to watchErrors. + errors: [ + async ({ page }, use) => { + const errors: string[] = []; + watchErrors(page, errors); + await use(errors); + expect(errors, 'page and console errors').toEqual([]); + }, + { auto: true } + ] +}); + +/** + * Collects `page`'s errors into `errors`. A failed request isn't one: the + * `fail` broker answers with an error on purpose. + */ +export function watchErrors(page: Page, errors: string[]): void { + page.on('pageerror', (e) => errors.push(`page: ${e.message}`)); + page.on('console', (m) => { + if ( + m.type() === 'error' && + !m.text().startsWith('Failed to load resource') + ) { + errors.push(`console: ${m.text()}`); + } + }); +} + +export { expect }; +export type { FrameLocator, Locator, Page }; + +/** + * Opens a lab page by its address, e.g. `'bi?telemetry=off'`, with `config` as + * its `?config=` overlay, and waits until it has hydrated: the server's page + * shows before it answers clicks. + */ +export async function openLab( + page: Page, + address: string, + config?: object +): Promise { + const overlay = config + ? `${address.includes('?') ? '&' : '?'}config=${encodeURIComponent(JSON.stringify(config))}` + : ''; + await page.goto(`/${address}${overlay}`); + await expect(page.locator('html[data-lab-ready]')).toHaveCount(1); + await expect(page.locator('lab-root main h1').first()).toBeVisible(); +} + +/** Waits until RUM has started a session, as the status bar shows; its id. */ +export async function sessionStarted(page: Page): Promise { + const status = page.locator('.lab-status'); + await expect(status).toContainText(/session: [0-9a-f]{8}/, { + timeout: 15_000 + }); + return (await status.textContent())!.match(/session: ([0-9a-f-]{36})/)![1]; +} + +/** + * Moves to another lab page within the app, keeping its state — through the + * folded nav's button too, as on a narrow screen or beside a pane. + */ +export async function navigate(page: Page, label: string): Promise { + const fold = page.locator('.lab-nav__toggle'); + if ( + (await fold.isVisible()) && + (await fold.getAttribute('aria-expanded')) !== 'true' + ) { + await fold.click(); + } + await page.locator('.lab-nav').getByRole('link', { name: label }).click(); + await expect(page.locator('lab-root main h1').first()).toHaveText(label); +} + +/** + * Moves to a section of Settings within the app, through its list, keeping + * the changes not applied yet. + */ +export async function settingsSection( + page: Page, + label: string +): Promise { + const link = page + .getByRole('navigation', { name: 'Settings sections' }) + .getByRole('link', { name: label }); + await link.click(); + await expect(link).toHaveAttribute('aria-current', 'page'); + // For a frame both sections are there, the new one not yet filled in. + await expect(page.locator('lab-root main h1').first()).toHaveText( + label === 'Overview' ? 'Settings' : label + ); +} + +/** + * Presses Settings' Apply and reload, and waits for the lab to load again: + * at the same address, when only the mock broker changed. + */ +export async function applyAndReload(page: Page): Promise { + const loaded = page.waitForEvent('load'); + await press(page, 'Apply and reload'); + await loaded; + await expect(page.locator('html[data-lab-ready]')).toHaveCount(1); +} + +/** Presses the lab's button with exactly this label. */ +export async function press( + where: Page | FrameLocator | Locator, + label: string +): Promise { + await where.getByRole('button', { name: label, exact: true }).first().click(); +} + +/** Shows one kind of event in the Event Inspector's timeline, or all. */ +export async function showKind( + page: Page, + kind: 'All' | 'BI' | 'Scenarios' | 'Logs' +): Promise { + // The toggle's options are radios, each inside the label people click. + await page + .locator('uwt-inspector cps-button-toggle label') + .filter({ hasText: new RegExp(`^\\s*${kind} \\d+\\s*$`) }) + .click(); +} diff --git a/playwright/lifecycle.spec.ts b/playwright/lifecycle.spec.ts new file mode 100644 index 0000000..7535940 --- /dev/null +++ b/playwright/lifecycle.spec.ts @@ -0,0 +1,19 @@ +import { expect, named, openLab, press, sessionStarted, test } from './lab'; + +test('a scenario running when the page goes arrives abandoned, by beacon', async ({ + page, + backend +}) => { + await openLab(page, 'lifecycle'); + await sessionStarted(page); + await press(page, 'Start a scenario and reload'); + await expect(page.locator('lab-root main h1')).toHaveText('Lifecycle'); + + const events = await backend.rumUntil( + (e) => named(e, 'lab-abandoned').length > 0 + ); + expect(named(events, 'lab-abandoned')[0].details).toMatchObject({ + status: 'abandoned', + metadata: { abandonedBy: 'page-hidden' } + }); +}); diff --git a/playwright/logging.spec.ts b/playwright/logging.spec.ts new file mode 100644 index 0000000..a28d8e1 --- /dev/null +++ b/playwright/logging.spec.ts @@ -0,0 +1,122 @@ +import { expect, LogRecord, openLab, press, sessionStarted, test } from './lab'; + +test.beforeEach(async ({ page }) => { + await openLab(page, 'logging'); + await sessionStarted(page); +}); + +test('records reach the log backend, and the noisy logger writes only warnings and up', async ({ + page, + backend +}) => { + await press(page, 'log'); + await press(page, 'log (dropped)'); + await press(page, 'warn (written)'); + await press(page, 'error, with an Error'); + + // Each record travels on its own request, so in any order. + await expect + .poll(async () => + (await backend.logs()).map((r) => `${r.logger}:${r.level}`).sort() + ) + .toEqual(['lab:error', 'lab:log', 'noisy:warn']); +}); + +test('an error record carries its context, metadata and error', async ({ + page, + backend +}) => { + await press(page, 'error, with an Error'); + await expect.poll(async () => (await backend.logs()).length).toBe(1); + expect((await backend.logs())[0]).toMatchObject({ + message: 'Saving the order failed', + context: 'checkout', + metadata: { orderId: 'A-1001' }, + error: { name: 'Error', message: 'Connection reset' } + }); +}); + +test('query() reads records back from the backend', async ({ page }) => { + await press(page, 'error, with an Error'); + await expect + .poll(async () => { + await press(page, 'Run query'); + return page.locator('.lab-readout').last().textContent(); + }) + .toContain('Saving the order failed'); +}); + +test('an error is mirrored to RUM', async ({ page, backend }) => { + await press(page, 'error, with an Error'); + await backend.rumUntil((events) => + events.some( + (e) => + e.type === 'com.amazon.rum.js_error_event' && + JSON.stringify(e.details).includes('Connection reset') + ) + ); +}); + +test('a query filter finds records by correlationId, by level and by time', async ({ + page, + backend +}) => { + await press(page, 'Log inside a scenario'); + await press(page, 'error, with an Error'); + await expect.poll(async () => (await backend.logs()).length).toBe(2); + const readout = page.locator('.lab-readout').last(); + const found = async () => + (JSON.parse((await readout.textContent())!) as LogRecord[]).map( + (r) => r.message + ); + + // The filter now names the scenario's id. + await press(page, 'Run query'); + await expect(readout).toContainText('Inside a journey'); + expect(await found()).toEqual(['Inside a journey']); + + const filter = page.getByRole('textbox', { name: 'Query filter (JSON)' }); + await filter.fill('{ "minLevel": "error" }'); + await press(page, 'Run query'); + await expect(readout).toContainText('Saving the order failed'); + expect(await found()).toEqual(['Saving the order failed']); + + const later = new Date(Date.now() + 60_000).toISOString(); + await filter.fill(`{ "from": "${later}" }`); + await press(page, 'Run query'); + await expect(readout).toHaveText('[]'); +}); + +test('a batching log provider is flushed as the page is hidden', async ({ + page, + backend +}) => { + await openLab(page, 'logging', { lab: { logBatching: true } }); + await press(page, 'warn'); + await page.waitForTimeout(1500); + expect(await backend.logs()).toEqual([]); + + await page.evaluate(() => { + Object.defineProperty(document, 'visibilityState', { + configurable: true, + get: () => 'hidden' + }); + document.dispatchEvent(new Event('visibilitychange')); + }); + // Well before the provider's own 10-second batch. + await expect + .poll(async () => (await backend.logs()).length, { timeout: 3000 }) + .toBe(1); +}); + +test('a batching log provider is flushed as the page reloads, in every browser', async ({ + page, + backend +}) => { + await openLab(page, 'logging', { lab: { logBatching: true } }); + await press(page, 'warn'); + await page.reload(); + await expect + .poll(async () => (await backend.logs()).length, { timeout: 4000 }) + .toBe(1); +}); diff --git a/playwright/options.spec.ts b/playwright/options.spec.ts new file mode 100644 index 0000000..bf927c7 --- /dev/null +++ b/playwright/options.spec.ts @@ -0,0 +1,66 @@ +import { expect, openLab, Page, press, test } from './lab'; + +/** Records every request this page makes to the RUM broker or dataplane. */ +function rumRequests(page: Page): string[] { + const seen: string[] = []; + page.on('request', (r) => { + if (/\/rum\/init|\/appmonitors\//.test(r.url())) { + seen.push(r.url()); + } + }); + return seen; +} + +const inspector = (page: Page) => page.locator('uwt-inspector'); + +test('?telemetry=off: no RUM requests at all, and the header says so', async ({ + page +}) => { + const seen = rumRequests(page); + await openLab(page, 'bi?telemetry=off'); + await press(page, 'Track a click'); + await page.waitForTimeout(3000); + + expect(seen).toEqual([]); + await expect(page.locator('.lab-header')).toContainText('telemetry: off'); +}); + +test('?destination=noop: no RUM requests; the Event Inspector still shows events', async ({ + page +}) => { + const seen = rumRequests(page); + await openLab(page, 'bi?destination=noop'); + await page.locator('.lab-header-button').click(); + await press(page, 'Track a click'); + await page.waitForTimeout(3000); + + expect(seen).toEqual([]); + await expect(inspector(page)).toContainText('lab_button_clicked'); +}); + +test('?inspector=off: the Event Inspector never opens', async ({ page }) => { + await openLab(page, 'bi?inspector=off'); + await page.locator('.lab-header-button').click(); + await page.keyboard.down('Shift'); + await page.keyboard.press('KeyE'); + await page.keyboard.press('KeyI'); + await page.keyboard.up('Shift'); + + await page.waitForTimeout(500); + await expect(inspector(page)).toHaveCount(0); +}); + +test('?inspector=noshortcut: the shortcut does nothing, the header button opens it', async ({ + page +}) => { + await openLab(page, 'bi?inspector=noshortcut'); + await page.keyboard.down('Shift'); + await page.keyboard.press('KeyE'); + await page.keyboard.press('KeyI'); + await page.keyboard.up('Shift'); + await page.waitForTimeout(500); + await expect(inspector(page)).toHaveCount(0); + + await page.locator('.lab-header-button').click(); + await expect(inspector(page)).toBeVisible(); +}); diff --git a/playwright/privacy.spec.ts b/playwright/privacy.spec.ts new file mode 100644 index 0000000..ca72a69 --- /dev/null +++ b/playwright/privacy.spec.ts @@ -0,0 +1,91 @@ +import { expect, named, openLab, press, sessionStarted, test } from './lab'; + +/** Personal values the Privacy page sends, none of which may arrive. */ +const PERSONAL = [ + 'anna.smith@example.com', + '4111 1111', + 'hunter2', + 'eyJhbGci', + '10.1.2.3', + '555 0123', + '123-45-6789' +]; + +test('personal values never reach RUM or the log backend', async ({ + page, + backend +}) => { + await page.addInitScript(() => + localStorage.setItem( + 'ngx-ui-watchtower.eventInspector', + JSON.stringify({ dock: 'right' }) + ) + ); + await openLab(page, 'privacy'); + await sessionStarted(page); + await page.locator('.lab-header-button').click(); + await press(page, 'As BI metadata'); + await press(page, 'In a log message'); + await press(page, 'In a failing scenario'); + + const events = await backend.rumUntil( + (e) => + named(e, 'lab_privacy_checked').length > 0 && + named(e, 'lab-privacy').length > 0 + ); + await expect + .poll(async () => (await backend.logs()).length) + .toBeGreaterThan(0); + const sent = JSON.stringify([events, await backend.logs()]); + + expect(PERSONAL.filter((value) => sent.includes(value))).toEqual([]); + expect(sent).toContain('[redacted]'); + expect(named(events, 'lab_privacy_checked')).toHaveLength(1); + + // Nor in what the Event Inspector shows. + const shown = await page.locator('uwt-inspector').textContent(); + expect(PERSONAL.filter((value) => shown!.includes(value))).toEqual([]); +}); + +test('errors of any shape are normalized: a string scrubbed, an object reduced to its type, an HTTP error without its query', async ({ + page, + backend +}) => { + await openLab(page, 'privacy'); + await press(page, 'An error as a string'); + await press(page, 'An error as a plain object'); + await press(page, 'An HttpErrorResponse'); + + await expect.poll(async () => (await backend.logs()).length).toBe(3); + const errors = Object.fromEntries( + (await backend.logs()).map((r) => [r.message, r.error]) + ); + expect(errors['The string error']).toEqual({ + name: 'Error', + message: 'Lookup failed for [redacted]' + }); + expect(errors['The object error']).toEqual({ + name: 'UnknownError', + message: '[redacted]' + }); + const http = errors['The http error']!; + expect(http.name).toBe('HttpErrorResponse'); + expect(http.message).toContain('https://shop.example/api/customers'); + expect(http.message).toContain('404 Not Found'); + expect(http.message).not.toContain('?email'); +}); + +test('the scrub playground shows what the library makes of any text', async ({ + page +}) => { + await openLab(page, 'privacy'); + await page + .getByRole('textbox', { name: 'Text to scrub' }) + .fill('Call +27 82 555 0123 about https://shop.example/a?session=1'); + const scrubbed = page.locator('.lab-scrubbed'); + await expect(scrubbed).toContainText( + '"uwtScrubString": "Call [redacted] about https://shop.example/a"' + ); + await expect(scrubbed).toContainText('"password": "[redacted]"'); + await expect(scrubbed).not.toContainText('555 0123'); +}); diff --git a/playwright/rum-startup.spec.ts b/playwright/rum-startup.spec.ts new file mode 100644 index 0000000..0bae402 --- /dev/null +++ b/playwright/rum-startup.spec.ts @@ -0,0 +1,204 @@ +import { + expect, + named, + navigate, + openLab, + Page, + press, + RumEvent, + sessionStarted, + test +} from './lab'; + +const clicks = (events: RumEvent[]) => named(events, 'lab_button_clicked'); + +/** Console warnings, and the requests to RUM's dataplane, as they happen. */ +function watch(page: Page): { warnings: string[]; sent: string[] } { + const seen = { warnings: [] as string[], sent: [] as string[] }; + page.on('console', (m) => { + if (m.type() === 'warning') { + seen.warnings.push(m.text()); + } + }); + page.on('request', (r) => { + if (r.url().includes('/appmonitors/')) { + seen.sent.push(r.url()); + } + }); + return seen; +} + +test('a broker that fails at first: RUM retries, then sends what was tracked meanwhile', async ({ + page, + backend +}) => { + const { warnings } = watch(page); + await backend.setBrokerMode('flaky'); + await openLab(page, 'bi'); + await press(page, 'Track a click'); + + await sessionStarted(page); + const events = await backend.rumUntil((e) => clicks(e).length > 0); + expect(clicks(events)).toHaveLength(1); + expect((await backend.state()).brokerCalls).toBe(3); + expect(warnings).toContainEqual( + expect.stringContaining('RUM init failed, retrying') + ); +}); + +test('settings of the wrong type: RUM never starts, and the console says which', async ({ + page, + backend +}) => { + const { warnings, sent } = watch(page); + await backend.setBrokerMode('invalid'); + await openLab(page, 'bi'); + await press(page, 'Track a click'); + + await expect + .poll(() => warnings) + .toContainEqual( + expect.stringContaining( + 'RUM not started, settings of the wrong type: pagesToExclude' + ) + ); + // RUM would have sent within two of its 2-second dispatches. + await page.waitForTimeout(4500); + expect(sent).toEqual([]); +}); + +test('settings left null count as unset: RUM starts as usual', async ({ + page, + backend +}) => { + const { warnings } = watch(page); + await backend.setBrokerConfig({ + pagesToInclude: null, + sessionSampleRate: null, + telemetries: null + }); + await openLab(page, 'bi'); + await sessionStarted(page); + await press(page, 'Track a click'); + await backend.rumUntil((e) => clicks(e).length > 0); + expect(warnings.filter((w) => w.includes('RUM not started'))).toEqual([]); +}); + +test('a refresh answered with null turns RUM off for the page', async ({ + page, + backend +}) => { + await backend.setBrokerMode('null-on-refresh'); + await openLab(page, 'bi'); + await sessionStarted(page); + await press(page, 'Track a click'); + await backend.rumUntil((e) => clicks(e).length > 0); + + await expect + .poll(async () => (await backend.state()).brokerCalls, { timeout: 15_000 }) + .toBe(2); + // RUM is off once the page has the null: no session any more. + await expect(page.locator('.lab-status')).toContainText('session: —'); + await press(page, 'Fire twice at once'); + await page.waitForTimeout(4500); + expect(named(await backend.rum(), 'lab_double_fired')).toEqual([]); +}); + +test("each page load is the broker's first call: a reload starts RUM again", async ({ + page, + backend +}) => { + await backend.setBrokerMode('null-on-refresh'); + await openLab(page, 'bi'); + const first = await sessionStarted(page); + await page.reload(); + await expect(page.locator('html[data-lab-ready]')).toHaveCount(1); + expect(await sessionStarted(page)).toBe(first); +}); + +test('a refresh without credentials keeps the old ones: events still arrive', async ({ + page, + backend +}) => { + const { warnings } = watch(page); + await backend.setBrokerMode('no-credentials-on-refresh'); + await openLab(page, 'bi'); + await sessionStarted(page); + + await expect + .poll(async () => (await backend.state()).brokerCalls, { timeout: 15_000 }) + .toBe(2); + await expect + .poll(() => warnings) + .toContainEqual(expect.stringContaining('RUM credential refresh')); + await press(page, 'Track a click'); + await backend.rumUntil((e) => clicks(e).length > 0); +}); + +test('a session sampled out sends nothing', async ({ page, backend }) => { + const { sent } = watch(page); + await backend.setBrokerConfig({ sessionSampleRate: 0 }); + await openLab(page, 'bi'); + await press(page, 'Track a click'); + await page.waitForTimeout(4500); + expect(sent).toEqual([]); +}); + +test('without cookies, scenario records carry no sessionElapsed', async ({ + page, + backend +}) => { + await backend.setBrokerConfig({ allowCookies: false }); + await openLab(page, 'scenarios'); + await sessionStarted(page); + await press(page, 'Complete, with 3 steps'); + const events = await backend.rumUntil( + (e) => named(e, 'lab-happy-path').length > 0 + ); + expect(named(events, 'lab-happy-path')[0].details).not.toHaveProperty( + 'sessionElapsed' + ); +}); + +test('before the first setUserId, events carry no signedIn', async ({ + page, + backend +}) => { + await openLab(page, 'bi'); + await sessionStarted(page); + await press(page, 'Track a click'); + const events = await backend.rumUntil((e) => clicks(e).length > 0); + expect(clicks(events)[0].metadata).not.toHaveProperty('signedIn'); +}); + +test('recordPageView adds a page view of its own', async ({ + page, + backend +}) => { + await openLab(page, 'lifecycle'); + await sessionStarted(page); + await press(page, 'Record a page view'); + await backend.rumUntil((e) => + e.some( + (event) => + event.type === 'com.amazon.rum.page_view_event' && + event.details.pageId === '/lab/virtual-page' + ) + ); +}); + +test('Flush now sends at once what would wait for the next batch', async ({ + page, + backend +}) => { + await backend.setBrokerConfig({ dispatchInterval: 60_000 }); + await openLab(page, 'bi'); + await sessionStarted(page); + await press(page, 'Track a click'); + await page.waitForTimeout(3000); + expect(clicks(await backend.rum())).toEqual([]); + + await navigate(page, 'Lifecycle'); + await press(page, 'Flush now'); + await backend.rumUntil((e) => clicks(e).length > 0, 3000); +}); diff --git a/playwright/scenario-api.spec.ts b/playwright/scenario-api.spec.ts new file mode 100644 index 0000000..f5cf32c --- /dev/null +++ b/playwright/scenario-api.spec.ts @@ -0,0 +1,129 @@ +import { expect, named, openLab, press, sessionStarted, test } from './lab'; + +test.beforeEach(async ({ page }) => { + await openLab(page, 'scenarios'); + await sessionStarted(page); +}); + +test('steps carry their detail, a failed step leaves the scenario running, and the outcome lands on the record', async ({ + page, + backend +}) => { + await press(page, 'Steps with detail'); + const events = await backend.rumUntil( + (e) => named(e, 'lab-detailed').length > 0 + ); + const record = named(events, 'lab-detailed')[0].details; + expect(record).toMatchObject({ + status: 'success', + statusCode: 200, + message: 'Saved', + reason: 'all-valid', + metadata: { orders: 2, source: 'lab' }, + stepCount: 2 + }); + const step = (name: string) => + record.steps.find((s: { name: string }) => s.name === name); + expect(step('fetch')).toMatchObject({ + status: 'success', + message: 'Fetched', + metadata: { rows: 3 } + }); + expect(step('validate')).toMatchObject({ + status: 'failure', + reason: 'validation', + error: { message: 'Row 2 is invalid' } + }); +}); + +test('a child names its parent, with its feature, operation and route', async ({ + page, + backend +}) => { + await press(page, 'A parent and a child'); + const events = await backend.rumUntil( + (e) => named(e, 'lab-parent').length > 0 && named(e, 'lab-child').length > 0 + ); + expect(named(events, 'lab-child')[0].details).toMatchObject({ + parentScenarioId: named(events, 'lab-parent')[0].details.scenarioId, + feature: 'checkout', + operation: 'pay', + route: '/orders/:id' + }); +}); + +test("settle() settles with the status it's given", async ({ + page, + backend +}) => { + await press(page, "settle('incomplete')"); + const events = await backend.rumUntil( + (e) => named(e, 'lab-settled').length > 0 + ); + expect(named(events, 'lab-settled')[0].details).toMatchObject({ + status: 'incomplete', + reason: 'no-results' + }); +}); + +test('more than 50 active scenarios draws a warning in development', async ({ + page +}) => { + const warnings: string[] = []; + page.on('console', (m) => m.type() === 'warning' && warnings.push(m.text())); + await press(page, '51 at once'); + await expect + .poll(() => warnings) + .toContainEqual( + expect.stringContaining('High number of active scenarios (51)') + ); +}); + +test('the lookups find an active scenario, until it settles', async ({ + page, + backend +}) => { + const readout = page.locator('.lab-readout').last(); + const lookups = async () => JSON.parse((await readout.textContent())!); + + await press(page, 'Start a long one'); + await expect(readout).toContainText('"find": "lab-long"'); + const found = await lookups(); + expect(found.getActive).toContain('lab-long'); + expect(found.findByName).toHaveLength(1); + expect(found.findByNameAndId).toBe(found.findByName[0]); + + await press(page, 'Cancel every active one'); + await expect(readout).toContainText('"getActive": []'); + const events = await backend.rumUntil((e) => named(e, 'lab-long').length > 0); + expect(named(events, 'lab-long')[0].details.status).toBe('abandoned'); +}); + +test("traceScenario's options: an outcome from the value, a cancel outcome, and a value then teardown", async ({ + page, + backend +}) => { + await press(page, 'outcome, from the response'); + await press(page, 'cancelOutcome, unsubscribed early'); + await press(page, "valueThenTeardown: 'cancel'"); + + const events = await backend.rumUntil((e) => + ['lab-trace-outcome', 'lab-trace-cancel', 'lab-trace-teardown'].every( + (name) => named(e, name).length > 0 + ) + ); + expect(named(events, 'lab-trace-outcome')[0].details).toMatchObject({ + status: 'success', + statusCode: 200, + message: 'Data arrived' + }); + expect(named(events, 'lab-trace-cancel')[0].details).toMatchObject({ + status: 'abandoned', + reason: 'navigated-away', + metadata: { abandonedBy: 'caller' } + }); + expect(named(events, 'lab-trace-teardown')[0].details).toMatchObject({ + status: 'abandoned', + metadata: { abandonedBy: 'caller' } + }); +}); diff --git a/playwright/scenarios.spec.ts b/playwright/scenarios.spec.ts new file mode 100644 index 0000000..24baaa9 --- /dev/null +++ b/playwright/scenarios.spec.ts @@ -0,0 +1,149 @@ +import { expect, named, openLab, press, sessionStarted, test } from './lab'; + +test.beforeEach(async ({ page }) => { + await openLab(page, 'scenarios'); + await sessionStarted(page); +}); + +test('a settled scenario arrives once, its steps packed inside, with the identity attributes', async ({ + page, + backend +}) => { + await press(page, 'Complete, with 3 steps'); + await expect(page.locator('.lab-readout').first()).toContainText( + '"scenarioName": "lab-happy-path"' + ); + + const events = await backend.rumUntil( + (e) => named(e, 'lab-happy-path').length > 0 + ); + const happy = named(events, 'lab-happy-path'); + expect(happy).toHaveLength(1); + expect(happy[0].type).toBe('com.uwt.scenario'); + expect(happy[0].user.sessionId).toBeTruthy(); + expect(happy[0].details).toMatchObject({ + status: 'success', + stepCount: 3, + application: 'watchtower-lab', + // RUM's session cookie dates the session's start. + sessionElapsed: expect.any(Number) + }); +}); + +test('a failing scenario arrives as a failure, with its error and status code', async ({ + page, + backend +}) => { + await press(page, 'Fail with an error'); + const events = await backend.rumUntil( + (e) => named(e, 'lab-failure').length > 0 + ); + expect(named(events, 'lab-failure')[0].details).toMatchObject({ + status: 'failure', + statusCode: 503, + error: { name: 'Error', message: 'The backend said no' } + }); +}); + +test('cancel, incomplete and a timeout each settle with their own status', async ({ + page, + backend +}) => { + await press(page, 'Cancel'); + await press(page, 'Incomplete'); + await press(page, 'Time out after 1.5 s'); + + const events = await backend.rumUntil( + (e) => named(e, 'lab-timeout').length > 0 + ); + expect(named(events, 'lab-cancel')[0].details).toMatchObject({ + status: 'abandoned', + metadata: { abandonedBy: 'caller' } + }); + expect(named(events, 'lab-incomplete')[0].details.status).toBe('incomplete'); + expect(named(events, 'lab-timeout')[0].details).toMatchObject({ + status: 'timeout', + message: 'Scenario did not settle within 1500ms' + }); +}); + +test('a backdated start and aggregated batches count in the timing', async ({ + page, + backend +}) => { + // A start from before the page loaded is ignored, so the page must be + // older than the backdating. + await page.waitForFunction(() => performance.now() > 1600); + await press(page, 'Backdated by 1.5 s'); + await press(page, '5 aggregated batches'); + + const events = await backend.rumUntil( + (e) => named(e, 'lab-aggregates').length > 0 + ); + // Backdated in epoch time, measured on the page's clock: a millisecond + // can go either way. + expect(named(events, 'lab-backdated')[0].details.delta).toBeGreaterThan(1490); + const [batch] = named(events, 'lab-aggregates')[0].details.aggregates; + expect(batch).toMatchObject({ name: 'batch', callCount: 5 }); + expect(batch.elapsed).toBeGreaterThanOrEqual(150); +}); + +test('HTTP scenarios settle through traceScenario, a failed request as a failure', async ({ + page, + backend +}) => { + await press(page, 'Request (600 ms)'); + await press(page, 'Request that fails (500)'); + const events = await backend.rumUntil( + (e) => + named(e, 'lab-http').length > 0 && named(e, 'lab-http-error').length > 0 + ); + expect(named(events, 'lab-http')[0].details.status).toBe('success'); + const failed = named(events, 'lab-http-error')[0].details; + expect(failed.status).toBe('failure'); + expect(failed.error.name).toBe('HttpErrorResponse'); + expect(failed.error.message).toContain('500'); +}); + +test('toRecord() shows a running scenario; completing it sends it once', async ({ + page, + backend +}) => { + const readout = page.locator('.lab-readout').first(); + await press(page, 'Start'); + await expect(readout).toContainText('"scenarioName": "lab-happy-path"'); + const running = JSON.parse((await readout.textContent())!); + expect(running.status).toBeUndefined(); + expect(running.steps.map((s: { name: string }) => s.name)).toContain('fetch'); + expect(named(await backend.rum(), 'lab-happy-path')).toHaveLength(0); + + await press(page, 'Complete it'); + const events = await backend.rumUntil( + (e) => named(e, 'lab-happy-path').length > 0 + ); + expect(named(events, 'lab-happy-path')).toHaveLength(1); + expect(named(events, 'lab-happy-path')[0].details).toMatchObject({ + status: 'success', + stepCount: 1 + }); +}); + +test('switchMap supersession: earlier searches are abandoned by the caller, the last succeeds', async ({ + page, + backend +}) => { + for (let n = 0; n < 3; n++) { + await press(page, 'Search'); + } + const events = await backend.rumUntil( + (e) => named(e, 'lab-superseded').length === 3 + ); + const settled = named(events, 'lab-superseded').map( + (e) => `${e.details.status}:${e.details.metadata?.abandonedBy ?? ''}` + ); + expect(settled.sort()).toEqual([ + 'abandoned:caller', + 'abandoned:caller', + 'success:' + ]); +}); diff --git a/playwright/settings.spec.ts b/playwright/settings.spec.ts new file mode 100644 index 0000000..63fdef9 --- /dev/null +++ b/playwright/settings.spec.ts @@ -0,0 +1,1074 @@ +import { + applyAndReload, + expect, + LogRecord, + named, + navigate, + openLab, + Page, + press, + sessionStarted, + settingsSection, + test +} from './lab'; + +/** The Event Inspector opens as a pane, beside the page's buttons. */ +const asPane = (page: Page) => + page.addInitScript(() => + localStorage.setItem( + 'ngx-ui-watchtower.eventInspector', + JSON.stringify({ dock: 'right' }) + ) + ); + +/** Picks the option starting with `option` in the select labelled `label`, from the keyboard. */ +async function choose(page: Page, label: string, option: string) { + await page.getByRole('combobox', { name: label }).press('ArrowDown'); + await page.getByRole('option', { name: new RegExp(`^${option}`) }).click(); +} + +const toggle = (page: Page, label: string) => + page + .locator('cps-switch', { hasText: label }) + .locator('label.cps-switch') + .click(); + +const fill = (page: Page, label: string, value: string) => + page.locator('cps-input', { hasText: label }).locator('input').fill(value); + +/** What the select labelled `label` shows as chosen. */ +const shown = (page: Page, label: string) => + page + .locator('cps-select', { hasText: label }) + .locator('[data-testid="cps-select-selected-value"]'); + +/** The bar listing the changes not applied yet. */ +const pending = (page: Page) => + page.getByRole('region', { name: 'Changes not applied' }); + +/** The library's settings as JSON, in their own section. */ +async function json(page: Page) { + await settingsSection(page, 'Configuration as JSON'); + return page.getByRole('textbox', { name: 'Settings (JSON)' }); +} + +/** The settings in effect, in their own section. */ +async function effective(page: Page) { + await settingsSection(page, 'Effective configuration'); + return page.locator('.lab-readout'); +} + +/** Waits for the log backend to hold one record, and returns it. */ +async function onlyLog(backend: { + logs(): Promise; +}): Promise { + await expect.poll(async () => (await backend.logs()).length).toBe(1); + return (await backend.logs())[0]; +} + +test('Settings sits at the foot of the nav, apart from the pages, and opens on its overview', async ({ + page +}) => { + await openLab(page, 'bi?destination=noop'); + const nav = page.locator('.lab-nav'); + await expect( + nav.getByRole('list', { name: 'Tools' }).getByRole('link') + ).toHaveText(['Event Inspector', 'On the wire']); + const last = (await nav + .getByRole('link', { name: 'On the wire' }) + .boundingBox())!; + const settings = (await nav + .getByRole('link', { name: 'Settings' }) + .boundingBox())!; + // Well below the last page, just above the status bar. + expect(settings.y).toBeGreaterThan(last.y + 3 * last.height); + expect(settings.y + settings.height).toBeGreaterThan( + page.viewportSize()!.height - 36 - 48 + ); + + await navigate(page, 'Settings'); + await expect(page).toHaveURL(/\/settings\?destination=noop$/); + await expect(page).toHaveTitle('Settings · Watchtower Lab'); +}); + +test('each section of Settings is a page of its own, listed by area, saying how it applies', async ({ + page +}) => { + await openLab(page, 'settings?destination=noop'); + const sections = page.getByRole('navigation', { name: 'Settings sections' }); + const areas = { + Library: [ + 'App identity', + 'Delivery', + 'Scenarios', + 'BI events', + 'Logging', + 'Privacy & redaction', + 'Event Inspector' + ], + 'Mock backend': ['RUM broker', 'App monitor'], + Diagnostics: ['Debug flags'], + Advanced: ['Configuration as JSON', 'Effective configuration'] + }; + // Each named first: Delivery goes on with its marks, as noop is modified. + for (const [area, labels] of Object.entries(areas)) { + await expect( + sections.getByRole('list', { name: area }).getByRole('link') + ).toHaveText(labels.map((label) => new RegExp(`^${label}`))); + } + + const main = page.locator('lab-root main'); + await settingsSection(page, 'Logging'); + await expect(page).toHaveURL(/\/settings\/logging\?destination=noop$/); + await expect(page).toHaveTitle('Logging · Settings · Watchtower Lab'); + await expect(main).toContainText('Applies on reload'); + await settingsSection(page, 'RUM broker'); + await expect(main).toContainText('Applies on reload, in every tab'); + await settingsSection(page, 'Debug flags'); + await expect(main).toContainText('Applies at once, in this browser'); +}); + +test('changes wait in one bar across the sections, marked in their list, until discarded', async ({ + page +}) => { + await openLab(page, 'settings/logging'); + const bar = pending(page); + await expect(bar).toHaveCount(0); + + await choose(page, 'Minimum level', 'warn'); + await expect(bar).toContainText('1 change not applied'); + await expect(bar).toContainText('Logging › Minimum level'); + await settingsSection(page, 'Scenarios'); + await toggle(page, 'Lifecycle step events'); + await expect(bar).toContainText('2 changes not applied'); + const sections = page.getByRole('navigation', { name: 'Settings sections' }); + await expect( + sections.getByRole('link', { name: /changes not applied/ }) + ).toHaveCount(2); + await expect( + sections.getByRole('link', { name: 'Logging (changes not applied)' }) + ).toBeVisible(); + + // Kept while the sections change. + await settingsSection(page, 'Logging'); + await expect(shown(page, 'Minimum level')).toHaveText( + 'warn — warnings and errors' + ); + + await bar.getByRole('link', { name: 'Review' }).click(); + const review = page.locator('lab-settings-overview'); + await expect(review.locator('h1')).toHaveText('Settings'); + await expect(review).toContainText('Logging › Minimum level'); + await expect(review).toContainText('log → warn'); + await expect(review).toContainText('Scenarios › Lifecycle step events'); + await expect(review).toContainText('off → on'); + + await press(page, 'Discard'); + await expect(bar).toHaveCount(0); + await expect(review).toContainText('Nothing'); + await settingsSection(page, 'Logging'); + await expect(shown(page, 'Minimum level')).toHaveText('log — everything'); +}); + +test('a setting set back as it was is no change', async ({ page }) => { + await openLab(page, 'settings/scenarios'); + await toggle(page, 'Lifecycle step events'); + await expect(pending(page)).toBeVisible(); + await toggle(page, 'Lifecycle step events'); + await expect(pending(page)).toHaveCount(0); +}); + +test("JSON that isn't an object: its controls say why they're off, and Apply's bar shows the way to fix it", async ({ + page +}) => { + await openLab(page, 'settings/json'); + await page.getByRole('textbox', { name: 'Settings (JSON)' }).fill('[1, 2]'); + await settingsSection(page, 'Logging'); + await expect(page.locator('lab-settings-section')).toContainText( + "These controls are off while the JSON in Configuration as JSON isn't an object." + ); + + await press(page, 'Apply and reload'); + const problem = pending(page).getByRole('alert'); + await expect(problem).toContainText( + 'Configuration as JSON: Not a JSON object.' + ); + await problem.getByRole('link', { name: 'Configuration as JSON' }).click(); + await expect(page.locator('lab-root main h1').first()).toHaveText( + 'Configuration as JSON' + ); + await expect(page.locator('.lab-header')).not.toContainText('config:'); +}); + +test('on a narrow screen, the sections of Settings fold into a picker', async ({ + page +}) => { + await page.setViewportSize({ width: 800, height: 700 }); + await openLab(page, 'settings/logging'); + await expect( + page.getByRole('navigation', { name: 'Settings sections' }) + ).toBeHidden(); + await expect(shown(page, 'Section')).toHaveText('Library › Logging'); + // The search stays, above the picker. + await expect( + page.getByRole('combobox', { name: 'Find a setting' }) + ).toBeVisible(); + + await choose(page, 'Section', 'Library › Scenarios'); + await expect(page).toHaveURL(/\/settings\/scenarios$/); + await expect(page.locator('lab-root main h1').first()).toHaveText( + 'Scenarios' + ); +}); + +test("a setting other than the lab's default is marked, with its default and a Reset that waits to be applied", async ({ + page +}) => { + await openLab(page, 'settings/bi-events', { bi: { dedupWindowMs: 0 } }); + const field = page.locator('.lab-field', { hasText: 'BI dedup window' }); + await expect(field).toContainText('Modified'); + await expect(field).toContainText('Lab default: 400'); + await expect( + page + .getByRole('navigation', { name: 'Settings sections' }) + .getByRole('link', { name: 'BI events (1 modified)' }) + ).toBeVisible(); + + await field + .getByRole('button', { + name: 'Reset BI dedup window, ms to the lab default' + }) + .click(); + await expect(field).toContainText('Not applied'); + // The control shows what the next load will have: the lab's default. + await expect(field.locator('input')).toHaveValue('400'); + await expect(pending(page)).toContainText('BI events › BI dedup window, ms'); + await field.getByRole('button', { name: 'Undo BI dedup window, ms' }).click(); + await expect(pending(page)).toHaveCount(0); + await expect(field).toContainText('Modified'); + await expect(field.locator('input')).toHaveValue('0'); + + await field.getByRole('button', { name: /^Reset/ }).click(); + await applyAndReload(page); + await expect(page.locator('.lab-header')).not.toContainText('config:'); + await expect( + page.locator('.lab-field', { hasText: 'BI dedup window' }) + ).not.toContainText('Modified'); +}); + +test("the overview lists what differs from the lab's defaults: a debug flag resets at once, the rest wait to be applied", async ({ + page, + backend +}) => { + await backend.setBrokerMode('null'); + await page.addInitScript(() => localStorage.setItem('debugBI', 'true')); + await openLab(page, 'settings?destination=noop', { + logging: { minLevel: 'warn' } + }); + const review = page.locator('lab-settings-overview'); + const modified = review.locator('.lab-card', { + hasText: "Different from the lab's defaults" + }); + for (const text of [ + 'Delivery › Destination', + 'rum → noop', + 'Logging › Minimum level', + 'log → warn', + 'RUM broker › Answer /rum/init with', + 'ok → null', + 'Debug flags › debugBI', + 'off → on' + ]) { + await expect(modified).toContainText(text); + } + + await modified + .getByRole('button', { name: 'Reset debugBI to the lab default' }) + .click(); + await expect(modified).not.toContainText('debugBI'); + await expect + .poll(() => page.evaluate(() => localStorage.getItem('debugBI'))) + .toBeNull(); + await expect(pending(page)).toHaveCount(0); + + await modified + .getByRole('button', { name: 'Reset Destination to the lab default' }) + .click(); + const waiting = review.locator('.lab-card', { hasText: 'Not applied yet' }); + await expect(waiting).toContainText('Delivery › Destination'); + await expect(waiting).toContainText('noop → rum'); + await waiting.getByRole('button', { name: 'Undo Destination' }).click(); + await expect(pending(page)).toHaveCount(0); +}); + +test("the mock broker's answer, other than its default, is marked in its sections", async ({ + page, + backend +}) => { + await backend.setBrokerMode('null'); + await backend.setTelemetries(['errors']); + await backend.setBrokerConfig({ sessionSampleRate: 0.5 }); + await openLab(page, 'settings/rum-broker'); + await expect( + page.locator('.lab-field', { hasText: 'Answer /rum/init with' }) + ).toContainText('Lab default: ok'); + await expect( + page.locator('.lab-field', { hasText: 'Telemetries' }) + ).toContainText("Lab default: the library's default"); + const sections = page.getByRole('navigation', { name: 'Settings sections' }); + await expect( + sections.getByRole('link', { name: 'RUM broker (2 modified)' }) + ).toBeVisible(); + await expect( + sections.getByRole('link', { name: 'App monitor (1 modified)' }) + ).toBeVisible(); + + await settingsSection(page, 'App monitor'); + await expect( + page.locator('.lab-field', { hasText: 'Session sample rate' }) + ).toContainText('Lab default: 1'); +}); + +test("the header's chips lead to their settings", async ({ page }) => { + await openLab(page, 'bi', { bi: { dedupWindowMs: 0 } }); + const header = page.locator('.lab-header'); + const title = page.locator('lab-root main h1').first(); + await header.getByRole('link', { name: 'destination: rum' }).click(); + await expect(page).toHaveURL(/\/settings\/delivery\?config=/); + await expect(title).toHaveText('Delivery'); + await header.getByRole('link', { name: 'broker: ok' }).click(); + await expect(title).toHaveText('RUM broker'); + await header.getByRole('link', { name: 'config: custom' }).click(); + await expect(title).toHaveText('Settings'); + + // Folded behind Run settings, they lead there too, and fold away. + await page.setViewportSize({ width: 1024, height: 768 }); + await header.getByText('Run settings').click(); + const panel = header.locator('.lab-run-settings__panel'); + await panel.getByRole('link', { name: 'telemetry: on' }).click(); + await expect(title).toHaveText('Delivery'); + await expect(panel).toBeHidden(); +}); + +test('each setting says what it does, as the documentation of what it sets has it', async ({ + page +}) => { + await openLab(page, 'settings/logging'); + // Read out with its control, where the kit's control takes a description. + await expect( + page.getByRole('combobox', { name: 'Minimum level' }) + ).toHaveAccessibleDescription(/Records below this severity are discarded/); + await expect( + page.locator('.lab-field', { hasText: 'Mirror errors to RUM' }) + ).toContainText('logger.error calls go to RUM as errors too'); + + await settingsSection(page, 'App monitor'); + await expect( + page.getByRole('spinbutton', { name: /Session sample rate/ }) + ).toHaveAccessibleDescription(/share of sessions RUM records/); +}); + +test('a setting is found by its name, its name in the JSON or what it does, and shown in its section', async ({ + page +}) => { + await openLab(page, 'settings/scenarios'); + const search = page.getByRole('combobox', { name: 'Find a setting' }); + const results = page.getByRole('option'); + const found = page.locator('.lab-field--found'); + // Once shown, the setting's control has the focus, and the page has + // scrolled to it: searching on before then, the focus or the scroll would + // arrive with the list open, and close it, as the kit closes its lists. + const shownFocused = async () => { + await expect( + found.locator('input, [role="combobox"]').first() + ).toBeFocused(); + // A scroll's event comes with the next frame. + await page.evaluate( + () => + new Promise((resolve) => + requestAnimationFrame(() => requestAnimationFrame(resolve)) + ) + ); + }; + + // "redact" finds every place that redacts, and the section that does most. + await search.fill('redact'); + for (const name of [ + 'Redact scenario payloads', + 'Redact log values', + 'Redact BI payloads', + 'Privacy & redaction' + ]) { + await expect(results.filter({ hasText: name }).first()).toBeVisible(); + } + await results.filter({ hasText: 'Redact log values' }).click(); + await expect(page).toHaveURL(/\/settings\/logging#logging\.redact$/); + await expect(found).toContainText('Redact log values'); + await expect(found.getByRole('switch')).toBeFocused(); + await shownFocused(); + // Emptied, for the next search. + await expect(search).toHaveValue(''); + + await search.fill('dedupWindowMs'); + await results.filter({ hasText: 'BI dedup window' }).click(); + await expect(page).toHaveURL(/\/settings\/bi-events#bi\.dedupWindowMs$/); + await shownFocused(); + + await search.fill('Luhn'); + await results.filter({ hasText: 'Value patterns scanned' }).click(); + await expect(page).toHaveURL( + /\/settings\/privacy#redaction\.scanValuePatterns$/ + ); + await shownFocused(); + + // The mock broker's answer and the debug flags are settings like the rest. + await search.fill('debugBI'); + await results.filter({ hasText: 'debugBI' }).click(); + await expect(page).toHaveURL(/\/settings\/debug-flags#debug\.debugBI$/); + await expect(found).toContainText('debugBI'); + await shownFocused(); +}); + +test('"/" goes to the search, but in a field it is typed', async ({ page }) => { + await openLab(page, 'settings/app-identity'); + await page.locator('lab-settings-section h1').click(); + await page.keyboard.press('/'); + await expect( + page.getByRole('combobox', { name: 'Find a setting' }) + ).toBeFocused(); + + const version = page + .locator('cps-input', { hasText: 'Version' }) + .locator('input'); + await version.fill('2'); + await version.press('/'); + await expect(version).toHaveValue('2/'); +}); + +test('Settings applies an overlay, shows it in effect, and goes back', async ({ + page +}) => { + await openLab(page, 'settings/json'); + const header = page.locator('.lab-header'); + await expect(header).not.toContainText('config:'); + + await page + .getByRole('textbox', { name: 'Settings (JSON)' }) + .fill('{ "bi": { "dedupWindowMs": 0 } }'); + await applyAndReload(page); + await expect(header).toContainText('config: custom'); + await expect(await effective(page)).toContainText('"dedupWindowMs": 0'); + + await settingsSection(page, 'Overview'); + await press(page, "Reset all to the lab's defaults"); + await applyAndReload(page); + await expect(header).not.toContainText('config:'); + await expect(await effective(page)).toContainText('"dedupWindowMs": 400'); +}); + +test("Reset all puts every setting back to the lab's default, to apply with the rest, a debug flag at once", async ({ + page, + backend +}) => { + await backend.setBrokerMode('null'); + await openLab(page, 'settings/debug-flags?destination=noop', { + logging: { minLevel: 'warn' } + }); + await toggle(page, 'debugBI'); + await settingsSection(page, 'Overview'); + + await press(page, "Reset all to the lab's defaults"); + await expect + .poll(() => page.evaluate(() => localStorage.getItem('debugBI'))) + .toBeNull(); + for (const change of [ + 'Delivery › Destination', + 'Logging › Minimum level', + 'RUM broker › Answer /rum/init with' + ]) { + await expect(pending(page)).toContainText(change); + } + + await applyAndReload(page); + expect(new URL(page.url()).search).toBe(''); + expect((await backend.state()).brokerMode).toBe('ok'); + await expect( + page + .locator('lab-settings-overview .lab-card', { + hasText: "Different from the lab's defaults" + }) + .getByText('Nothing') + ).toBeVisible(); +}); + +test('Settings checks the overlay against the library: no unknown setting, no wrong type', async ({ + page +}) => { + await openLab(page, 'settings/json'); + const overlay = page.getByRole('textbox', { name: 'Settings (JSON)' }); + const settings = page.locator('lab-settings-page'); + + await overlay.fill('{ "bi": { "dedupWindow": 0 } }'); + await press(page, 'Apply and reload'); + await expect(settings).toContainText(`"bi.dedupWindow" isn't a setting`); + + await overlay.fill('{ "bi": { "dedupWindowMs": "0" } }'); + await press(page, 'Apply and reload'); + await expect(settings).toContainText( + '"bi.dedupWindowMs" should be a number.' + ); + + await overlay.fill('{ "scenario": {} }'); + await press(page, 'Apply and reload'); + await expect(settings).toContainText(`"scenario" isn't a section`); + + // Own settings only: what every object inherits is none. + await overlay.fill('{ "toString": {} }'); + await press(page, 'Apply and reload'); + await expect(settings).toContainText(`"toString" isn't a section`); + // Never applied. + await expect(page.locator('.lab-header')).not.toContainText('config:'); +}); + +test('Settings checks types against the library, not a wrong value in effect', async ({ + page +}) => { + await openLab(page, 'settings/json', { bi: { dedupWindowMs: '500' } }); + const overlay = page.getByRole('textbox', { name: 'Settings (JSON)' }); + + await overlay.fill('{ "bi": { "dedupWindowMs": 600 } }'); + await press(page, 'Apply and reload'); + // Applied: the page reloads with it. + await expect + .poll(() => new URL(page.url()).searchParams.get('config')) + .toBe('{"bi":{"dedupWindowMs":600}}'); +}); + +test('Settings controls write the common options into the overlay', async ({ + page +}) => { + await openLab(page, 'settings/json'); + await expect( + page.getByRole('textbox', { name: 'Settings (JSON)' }) + ).toHaveValue('{}'); + + await settingsSection(page, 'Scenarios'); + await toggle(page, 'Lifecycle step events'); + await settingsSection(page, 'BI events'); + await fill(page, 'BI dedup window', '250'); + const overlay = await json(page); + await expect(overlay).toHaveValue(/"emitLifecycleEvents": true/); + await expect(overlay).toHaveValue(/"dedupWindowMs": 250/); + + await applyAndReload(page); + const readout = await effective(page); + await expect(readout).toContainText('"emitLifecycleEvents": true'); + await expect(readout).toContainText('"dedupWindowMs": 250'); +}); + +test('eventNamespace renames the event types, and the inspector still sorts them', async ({ + page, + backend +}) => { + await asPane(page); + await openLab(page, 'bi', { identity: { eventNamespace: 'com.acme' } }); + await sessionStarted(page); + await page.locator('.lab-header-button').click(); + await press(page, 'Track a click'); + + await backend.rumUntil((e) => + e.some( + (event) => + event.type === 'com.acme.bi' && + event.details.eventName === 'lab_button_clicked' + ) + ); + await expect(page.locator('uwt-inspector-timeline')).toContainText( + 'lab_button_clicked' + ); +}); + +test('scenario settings: step events, user timings, and a step limit', async ({ + page, + backend +}) => { + // The library clears each measure once made; an observer still sees it. + await page.addInitScript(() => { + const seen: string[] = ((window as { labMeasures?: string[] }).labMeasures = + []); + new PerformanceObserver((list) => + seen.push(...list.getEntries().map((e) => e.name)) + ).observe({ entryTypes: ['measure'] }); + }); + await openLab(page, 'scenarios', { + scenarios: { emitLifecycleEvents: true, userTimings: true, maxSteps: 2 } + }); + await sessionStarted(page); + await press(page, 'Complete, with 3 steps'); + + const events = await backend.rumUntil((e) => + named(e, 'lab-happy-path').some((x) => x.type === 'com.uwt.scenario') + ); + const happy = named(events, 'lab-happy-path'); + const of = (type: string) => happy.filter((e) => e.type === type); + expect(of('com.uwt.scenario')[0].details).toMatchObject({ + stepCount: 3, + exceededStepsLimit: true + }); + // Only stored steps are sent on their own: two, not three. + expect(of('com.uwt.scenario.step').map((e) => e.details.name)).toEqual([ + 'fetch', + 'validate' + ]); + expect( + await page.evaluate( + () => (window as { labMeasures?: string[] }).labMeasures + ) + ).toContain('lab-happy-path [fetch]'); +}); + +test('BI dedup can be switched off', async ({ page, backend }) => { + await openLab(page, 'bi', { bi: { dedupWindowMs: 0 } }); + await sessionStarted(page); + await press(page, 'Fire twice at once'); + await press(page, 'Track a click'); + const events = await backend.rumUntil( + (e) => named(e, 'lab_button_clicked').length > 0 + ); + expect(named(events, 'lab_double_fired')).toHaveLength(2); +}); + +test('logging settings: a higher floor, and errors not mirrored to RUM', async ({ + page, + backend +}) => { + await openLab(page, 'logging', { + logging: { minLevel: 'warn', mirrorErrorsToRum: false } + }); + await sessionStarted(page); + await press(page, 'log'); + await press(page, 'warn'); + await press(page, 'error, with an Error'); + await expect + .poll(async () => (await backend.logs()).map((r) => r.level).sort()) + .toEqual(['error', 'warn']); + + // Tracked after the error, so a mirror would have arrived by now. + await navigate(page, 'BI events'); + await press(page, 'Track a click'); + const events = await backend.rumUntil( + (e) => named(e, 'lab_button_clicked').length > 0 + ); + expect(events.map((e) => e.type)).not.toContain( + 'com.amazon.rum.js_error_event' + ); +}); + +test('redaction: a value transform, a length cap, and no stacks', async ({ + page, + backend +}) => { + await openLab(page, 'logging', { + redaction: { + extraValueTransforms: ['mask-order-ids'], + maxStringLength: 12, + includeStack: false + } + }); + await press(page, 'error, with an Error'); + const record = await onlyLog(backend); + expect(record.metadata).toEqual({ orderId: 'A-****' }); + // Twelve characters kept, then the ellipsis. + expect(record.message).toBe('Saving the o…'); + expect(record.error!.message).toBe('Connection r…'); + expect(record.error!.stack).toBeUndefined(); +}); + +test('redaction: extra key and value patterns', async ({ page, backend }) => { + await openLab(page, 'logging', { + redaction: { + extraKeyPatterns: ['^orderId$'], + extraValuePatterns: ['Connection \\w+'] + } + }); + await press(page, 'error, with an Error'); + const record = await onlyLog(backend); + expect(record.metadata).toEqual({ orderId: '[redacted]' }); + expect(record.error!.message).not.toContain('Connection reset'); +}); + +test('redaction: URL queries kept when stripUrlQuery is off', async ({ + page, + backend +}) => { + await openLab(page, 'scenarios', { redaction: { stripUrlQuery: false } }); + await sessionStarted(page); + await press(page, 'Request that fails (500)'); + const events = await backend.rumUntil( + (e) => named(e, 'lab-http-error').length > 0 + ); + expect(named(events, 'lab-http-error')[0].details.error.message).toContain( + '?status=500' + ); +}); + +test('redact: false for logging stops its value scan only; BI still scans, and key names always redact', async ({ + page, + backend +}) => { + await openLab(page, 'privacy', { logging: { redact: false } }); + await sessionStarted(page); + await press(page, 'As BI metadata'); + await press(page, 'In a log message'); + + const record = await onlyLog(backend); + expect(record.message).toContain('anna.smith@example.com'); + expect(record.metadata!.password).toBe('[redacted]'); + + const events = await backend.rumUntil( + (e) => named(e, 'lab_privacy_checked').length > 0 + ); + expect(JSON.stringify(named(events, 'lab_privacy_checked'))).not.toContain( + 'anna.smith@example.com' + ); +}); + +test('Settings sets the startup options, and applying them reloads with them', async ({ + page +}) => { + await openLab(page, 'settings/delivery'); + await choose(page, 'Destination', 'noop'); + await choose(page, 'Broadcast channel', 'shared'); + await toggle(page, 'Telemetry'); + await settingsSection(page, 'Event Inspector'); + await choose(page, 'Availability', 'no shortcut'); + + // Nothing changes until applied. + expect(new URL(page.url()).searchParams.get('destination')).toBeNull(); + + await press(page, 'Apply and reload'); + await expect(page).toHaveURL(/destination=noop/); + + const params = new URL(page.url()).searchParams; + expect(params.get('destination')).toBe('noop'); + expect(params.get('channel')).toBe('shared'); + expect(params.get('inspector')).toBe('noshortcut'); + expect(params.get('telemetry')).toBe('off'); + const header = page.locator('.lab-header'); + await expect(header).toContainText('destination: noop'); + await expect(header).toContainText('telemetry: off'); + await expect(header).toContainText('channel: shared'); +}); + +test("every area's settings are controls in its section, and apply together as set", async ({ + page +}) => { + await openLab(page, 'settings/app-identity'); + await fill(page, 'Event namespace', 'acme'); + await settingsSection(page, 'Scenarios'); + await fill(page, 'Default timeout', '12345'); + await fill(page, 'Step limit', '7'); + await fill(page, 'Mark cleanup fallback', '999'); + await toggle(page, 'Redact scenario payloads'); + await settingsSection(page, 'Logging'); + await choose(page, 'Minimum level', 'warn'); + await toggle(page, 'Mirror errors to RUM'); + await toggle(page, 'Redact log values'); + await settingsSection(page, 'BI events'); + await fill(page, 'BI dedup keys kept', '77'); + await toggle(page, 'Redact BI payloads'); + await settingsSection(page, 'Privacy & redaction'); + await fill(page, 'Longest string', '321'); + await fill(page, 'Most keys', '9'); + await fill(page, 'Longest stack', '456'); + await toggle(page, 'Include stacks'); + await toggle(page, 'Strip URL queries'); + await settingsSection(page, 'Event Inspector'); + await fill(page, 'Events kept per kind', '25'); + await fill(page, 'Payload characters shown', '1234'); + await fill(page, 'Keep events from before opening', '5'); + await settingsSection(page, 'Delivery'); + await toggle(page, 'Batch logs'); + + // Each control writes into the overlay, which shows it. + const overlay = await json(page); + await expect(overlay).toHaveValue(/"eventNamespace": "acme"/); + await expect(overlay).toHaveValue(/"minLevel": "warn"/); + await expect(overlay).toHaveValue(/"captureBeforeOpen": 5/); + await expect(overlay).toHaveValue(/"logBatching": true/); + + await applyAndReload(page); + + const readout = await effective(page); + for (const line of [ + '"eventNamespace": "acme"', + '"defaultTimeoutMs": 12345', + '"maxSteps": 7', + '"markCleanupFallbackMs": 999', + '"minLevel": "warn"', + '"mirrorErrorsToRum": false', + '"dedupMaxKeys": 77', + '"maxStringLength": 321', + '"maxKeys": 9', + '"maxStackLength": 456', + '"includeStack": false', + '"stripUrlQuery": false', + '"maxEventsPerSection": 25', + '"maxPayloadCharsInView": 1234', + '"captureBeforeOpen": 5' + ]) { + await expect(readout).toContainText(line); + } + // The three `redact` switches each turned their own concern's scrubbing off. + const applied = await json(page); + await expect(applied).toHaveValue(/"scenarios": \{[^}]*"redact": false/); + await expect(applied).toHaveValue(/"logging": \{[^}]*"redact": false/); + await expect(applied).toHaveValue(/"bi": \{[^}]*"redact": false/); +}); + +test('redaction lists are picked from, and written as lines of RegExp sources', async ({ + page +}) => { + await openLab(page, 'settings/privacy'); + + // The five value patterns start picked; ipv4 is left out, a transform added. + const patterns = page.getByRole('combobox', { + name: 'Value patterns scanned' + }); + await patterns.press('ArrowDown'); + await page.getByRole('option', { name: /^ipv4/ }).click(); + await page.keyboard.press('Escape'); + await choose(page, 'Value transforms', 'mask-order-ids'); + await page + .getByRole('textbox', { name: /^Extra key patterns/ }) + .fill('^acct_\nsecret'); + await page + .getByRole('textbox', { name: /^Extra value patterns/ }) + .fill('ORD-\\d+'); + + const overlay = await json(page); + await expect(overlay).toHaveValue(/"scanValuePatterns": \[[^\]]*"email"/); + await expect(overlay).not.toHaveValue(/"ipv4"/); + await expect(overlay).toHaveValue( + /"extraValueTransforms": \[\s*"mask-order-ids"/ + ); + await expect(overlay).toHaveValue( + /"extraKeyPatterns": \[\s*"\^acct_",\s*"secret"/ + ); + await expect(overlay).toHaveValue(/"extraValuePatterns": \[\s*"ORD-\\\\d\+"/); + + await applyAndReload(page); + + const readout = await effective(page); + await expect(readout).toContainText('"email"'); + await expect(readout).not.toContainText('"ipv4"'); + await expect(readout).toContainText('ƒ mask-order-ids'); + await expect(readout).toContainText('/^acct_/i'); + // The readout is JSON: the backslash of `\d` shows doubled. + await expect(readout).toContainText('/ORD-\\\\d+/'); +}); + +test('loggers each have a level, added and removed one at a time', async ({ + page +}) => { + await openLab(page, 'settings/logging'); + // The lab starts with one: noisy, at warn. + await expect(page.getByText('noisy', { exact: true })).toBeVisible(); + + await page.getByRole('button', { name: 'Remove logger noisy' }).click(); + await fill(page, 'Logger name', 'orders'); + await choose(page, 'Level for a new logger', 'error'); + await press(page, 'Add logger'); + await expect( + page.getByRole('button', { name: 'Remove logger orders' }) + ).toBeVisible(); + await expect(await json(page)).toHaveValue( + /"levels": \{\s*"orders": "error"\s*\}/ + ); + + await applyAndReload(page); + const readout = await effective(page); + await expect(readout).toContainText('"orders": "error"'); + await expect(readout).not.toContainText('"noisy"'); +}); + +test("the Event Inspector's shortcuts are listed, removed and added", async ({ + page +}) => { + await openLab(page, 'settings/event-inspector'); + await expect( + page.getByRole('button', { name: 'Remove shortcut Shift+E+I' }) + ).toBeVisible(); + + await page.getByRole('button', { name: 'Remove shortcut Shift+E+I' }).click(); + await fill(page, 'Key (KeyboardEvent.code)', 'F9'); + await press(page, 'Add shortcut'); + await expect(await json(page)).toHaveValue( + /"shortcuts": \[\s*\{\s*"code": "F9"\s*\}\s*\]/ + ); + + await applyAndReload(page); + const inspector = page.locator('uwt-inspector'); + await page.keyboard.down('Shift'); + await page.keyboard.press('KeyE'); + await page.keyboard.press('KeyI'); + await page.keyboard.up('Shift'); + await expect(inspector).toHaveCount(0); + await page.keyboard.press('F9'); + await expect(inspector).toBeVisible(); +}); + +test("the application's identity is set from the page", async ({ page }) => { + await openLab(page, 'settings/app-identity'); + + await fill(page, 'Application name', 'acme-shop'); + await fill(page, 'Environment', 'qa'); + await fill(page, 'Version', '2.3.4'); + await applyAndReload(page); + + const readout = await effective(page); + await expect(readout).toContainText('"application": "acme-shop"'); + await expect(readout).toContainText('"environment": "qa"'); + await expect(readout).toContainText('"version": "2.3.4"'); +}); + +test("the mock broker's app monitor settings are controls, and apply with the rest", async ({ + page, + backend +}) => { + await openLab(page, 'settings/app-monitor'); + const config = page.getByRole('textbox', { + name: 'Laid over the app monitor config (JSON)' + }); + + await fill(page, 'Session sample rate', '0.5'); + await fill(page, 'Session event limit', '25'); + await fill(page, 'Dispatch interval', '500'); + await toggle(page, 'Allow cookies'); + await toggle(page, 'X-Ray tracing'); + await toggle(page, 'Automatic page view'); + await toggle(page, 'Record resource URLs'); + await page + .getByRole('textbox', { name: /^Pages to exclude/ }) + .fill('^/admin'); + await page + .getByRole('textbox', { name: /^Pages to include/ }) + .fill('^/shop\n^/cart'); + + // Each control writes into the overlay, which shows it. + await expect(config).toHaveValue(/"sessionSampleRate":0.5/); + await expect(config).toHaveValue( + /"pagesToInclude":\["\^\/shop","\^\/cart"\]/ + ); + // Nothing is set until applied. + expect((await backend.state()).brokerConfig).toEqual({}); + + await press(page, 'Apply and reload'); + await expect + .poll(async () => (await backend.state()).brokerConfig) + .toEqual({ + sessionSampleRate: 0.5, + sessionEventLimit: 25, + dispatchInterval: 500, + allowCookies: false, + enableXRay: true, + disableAutoPageView: true, + recordResourceUrl: false, + pagesToExclude: ['^/admin'], + pagesToInclude: ['^/shop', '^/cart'] + }); +}); + +test('the broker controls start at what the broker answers with', async ({ + page +}) => { + await openLab(page, 'settings/app-monitor'); + const { config } = await (await page.request.get('/rum/init')).json(); + + const shown = async (label: string) => + page.locator('cps-input', { hasText: label }).locator('input').inputValue(); + expect(await shown('Session sample rate')).toBe( + String(config.sessionSampleRate) + ); + expect(await shown('Session event limit')).toBe( + String(config.sessionEventLimit) + ); + expect(await shown('Dispatch interval')).toBe( + String(config.dispatchInterval) + ); + // On, as the broker's answer has it. + await expect( + page.locator('cps-switch', { hasText: 'Allow cookies' }).locator('input') + ).toBeChecked({ checked: config.allowCookies }); +}); + +test('inspector settings: a custom shortcut, a smaller section, a smaller view', async ({ + page +}) => { + await asPane(page); + await openLab(page, 'inspector', { + inspector: { + maxEventsPerSection: 10, + maxPayloadCharsInView: 1000, + shortcuts: [{ code: 'F9' }] + } + }); + const inspector = page.locator('uwt-inspector'); + + await page.keyboard.down('Shift'); + await page.keyboard.press('KeyE'); + await page.keyboard.press('KeyI'); + await page.keyboard.up('Shift'); + await expect(inspector).toHaveCount(0); + + await page.keyboard.press('F9'); + await expect(inspector).toBeVisible(); + + await press(page, 'A burst of 600 BI events'); + await expect(inspector).toContainText('10 events · 590 oldest dropped'); + await expect(inspector.locator('cps-button-toggle')).toContainText('BI 10'); + + await press(page, 'A 300,000-character payload'); + const timeline = page.locator('uwt-inspector-timeline'); + await timeline + .locator('tbody tr', { hasText: 'lab_huge_payload' }) + .getByRole('button', { name: 'Expand row' }) + .click(); + await expect(timeline).toContainText(/showing the first 1,?000 characters/); +}); + +test('a pattern that is not a RegExp is left out with a warning, and the lab still starts', async ({ + page, + backend +}) => { + const warnings: string[] = []; + page.on('console', (m) => m.type() === 'warning' && warnings.push(m.text())); + await openLab(page, 'logging', { + redaction: { extraKeyPatterns: ['(', '^orderId$'] } + }); + await press(page, 'error, with an Error'); + expect((await onlyLog(backend)).metadata).toEqual({ orderId: '[redacted]' }); + expect(warnings).toContainEqual( + expect.stringContaining('"(" isn\'t a RegExp') + ); +}); + +test('the start page keeps the startup options', async ({ page }) => { + await page.goto('/?destination=noop'); + await expect(page).toHaveURL(/\/scenarios\?destination=noop$/); +}); + +test('in-app links keep the startup options, and so does applying an overlay', async ({ + page +}) => { + await openLab(page, 'bi?destination=noop'); + await navigate(page, 'Settings'); + expect(new URL(page.url()).searchParams.get('destination')).toBe('noop'); + await (await json(page)).fill('{ "bi": { "dedupWindowMs": 0 } }'); + expect(new URL(page.url()).searchParams.get('destination')).toBe('noop'); + await press(page, 'Apply and reload'); + + await expect(page.locator('.lab-header')).toContainText('config: custom'); + await expect(page.locator('.lab-header')).toContainText('destination: noop'); + expect(new URL(page.url()).searchParams.get('destination')).toBe('noop'); +}); diff --git a/playwright/ssr.spec.ts b/playwright/ssr.spec.ts new file mode 100644 index 0000000..782d511 --- /dev/null +++ b/playwright/ssr.spec.ts @@ -0,0 +1,35 @@ +import { expect, named, openLab, press, sessionStarted, test } from './lab'; + +test('the server renders the page, and sends no telemetry itself', async ({ + request, + backend +}) => { + const html = await (await request.get('/bi')).text(); + + expect(html).toMatch(/]*>BI events<\/h1>/); + expect(html).toContain('class="lab-nav"'); + // Annotations for hydration: the browser reuses this DOM. + expect(html).toMatch(/ngh="/); + // What the render asked of the lab's destinations, kept on the server + // rather than sent: nothing. A page still sending from the last test + // sends from the browser, and doesn't count. + expect((await backend.state()).serverSends).toEqual([]); +}); + +test('the browser hydrates the rendered page, and telemetry starts there', async ({ + page, + backend +}) => { + const messages: string[] = []; + page.on('console', (m) => messages.push(m.text())); + + await openLab(page, 'bi'); + await sessionStarted(page); + // Angular reports, in development builds, what it hydrated. + await expect + .poll(() => messages.find((m) => m.startsWith('Angular hydrated'))) + .toBeTruthy(); + + await press(page, 'Track a click'); + await backend.rumUntil((e) => named(e, 'lab_button_clicked').length > 0); +}); diff --git a/playwright/tabs.spec.ts b/playwright/tabs.spec.ts new file mode 100644 index 0000000..a46e99d --- /dev/null +++ b/playwright/tabs.spec.ts @@ -0,0 +1,41 @@ +import { expect, named, openLab, press, test, watchErrors } from './lab'; + +test("two tabs: each fragment reaches its own tab's shell, over that tab's channel", async ({ + context, + backend, + errors +}) => { + // Both Event Inspectors open as panes, beside their fragments. + await context.addInitScript(() => + localStorage.setItem( + 'ngx-ui-watchtower.eventInspector', + JSON.stringify({ dock: 'right' }) + ) + ); + const tabs = [await context.newPage(), await context.newPage()]; + tabs.forEach((tab) => watchErrors(tab, errors)); + for (const tab of tabs) { + await openLab(tab, 'web-fragment'); + await expect(tab.locator('web-fragment h1')).toHaveText( + 'Embedded application' + ); + await tab.locator('.lab-header-button').click(); + await expect(tab.locator('uwt-inspector')).toBeVisible(); + } + const [first, second] = tabs; + + await press(second.locator('web-fragment'), 'Track a BI event'); + + // The fragment found its shell's channel on `window.top`, from inside the + // Web Fragment's hidden iframe: only its own tab saw the event. + await expect(second.locator('uwt-inspector')).toContainText( + 'fragment_button_clicked' + ); + const events = await backend.rumUntil( + (e) => named(e, 'fragment_button_clicked').length > 0 + ); + expect(named(events, 'fragment_button_clicked')).toHaveLength(1); + await expect(first.locator('uwt-inspector')).not.toContainText( + 'fragment_button_clicked' + ); +}); diff --git a/playwright/telemetries.spec.ts b/playwright/telemetries.spec.ts new file mode 100644 index 0000000..635aa2b --- /dev/null +++ b/playwright/telemetries.spec.ts @@ -0,0 +1,138 @@ +import { + expect, + openLab, + press, + RumEvent, + sessionStarted, + Telemetry, + test +} from './lab'; + +const ALL: Telemetry[] = [ + 'errors', + 'performance', + 'interaction', + 'http', + 'replay' +]; + +const types = (events: RumEvent[]) => + [...new Set(events.map((e) => e.type.replace('com.amazon.rum.', '')))].sort(); + +const cases: { + name: string; + telemetries: Telemetry[] | null; + chip: string; + expected: (types: string[]) => void; +}[] = [ + { + name: "the library's default", + telemetries: null, + chip: 'default', + expected: (t) => { + expect(t).toEqual( + expect.arrayContaining([ + 'performance_navigation_event', + 'xray_trace_event' + ]) + ); + expect(t).not.toContain('dom_event'); + expect(t).not.toContain('rrweb_event'); + } + }, + { + name: 'none', + telemetries: [], + chip: 'none', + // Only RUM's session start and page views: without 'errors', RUM rejects + // the mirrored error, so no error event is sent either. + expected: (t) => + expect(t.filter((type) => !/session_start|page_view/.test(type))).toEqual( + [] + ) + }, + { + name: 'all five', + telemetries: ALL, + chip: ALL.join(', '), + expected: (t) => + expect(t).toEqual(expect.arrayContaining(['dom_event', 'rrweb_event'])) + } +]; + +for (const { name, telemetries, chip, expected } of cases) { + test(`RUM telemetries, ${name}: as set, quiet while idle, errors mirrored only with 'errors'`, async ({ + page, + backend + }) => { + await backend.setTelemetries(telemetries); + await openLab(page, 'logging'); + const session = await sessionStarted(page); + await expect(page.locator('.lab-header')).toContainText( + `rum telemetries: ${chip}` + ); + const mine = async () => + (await backend.rum()).filter((e) => e.user.sessionId === session); + + // Replay sends its opening snapshot about 5 s in; after that, nothing + // should arrive while nobody uses the page — the lab's own polling least + // of all. + await page.waitForTimeout(8000); + const loaded = (await mine()).length; + await page.waitForTimeout(6000); + expect((await mine()).length).toBe(loaded); + + await press(page, 'error, with an Error'); + const mirrored = async () => + (await mine()).some( + (e) => + e.type === 'com.amazon.rum.js_error_event' && + JSON.stringify(e.details).includes('Connection reset') + ); + if (telemetries === null || telemetries.includes('errors')) { + await expect.poll(mirrored).toBe(true); + } else { + // RUM rejects the error without its 'errors' telemetry; give it time. + await page.waitForTimeout(2000); + expect(await mirrored()).toBe(false); + } + + const events = await mine(); + expect(events.filter((e) => JSON.stringify(e).includes('/__lab/'))).toEqual( + [] + ); + expected(types(events)); + }); +} + +test('Settings switches between the default and a list to edit, set on Apply', async ({ + page, + backend +}) => { + await openLab(page, 'settings/rum-broker'); + const telemetriesList = page.locator('cps-select[label="Telemetries"]'); + const useDefault = page + .locator('cps-switch', { hasText: "The library's default" }) + .locator('label.cps-switch'); + await expect(telemetriesList).toHaveCount(0); + + await useDefault.click(); + await expect(telemetriesList).toBeVisible(); + await press(page, 'Apply and reload'); + await expect(page.locator('.lab-header')).toContainText( + 'rum telemetries: errors, performance, http' + ); + expect((await backend.state()).telemetries).toEqual([ + 'errors', + 'performance', + 'http' + ]); + + await useDefault.click(); + await expect(telemetriesList).toHaveCount(0); + await press(page, 'Apply and reload'); + await expect(page.locator('.lab-header')).toContainText( + 'rum telemetries: default' + ); + expect((await backend.state()).telemetries).toBeNull(); +}); diff --git a/playwright/tsconfig.json b/playwright/tsconfig.json new file mode 100644 index 0000000..da2eda9 --- /dev/null +++ b/playwright/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "noEmit": true, + "types": ["node"] + }, + "include": ["**/*.ts", "../playwright.config.ts"] +} diff --git a/playwright/wire.spec.ts b/playwright/wire.spec.ts new file mode 100644 index 0000000..4c0ef25 --- /dev/null +++ b/playwright/wire.spec.ts @@ -0,0 +1,160 @@ +import { + expect, + navigate, + openLab, + Page, + press, + sessionStarted, + settingsSection, + test +} from './lab'; + +/** + * Picks the option starting with `option` in the select labelled `label`, + * opened from the keyboard: a click in a list's box can land on a chip's + * Remove, wherever the fonts put the chips. + */ +async function choose(page: Page, label: string, option: string) { + await page.getByRole('combobox', { name: label }).press('ArrowDown'); + await page.getByRole('option', { name: new RegExp(`^${option}`) }).click(); +} + +test('Settings sets the broker, all at once with Apply and reload; the header tells this load from the next', async ({ + page, + backend +}) => { + await openLab(page, 'settings/rum-broker'); + await choose(page, 'Answer /rum/init with', 'null —'); + await page + .locator('cps-switch', { hasText: "The library's default" }) + .locator('label.cps-switch') + .click(); + await choose(page, 'Telemetries', 'replay'); + await settingsSection(page, 'App monitor'); + const overlay = page.getByRole('textbox', { + name: 'Laid over the app monitor config (JSON)' + }); + await overlay.fill('{ "sessionSampleRate": 0 }'); + // Past the lab's next poll of the backend, the draft is still there. + await page.waitForTimeout(2000); + await expect(overlay).toHaveValue('{ "sessionSampleRate": 0 }'); + // Nothing is set until applied: not the mode, the list, or the overlay. + const { brokerMode, telemetries, brokerConfig } = await backend.state(); + expect({ brokerMode, telemetries, brokerConfig }).toEqual({ + brokerMode: 'ok', + telemetries: null, + brokerConfig: {} + }); + + await press(page, 'Apply and reload'); + await expect(page.locator('.lab-header')).toContainText('broker: null'); + expect(await backend.state()).toMatchObject({ + brokerMode: 'null', + telemetries: ['errors', 'performance', 'http', 'replay'], + brokerConfig: { sessionSampleRate: 0 } + }); + // After the reload, the fields show what is set. + await expect( + page.getByRole('textbox', { + name: 'Laid over the app monitor config (JSON)' + }) + ).toHaveValue('{"sessionSampleRate":0}'); + + // Set elsewhere, for the next load: the header says so. + await backend.setBrokerMode('ok'); + await expect(page.locator('.lab-header')).toContainText( + 'broker: null → ok after reload' + ); +}); + +test('the mock backend keeps only what has the shape it stores', async ({ + page, + request, + backend +}) => { + await request.post('/appmonitors/x', { + data: { RumEvents: [{ id: 'no-type' }] } + }); + await request.post('/api/logs', { data: ['not a record'] }); + expect(await backend.rum()).toEqual([]); + expect(await backend.logs()).toEqual([]); + + // Every page still reads it. + await openLab(page, 'wire'); + await expect(page.locator('.lab-status')).toContainText('batches: 0'); +}); + +test("another site's page can't change the mock backend", async ({ + request, + backend +}) => { + const res = await request.post('/__lab/broker-mode', { + data: { mode: 'null' }, + headers: { Origin: 'https://elsewhere.example' } + }); + + expect(res.status()).toBe(403); + expect((await backend.state()).brokerMode).toBe('ok'); +}); + +test('a broker overlay that is not JSON stops Apply, and sets nothing', async ({ + page, + backend +}) => { + await openLab(page, 'settings/app-monitor'); + await page + .getByRole('textbox', { name: 'Laid over the app monitor config (JSON)' }) + .fill('[1, 2]'); + await press(page, 'Apply and reload'); + await expect(page.locator('lab-settings-page')).toContainText( + 'Not a JSON object.' + ); + expect((await backend.state()).brokerConfig).toEqual({}); +}); + +test("the tables show what arrived, RUM's own events on request, until a confirmed Reset empties them", async ({ + page, + backend +}) => { + await openLab(page, 'wire'); + await sessionStarted(page); + await navigate(page, 'BI events'); + await press(page, 'Track a click'); + await navigate(page, 'Logging'); + await press(page, 'warn'); + await navigate(page, 'On the wire'); + const rumTable = page.locator('.lab-card', { hasText: 'RUM events (' }); + const logTable = page.locator('.lab-card', { hasText: 'Log records (' }); + + await expect(rumTable).toContainText('lab_button_clicked'); + await expect(rumTable).not.toContainText('com.amazon.rum.page_view_event'); + await expect(logTable).toContainText('A lab warning'); + + // The filter sits with the table it filters. + const libraryOnly = rumTable.locator('cps-switch', { + hasText: 'Library events only' + }); + await libraryOnly.click(); + await expect(rumTable).toContainText('com.amazon.rum.page_view_event'); + await libraryOnly.click(); + + // Asked first; declined, nothing goes. + let asked = ''; + page.once('dialog', (dialog) => { + asked = dialog.message(); + return dialog.dismiss(); + }); + await press(page, 'Reset the mock backend'); + await expect.poll(() => asked).toContain('Empty the mock backend?'); + expect((await backend.logs()).length).toBeGreaterThan(0); + + // RUM's own events can still arrive after it; the library's can't. + page.once('dialog', (dialog) => dialog.accept()); + await press(page, 'Reset the mock backend'); + await expect(page.getByRole('status')).toContainText( + 'Reset: the mock backend is empty.' + ); + await expect(rumTable.locator('h2')).toHaveText('RUM events (0)'); + await expect(logTable.locator('h2')).toHaveText('Log records (0)'); + expect(await backend.logs()).toEqual([]); +}); diff --git a/projects/ngx-ui-watchtower/DESIGN.md b/projects/ngx-ui-watchtower/DESIGN.md index 0e6eb0d..7110b69 100644 --- a/projects/ngx-ui-watchtower/DESIGN.md +++ b/projects/ngx-ui-watchtower/DESIGN.md @@ -24,7 +24,7 @@ configuration, log backend and AWS credentials. - **Product signal.** Record feature adoption and interaction events without putting business vocabulary inside the telemetry infrastructure itself. - **Developer debugging.** Let any developer see exactly what telemetry is - produced, in any environment, with a LocalStorage flag — + produced, in any environment, with a LocalStorage flag or an in-app popup — no rebuild, no config change, no production switch. - **Reusability.** Ship as a library any Angular application can install and configure. @@ -85,24 +85,28 @@ flowchart TD | `UwtNoopTelemetrySink` | Explicit opt-out — everything runs, nothing ships | | `UwtLogApiProvider` | The seam where the application supplies its log store — send, query, optional flush | | `UwtRumCredentialsProvider` | The seam where the application supplies its AWS details | +| `UwtTelemetryMonitor` | A read-only stream of everything handed to a destination | Everything is wired through Angular DI, so a destination can be swapped in production and stubbed in tests. ### Entry points -Two, each pulling in only what it needs: +Three, each pulling in only what it needs: -| Entry point | Contains | Needs | -| ------------------------------------ | ------------------------------------ | ------------------------------- | -| `@absaoss-cps/ngx-ui-watchtower` | Everything except the one below | Angular | -| `@absaoss-cps/ngx-ui-watchtower/rum` | `UwtRumTelemetrySink`, its providers | `aws-rum-web`, an optional peer | +| Entry point | Contains | Needs | +| ------------------------------------------------ | --------------------------------------- | --------------------------------------------------------------------------- | +| `@absaoss-cps/ngx-ui-watchtower` | Everything except the two below | Angular | +| `@absaoss-cps/ngx-ui-watchtower/rum` | `UwtRumTelemetrySink`, its providers | `aws-rum-web`, an optional peer | +| `@absaoss-cps/ngx-ui-watchtower/event-inspector` | The [Event Inspector](#event-inspector) | `cps-ui-kit` (with `@angular/forms`, `@angular/animations`), optional peers | A bundler resolves every static import in a module graph, and a dynamic `import()`'s specifier, whether or not that code path ever runs. Keeping the RUM sink and its `await import('aws-rum-web')` out of the main entry is what lets an app using only `'broadcast'`/`'noop'` build without `aws-rum-web` -installed. +installed. The Event Inspector is split out the same way: it is the only UI +in the package, and nothing in the main entry — bundle or `.d.ts` — +references cps-ui-kit. ### Public API @@ -110,18 +114,19 @@ Everything each entry point exports, by role. **`@absaoss-cps/ngx-ui-watchtower`** -| Role | Exports | -| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Setup | `provideUwtTelemetry`, `withLogging`, `withScenarios`, `withBIEvents`, `withRedaction`, `UwtTelemetryFeature`, `provideUwtTelemetrySink`, `UwtTelemetryLocalSinkMode` (`'broadcast' \| 'noop'`), `provideUwtTelemetryBroadcastHost`, `provideUwtTelemetryDestination` + `UwtTelemetryDestinationOptions` | -| Configuration | `UWT_TELEMETRY_IDENTITY` + `UwtTelemetryIdentity`; `UWT_LOG_CONFIG` + `UwtLogConfig`; `UWT_SCENARIO_TELEMETRY_CONFIG` + `UwtScenarioTelemetryConfig`; `UWT_BI_TELEMETRY_CONFIG` + `UwtBITelemetryConfig`; `UWT_REDACT_CONFIG`; `UWT_DEFAULT_TELEMETRY_CONFIG` (every default) | -| Scenarios | `UwtScenarioTelemetryService`, `UwtScenario`, `traceScenario` + `UwtTraceScenarioOptions`; models `UwtScenarioOptions`, `UwtScenarioOutcome`, `UwtScenarioStepDetail`, `UwtScenarioRecord`, `UwtScenarioStep`, `UwtScenarioStepEvent`, `UwtScenarioAggregate`, `UwtScenarioStatus`, `UwtScenarioStepStatus` | -| BI events | `UwtBITelemetryService`; `UwtBIEvent`, `UwtBIEventDetail` (`scenarioId`, `feature`) | -| Logging | `UwtLoggerService`, `UwtLogger`; `UwtLogDetail`, `UwtLogRecord`, `UwtLogLevel`, `UWT_LOG_LEVEL_ORDER`; `UWT_LOG_API_PROVIDER`, `UwtLogApiProvider`, `UwtLogQuery`; `UwtNoopLogApiProvider`, `UwtBroadcastLogApiProvider` | -| Name registries | `UwtScenarioNames` / `UwtScenarioName`, `UwtScenarioSteps` / `UwtStepName`, `UwtBIEventNames` / `UwtBIEventName`, `UwtLoggerNames` / `UwtLoggerName` | -| Sinks | `UwtTelemetrySink` (abstract), `UwtNoopTelemetrySink`, `UwtBroadcastTelemetrySink`, `UwtTelemetryBroadcastHost`, `UWT_BROADCAST_CHANNEL`, `UWT_DEFAULT_BROADCAST_CHANNEL` (`'ngx-ui-watchtower'`); `uwtClassifyTelemetryEvent` + `UwtTelemetrySinkEvent`, for a sink to tell what it received | -| Shared models | `UwtTelemetryMetadata`, `UwtTelemetryError`, `UwtTelemetryAttribution`; event types `UWT_DEFAULT_EVENT_NAMESPACE`, `UWT_TELEMETRY_EVENT_TYPE`, `uwtEventTypes()`, `UwtTelemetryEventTypes` | -| Redaction, for custom sinks | `uwtRedactMetadata`, `uwtNormalizeError`, `uwtScrubString`, `uwtRedactConfigFor`, `UwtRedactConfig`, `UWT_DEFAULT_REDACT_CONFIG`, `UwtPiiValuePattern`, `UWT_REDACTED` (`'[redacted]'`, the replacement value) | -| Utilities | `uwtUuid()`, `uwtIsDebugEnabled(flag, name?)` + `UwtDebugFlag` (`'debugLogger' \| 'debugScenario' \| 'debugBI'`) | +| Role | Exports | +| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Setup | `provideUwtTelemetry`, `withLogging`, `withScenarios`, `withBIEvents`, `withRedaction`, `withTelemetryEnabled`, `UwtTelemetryFeature`, `provideUwtTelemetrySink`, `UwtTelemetryLocalSinkMode` (`'broadcast' \| 'noop'`), `provideUwtTelemetryBroadcastHost`, `provideUwtTelemetryDestination` + `UwtTelemetryDestinationOptions` | +| Configuration | `UWT_TELEMETRY_IDENTITY` + `UwtTelemetryIdentity`; `UWT_TELEMETRY_ENABLED`; `UWT_LOG_CONFIG` + `UwtLogConfig`; `UWT_SCENARIO_TELEMETRY_CONFIG` + `UwtScenarioTelemetryConfig`; `UWT_BI_TELEMETRY_CONFIG` + `UwtBITelemetryConfig`; `UWT_REDACT_CONFIG`; `UWT_DEFAULT_TELEMETRY_CONFIG` (every default) | +| Scenarios | `UwtScenarioTelemetryService`, `UwtScenario`, `traceScenario` + `UwtTraceScenarioOptions`; models `UwtScenarioOptions`, `UwtScenarioOutcome`, `UwtScenarioStepDetail`, `UwtScenarioRecord`, `UwtScenarioStep`, `UwtScenarioStepEvent`, `UwtScenarioAggregate`, `UwtScenarioStatus`, `UwtScenarioStepStatus` | +| BI events | `UwtBITelemetryService`; `UwtBIEvent`, `UwtBIEventDetail` (`scenarioId`, `feature`) | +| Logging | `UwtLoggerService`, `UwtLogger`; `UwtLogDetail`, `UwtLogRecord`, `UwtLogLevel`, `UWT_LOG_LEVEL_ORDER`; `UWT_LOG_API_PROVIDER`, `UwtLogApiProvider`, `UwtLogQuery`; `UwtNoopLogApiProvider`, `UwtBroadcastLogApiProvider` | +| Name registries | `UwtScenarioNames` / `UwtScenarioName`, `UwtScenarioSteps` / `UwtStepName`, `UwtBIEventNames` / `UwtBIEventName`, `UwtLoggerNames` / `UwtLoggerName` | +| Sinks | `UwtTelemetrySink` (abstract), `UwtNoopTelemetrySink`, `UwtBroadcastTelemetrySink`, `UwtTelemetryBroadcastHost`, `UWT_BROADCAST_CHANNEL`, `UWT_DEFAULT_BROADCAST_CHANNEL` (`'ngx-ui-watchtower'`); `uwtClassifyTelemetryEvent` + `UwtTelemetrySinkEvent`, for a sink to tell what it received | +| Shared models | `UwtTelemetryMetadata`, `UwtTelemetryError`, `UwtTelemetryAttribution`; event types `UWT_DEFAULT_EVENT_NAMESPACE`, `UWT_TELEMETRY_EVENT_TYPE`, `uwtEventTypes()`, `UwtTelemetryEventTypes` | +| Monitor | `UwtTelemetryMonitor`; `UwtTelemetryObservedEvent`, `UwtTelemetryPublishInput`, `UwtTelemetryEventKind`, `UwtTelemetryDestination`, `UwtTelemetryEventOrigin`, `UwtJsonValue`, `UwtJsonObject` | +| Redaction, for custom sinks | `uwtRedactMetadata`, `uwtNormalizeError`, `uwtScrubString`, `uwtRedactConfigFor`, `UwtRedactConfig`, `UWT_DEFAULT_REDACT_CONFIG`, `UwtPiiValuePattern`, `UWT_REDACTED` (`'[redacted]'`, the replacement value) | +| Utilities | `uwtUuid()`, `uwtIsDebugEnabled(flag, name?)` + `UwtDebugFlag` (`'debugLogger' \| 'debugScenario' \| 'debugBI'`), `uwtMarkSessionStart(sessionId, now?)`, for a sink that knows when its sessions start | **`@absaoss-cps/ngx-ui-watchtower/rum`:** `provideUwtTelemetryRumSink`, `UwtRumTelemetrySink`, `UWT_RUM_CREDENTIALS_PROVIDER`, `UwtRumCredentialsProvider`, @@ -129,6 +134,15 @@ Everything each entry point exports, by role. `UwtRumCredentials` (`accessKeyId`, `secretAccessKey`, `sessionToken`, `expiration`). +**`@absaoss-cps/ngx-ui-watchtower/event-inspector`:** `provideUwtEventInspector`, +`UwtEventInspectorService`, `UWT_EVENT_INSPECTOR_CONFIG`, +`UwtEventInspectorConfig`, `UWT_DEFAULT_INSPECTOR_CONFIG`, +`UwtInspectorShortcut`, `UWT_DEFAULT_INSPECTOR_SHORTCUTS`, +`UwtInspectorDock`, `UwtInspectorExport`, `UwtInspectorSectionId`, +`UwtInspectorKindFilter`, and the filter types +`UwtInspectorFilterState`, `UwtInspectorFieldFilter`, +`UwtInspectorFilterOperator` — see §12, "Event Inspector". + ### Destinations A realm has exactly **one telemetry destination**: the sink every scenario @@ -179,8 +193,10 @@ redacted), deduplicate BI events, time anything, or guard the library's calls — every call the library makes into it, `init` included, is wrapped fail-open. The one exception is `setUserId`/`getUserId`, which the application calls on the sink directly; a destination should not throw -there. Log lines are not part of it: they go to the application's -own log backend through `UWT_LOG_API_PROVIDER`, whatever the destination. +there. The telemetry monitor observes the hand-off, so the Event Inspector +works with any destination. Log lines are not part of it: they go to the +application's own log backend through `UWT_LOG_API_PROVIDER`, whatever the +destination. ```ts providers: [ @@ -190,6 +206,17 @@ providers: [ ]; ``` +### The telemetry monitor + +Every place that hands something to a destination — the scenario's emit, +`track()`, the logger's delivery and its RUM mirror, and the broadcast host +forwarding a fragment's events and logs — then publishes the same object to +`UwtTelemetryMonitor`, inside the same fail-open block. So nothing is sent +twice, and a hand-off that throws is not shown. A fragment's event appears +once in each popup: as its own in the fragment's, as forwarded in the +shell's — there with the shell's session and user, as sent (see Identity +across realms). + ### Packaging and internals - **Every export is explicit.** The barrels list their exports one by one — @@ -198,10 +225,40 @@ providers: [ broadcast plumbing stay internal so they can change freely. Redaction is the exception: a custom sink needs `uwtRedactMetadata`, `uwtNormalizeError`, `uwtScrubString` and `uwtRedactConfigFor`. -- **The secondary entry keeps private copies of a few utilities.** ng-packagr +- **Secondary entries keep private copies of a few utilities.** ng-packagr fixes each entry point's `rootDir` to its own `src`, so - `@absaoss-cps/ngx-ui-watchtower/rum` can't import the main entry's - internals by relative path and carries small copies of what it needs. + `@absaoss-cps/ngx-ui-watchtower/rum` and `@absaoss-cps/ngx-ui-watchtower/event-inspector` can't import the main + entry's internals by relative path and carry small copies of what they + need. +- **cps-ui-kit comes from npm.** It is an optional peer of the package and a + dev dependency of the workspace, so the build and the unit tests resolve it + from `node_modules` and it is never compiled into the package. The + workspace uses cps-ui-kit 21, the lowest version the Event Inspector + supports, as it uses the lowest supported Angular. npm checks an optional + peer's range whenever it is installed, so an app on a cps-ui-kit older + than 21.33 can't install the package until it upgrades the kit — even + without the Inspector. That is a breaking change for such apps. +- **`cps-slider` is staged here for cps-ui-kit.** The kit has no slider, so + the Transparency control is a `cps-slider` written to the kit's + conventions — inputs, `valueChanged`, forms support, colour variables — + in its own folder, `event-inspector/src/lib/cps-slider/`, depending only + on Angular and the kit's public API, so it can move into cps-ui-kit as + is. +- **The separate window is drawn by the app.** The inspector renders into + the window's document from the app's own Angular application, with that + document as its `DOCUMENT` and its own cps-ui-kit notification and + announcer services, so tooltips, menus and messages appear in the window. + The app's stylesheets are copied in and kept in step, since Angular adds + a component's styles when it first renders. Its icon is the inspector's + own mark, an SVG taken from the logo it drew, as a library can't count on + serving a file. The window stays at `about:blank`, as the app draws it. + Reloading it gives it a new, empty document, which the app draws into again + (its page hiding counts as a close only if the window is gone a moment + later). One session holds the captured history from opening to closing, so + moving between docks — each a fresh view — loses nothing. A reload starts + over, as it does on any page: it is closing the inspector and opening it + again, in the same window. The captured history, filters and pause are + gone; the window's size and place, and the preferences, stay. - **Test doubles live in the specs that use them**, declared inline, never in a shared file and never exported. The one browser gap needing a stub is `BroadcastChannel`, which jsdom doesn't implement. @@ -602,7 +659,7 @@ const outcomeFor: Record = { }; jobUpdates.subscribe((update) => - this.scenarios.get(update.id)?.settle(outcomeFor[update.state]) + this.scenarios.find(update.id)?.settle(outcomeFor[update.state]) ); ``` @@ -864,6 +921,12 @@ The custom layers duplicate nothing built in: the library records no page views, does not re-capture unhandled errors, and collects no browser or device attributes. +The sink never edits the app's RUM config. `recordError` and +`mirrorErrorsToRum` go through RUM's error plugin, which RUM rejects when +`'errors'` isn't in `telemetries` (or its plugin in `eventPluginsToLoad`). The +sink then prints one console warning, in every environment, and mirrors no +errors; the app adds `'errors'` or sets `mirrorErrorsToRum: false`. + ### From events to metrics - **Custom RUM events don't become CloudWatch metrics on their own.** They @@ -1081,8 +1144,11 @@ for the session, `cwr_u` for the user id), the library keeps one `localStorage` entry, `ngx-ui-watchtower.sessionStarts`: up to five RUM session ids, each with the time it started, for `sessionElapsed`. It is written only while the RUM client's cookies are on, and an id stays until -five newer sessions have replaced it. Debug flags are read from -`localStorage`, never written. +five newer sessions have replaced it. The Event Inspector adds one more, +`ngx-ui-watchtower.eventInspector`, once someone moves, resizes or fades it: +its dock, the dialog's transparency, each pane's size, and the separate +window's size and position. Debug flags are read from `localStorage`, never +written. ### Scanning values for personal data @@ -1282,6 +1348,28 @@ Reading through AWS also answers the wrong question: by the time a record is in CloudWatch it is minutes old and mixed in with everyone else's, which is rarely what someone asking for "the logs" actually wants. +### The Event Inspector + +The popup shows exactly what is sent, after redaction — including session +and user ids — and redacts nothing further, since a popup that hid fields +would misreport what leaves the browser. That is acceptable in every +environment because it exposes nothing new: the data is the current user's +own, already in their browser and visible in the network panel. It stores +nothing, sends nothing and grants no remote access. + +What it shows, and what it doesn't, is said by an info icon by the title in +every dock, not text on the page: a tooltip, which `cps-info-circle` places +only where it fits, so it is given a width of its own. A dialog's header is +cps-dialog's, whose own `headerInfoTooltip` can't take one, so the inspector +moves its icon into the header's info slot, as it does its mark. + +Avoid sharing your screen while it is open, and treat downloads as you would +any file with ids in it; filenames carry only the application name, the +kind exported and a timestamp +(`ngx-ui-watchtower-events---.json`, +the kind being `all`, `bi`, `scenario` or `logging`). The shortcut prevents accidental opening; it is not access +control — use `enabled` for that, or leave the provider out. + --- ## 10. Configuration @@ -1590,7 +1678,11 @@ convention, for an application that wants the pre-fallback behavior back. becoming stable. - **Bounded.** A 100-item buffer keeps events, page views and errors recorded before the RUM client is ready, then replays them; it never grows past - that if initialization never completes. + that if initialization never completes. The telemetry monitor costs one + check per event while nobody watches; the popup keeps at most 500 events + of each kind, updates the screen at most every 250 ms, and releases + everything when it closes. Only `captureBeforeOpen` watches from startup, + keeping at most as many events as the popup would. The console report is itself wrapped in a try/catch — an application that has patched or otherwise broken `console.error` cannot turn a suppressed @@ -1625,6 +1717,8 @@ How an application uses the library, end to end. npm install @absaoss-cps/ngx-ui-watchtower # optional, only if you use the AWS RUM sink npm install aws-rum-web +# optional, only if you use the Event Inspector +npm install cps-ui-kit ``` ### Setup @@ -1825,7 +1919,9 @@ export class MyLogBackend implements UwtLogApiProvider { body: JSON.stringify(record), // Survives page unload. Only while the page is hidden (it may be // closing): browsers reject keepalive requests once all in flight - // exceed 64 KiB, so a burst of records would be lost. + // exceed 64 KiB, so a burst of records would be lost. Safari still says + // `visible` while a reloading page hides, so a record written then can + // be lost there; batching, below, sends those from flush(). keepalive: document.visibilityState === 'hidden' }).catch(() => undefined); } @@ -1888,7 +1984,11 @@ export class MyLogBackend implements UwtLogApiProvider { The one moment you cannot catch from inside a provider is the tab closing. Implement the optional `flush()`, and the library calls it on `pagehide`, on `visibilitychange` going hidden (mobile browsers routinely kill a -backgrounded tab without `pagehide`) and on teardown. +backgrounded tab without `pagehide`) and on teardown. Send from it with a +beacon or `keepalive` every time, as above, rather than only when +`document.visibilityState` is `'hidden'`: Safari still says `visible` while a +page that is reloading or navigating away hides, and cancels a plain request +then. #### Sending loggers to different destinations @@ -1979,6 +2079,316 @@ writes to the console, so starting a scenario, opening a step or calling a method after settlement — none of which sends anything — print nothing. To see each step, turn on `emitLifecycleEvents`, which makes steps real events. +### Event Inspector + +An in-app window showing, live, every BI event, scenario event and log +record your app hands to its telemetry destinations — no DevTools, no +backend access: + +```ts +import { provideUwtEventInspector } from '@absaoss-cps/ngx-ui-watchtower/event-inspector'; + +providers: [ + provideUwtTelemetry({ application: 'my-app', environment, version }), + provideUwtEventInspector() +]; +``` + +It is built from cps-ui-kit — 21.33 or later on Angular 21, or 22 — so the +app needs cps-ui-kit set up as for any cps-ui-kit dialog (styles, icons, +animations). It supports the light theme +only, for now: cps-dialog, which frames it, is light-only, and the kit's +dark theme is not yet complete for the components inside it. + +#### Opening it + +**Shift + E + I** on every platform — hold Shift, press E, then I — for +Event Inspector. No browser binds it. Keys are matched by physical position +(`KeyboardEvent.code`): the keys where E and I sit on a QWERTY keyboard — +the same on AZERTY and QWERTZ, elsewhere on Dvorak or Colemak. Shift must be +the only modifier, and held-down repeats are ignored. + +It types text, so it is ignored in a text field — typing a capital "EI" +there never opens it, even in a shadow root; press it with focus outside the +field. Screen readers +in browse mode keep letter keys for their own navigation; there, open it +from a button that calls `open()`. + +It opens where it was last — a dialog the first time — and never blocks the +app, so you can watch events arrive. Focus goes to the inspector itself, +not its search field, which is a Tab away, so the shortcut closes it next. +With it open, the shortcut brings focus back to it from the app, and closes +it when focus is already inside, outside a text field. + +#### Where it opens + +The **Dock** buttons at its top right move it, like Chrome DevTools' dock +side, and the choice is remembered in this browser: + +| Dock | What you get | +| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Dialog (default) | A non-modal dialog on the right, half the screen wide, between 40rem (or nearly all of a small screen) and 64rem. Draggable, resizable and maximizable, with a Transparency slider. Escape inside it, its header included, closes it and marks the key handled, so cps-ui-kit's notifications stay open; elsewhere, Escape stays the app's. | +| Separate window | Its own browser window, which it fills: 1200×800 the first time, then wherever it was last. Closing the window closes the inspector, and so does reloading the app; reloading the window draws it again, with what it had captured. | +| Left, right, bottom | A pane beside or below the app, which moves aside to make room. Drag its inner edge, or focus it and use the arrow keys, to resize it between 30rem (12rem at the bottom) and 80% of the screen; each side remembers its size. | + +Moving it keeps everything captured, the filters and the pause; pages and +expanded rows start afresh. + +A screen narrower than 640px has no room for a pane: the Dock buttons offer +only the dialog and the window, and a pane remembered from a wider screen +opens as a dialog there. + +Where the inspector is narrower than 34rem — a pane dragged towards its 30rem +minimum, or a dialog on a phone — the five Dock buttons would take a row of +their own, so one button shows the current dock and opens a menu of them all, +each named. A pane opens at 36rem and keeps the buttons. The separate window +always keeps them: a menu there would open against the app's window. + +A list opened from the inspector — the filter's field and condition, Export — +closes when the page scrolls. The inspector is fixed, so the page scrolls under +it, while cps-ui-kit puts an open list in the page, where it would scroll away +from its field. The kit closes a list when a scrolling parent of its field +scrolls, and every dock has one — the body, or the dialog's content — so the +inspector passes the page's scroll on to it, and the kit does the rest. Only +elements of the inspector hear it, never the app's. + +Spacing follows one scale, set as custom properties on the inspector host: +`--uwt-inspector-space-s` (8px) inside a cluster of controls, +`--uwt-inspector-space-m` (12px) between controls and between their rows, +`--uwt-inspector-space-l` (24px) between groups, and `--uwt-inspector-pad` +(20px) from the edge of a pane. What belongs together sits closer than what +doesn't. They are internal, not part of the API. Buttons beside an input are +as tall as it, so a row reads as one line. + +Wheeling over the inspector doesn't scroll the app under it. The body and the +dialog's content use `overscroll-behavior: contain`; and because a part with +nothing to scroll, like the header, still passes the wheel on in some browsers +(WebKit), the inspector also keeps a wheel from the page unless a part under +the pointer can scroll that way. Pinch zoom is left alone. + +A pane makes room by padding the page's `body` on its side, so content in +the normal flow moves aside. A page can't shrink the browser's viewport — +only the browser can, as DevTools does — so whatever is laid out against the +window stays where it is: `position: fixed` headers and buttons, `100vw` +widths and `100vh` heights reach under the pane, and media queries still see +the whole window. + +For those, the pane publishes its size on `` while it is open, in +pixels: `--uwt-inspector-inset-left`, `--uwt-inspector-inset-right` or +`--uwt-inspector-inset-bottom`, and leaves the others unset. An app opts in +where it needs to; the `0px` fallback keeps everything as it was with no pane +open: + +```css +.app-header { + left: var(--uwt-inspector-inset-left, 0px); + right: var(--uwt-inspector-inset-right, 0px); +} +.help-button { + right: calc(1.5rem + var(--uwt-inspector-inset-right, 0px)); + bottom: calc(1.5rem + var(--uwt-inspector-inset-bottom, 0px)); +} +.app-shell { + height: calc(100vh - var(--uwt-inspector-inset-bottom, 0px)); +} +``` + +The separate window has to open from a click or key press — the shortcut and +the Dock buttons both are — or the browser blocks it. The inspector then +opens as a dialog, and the console says why. + +It works in every environment. To change the keys, turn the shortcut off, or +limit who can open it: + +```ts +provideUwtEventInspector({ + // Your own combination — replaces the defaults. + shortcuts: [ + { + code: 'KeyD', + ctrl: true, + alt: true, + shift: true + } + ], + + // Or no keyboard at all, opening it from your own UI instead: + // shortcuts: [], then inject(UwtEventInspectorService).open(); + + // Read once at startup. + enabled: () => inject(AuthService).isSupportStaff() +}); +``` + +#### What it shows + +One timeline of everything sent from the moment the popup opens — or from +the app's start, with `captureBeforeOpen` — newest first: BI events, scenarios and their steps, and log records with the errors +they mirror to RUM. Each row shows the time, the kind, a name and the +details — a BI event's first metadata, a scenario's status, duration and +steps, a log's level and message — with a status or level coloured as well +as written. With room to spare, it adds the event type and the scenario or +correlation id. A new row is tinted for a moment, and screen readers hear +that it is new. The table always fits the popup: a long value ends in an +ellipsis, and hovering shows it in full. Expand a row for the full payload +as JSON; its **Copy event** puts that one payload on the clipboard. + +An open row offers to **Filter by** its key values: a BI event's name and +feature, a scenario's or step's name and status, a log's logger and level. +An error mirrored to RUM links to the log event it came from, and the log +event back to it. Following a link turns to that event's page, opens it and +moves focus to its payload, clearing the search and filters first if they +hide it. + +It shows 25 rows a page, with a pager once there is more than one. On a +later page, a notice counts the new events at the top, with **Go to +newest**. + +The tools above the table: + +- **All, BI, Scenarios, Logs** shows every kind or one, each with how many + of its events match the search and filters. +- **Export** saves the kind shown as JSON: **Download JSON** as a file, or + **Copy JSON** to the clipboard, for when a browser blocks downloads. It + holds every event captured of that kind, oldest first, whatever the search + and filters, which are recorded in the file. Each event keeps its + `sequence` and `capturedAt`, so exports of different kinds can be + interleaved again. +- **Search** matches any field's value or name, in every kind. **Add + filter** compares one field — `metadata.theme`, `status`, or + `steps[].name` for any item of a list — chosen by name, such as "Event + name", with its path below, which finds it too. The fields offered come + from the events of the kind shown. It closes once the filter is added, + and **How filters work**, beside it, explains them with examples. + +Two controls at the top apply to everything, because they are about +capturing rather than viewing: + +- **Pause live updates** freezes the view while you read; events are still + captured. The status line then says when you paused as well as when + capturing began: "Paused at 22:08:41 · capturing since 22:08:10". +- **Clear** empties the history; capturing continues. + +As a dialog, it also has **Transparency**, which lets the app show through, +so you can watch the page and its events together. Text fades with it, so it +goes up to 30%, and a higher value remembered from an older version is +brought down to that. A maximized dialog covers the app, so it is opaque and +hides the slider. The setting is remembered in this browser. Panes and the +window don't cover the app, so they have none. + +Each kind keeps its latest 500 events (`maxEventsPerSection`), so a burst +of one never pushes out another; the status line counts the oldest dropped. +History is discarded when the popup closes. + +To see what happened before it opened, set `captureBeforeOpen`: from the +app's start, it keeps up to that many events of each kind, which the popup +shows on opening, with "capturing since" the start. They keep coming while +the popup is closed; **Clear** empties them too. The cost is a copy of every +event handed over, from startup, popup open or not — why it is off by +default. They last as long as the page: after a sign-out without a reload, +they still hold the previous user's events, so where people share a +signed-in browser, reload on sign-out or leave it off. + +Under forced colours, such as Windows' contrast themes, the current dock, +the kind shown, the Pause switch and solid buttons keep the marks that show +their state, in the system's colours. + +What it does **not** show: + +- Anything before the popup opened, unless `captureBeforeOpen` kept it. +- RUM's own automatic events — page views, web vitals, HTTP and JS errors — + which never pass through this library. +- Whether the server accepted an event. It shows what was handed to the sink + or log provider; RUM can still drop events, for example when it samples a + session out or reaches its event limit. + +In a composed page, the shell's popup also shows what its fragments send — BI +and scenario events and log records alike, since the shell delivers them — +each marked with `↪` and the fragment's name. + +#### Configuration + +| Option | Default | Meaning | +| ----------------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| `enabled` | `true` | `false`, or a function read once at startup, turns the popup off — shortcut and `open()` alike | +| `shortcuts` | `UWT_DEFAULT_INSPECTOR_SHORTCUTS` | Replaces the defaults; `[]` disables the keyboard | +| `maxEventsPerSection` | `500` | Events kept of each kind — BI, scenarios, logs; the oldest are dropped, and the status line counts them | +| `maxPayloadCharsInView` | `262144` (256 × 1024) | A payload whose JSON is longer is shown truncated to its first `maxPayloadCharsInView` characters, at most 65,536; downloads stay complete | +| `captureBeforeOpen` | `0` | Events of each kind kept from the app's start, for the popup to show on opening; at most `maxEventsPerSection`, and copied from startup | + +An option left `undefined` keeps its default, as does a count that isn't a +usable number — negative, `NaN`, or `0` for `maxEventsPerSection`; a fraction +is rounded down. A config read from the environment can't stop the app. + +Characters here are UTF-16 code units (JavaScript's `length`), not bytes: the +limit protects rendering, which costs by the length of the text. A cut never +splits a surrogate pair. + +A shortcut is a `UwtInspectorShortcut` — `{ code, after?, ctrl?, alt?, +shift?, meta? }`, where `code` is a `KeyboardEvent.code` such as +`'Digit8'` or `'KeyD'`, and an omitted modifier must be up. `after` is a key +pressed just before `code` with the modifiers still held: the default is +`{ code: 'KeyI', after: 'KeyE', shift: true }`. Without `ctrl` or `meta`, a +key that types a character doesn't count in a text field; one that types +nothing, such as `F9`, still does. AltGr, which many +European Windows layouts report as Ctrl+Alt, never matches. Spread +`UWT_DEFAULT_INSPECTOR_SHORTCUTS` to add one rather than replace them. + +`UwtEventInspectorService` opens it from code: `open()`, `close()`, +`toggle()`, `setDock(dock)` — a `UwtInspectorDock`: `'dialog'`, `'window'`, +`'left'`, `'right'` or `'bottom'` — an `isOpen` signal, and a `dock` signal +for where it is shown, `undefined` while closed. `setDock` opens it there if +it is closed, and remembers the choice like the Dock buttons; asked for the +dock it is already in, it just focuses it. `dock` can differ from the dock +asked for: a blocked window, or a pane on a narrow screen, shows as a +dialog. + +An export is a `UwtInspectorExport`: + +```ts +interface UwtInspectorExport { + format: 'ngx-ui-watchtower-event-inspector'; + formatVersion: 2; + section: 'all' | 'bi' | 'scenario' | 'logging'; // the kind shown + exportedAt: string; + capture: { startedAt: string; endedAt: string; paused: boolean }; + app: { + application: string; + environment: string; + version: string; + eventNamespace: string; + }; + session: { sessionId?: string; userId?: string }; + userAgent: string; + activeFilters: UwtInspectorFilterState; // recorded, not applied + count: { captured: number; droppedOldest: number }; + events: UwtTelemetryObservedEvent[]; // oldest first +} +``` + +Version 1, from before the timeline, exported one of three sections, so its +`section` was never `'all'`. + +Field filters use the operators `contains` (the default), `equals`, +`not-contains`, `exists` and `missing`, all case-insensitive; several filters +combine with AND. A path such as `steps[].name` matches any item of a list. + +#### Reading events from code + +The popup is built on `UwtTelemetryMonitor`, from the main entry point, which +anyone can observe — a test, or your own overlay: + +```ts +inject(UwtTelemetryMonitor) + .events$.pipe(filter((e) => e.kind === 'scenario')) + .subscribe((e) => console.table(e.payload)); +``` + +Each event is a deep copy of what was sent, so observers can't change what +the app sent. Every observer gets the same copy, so treat it as read-only: +the Event Inspector shows, and exports, what another observer changes. +While nothing subscribes, it costs one check per event. + ### Testing The library ships **no test helpers**. Everything a test needs is already in @@ -2165,6 +2575,13 @@ bootstrapApplication(FragmentRoot, { | Channel name | **Must match.** The default (`'ngx-ui-watchtower'`) needs no argument; if the shell passes a custom name, pass the same — `provideUwtTelemetrySink('broadcast', { channelName })` | | `application` | **Should differ** per fragment. The forwarding sink stamps its realm's `application`, `environment` and `appVersion` onto every event, so you can tell fragments apart | +**An Angular fragment served as a Web Fragment** must load its files by +absolute URL. Its scripts run in the hidden iframe, whose location is the +host page's, so a relative `main.js` in its `index.html` resolves against the +shell's address — and loads the shell's own bundle. Build it with `deployUrl` +(and `baseHref`) set to the prefix the gateway routes to it, such as +`/_fragment/cart/`. The Watchtower Lab's fragment is built this way. + A forwarded **error** has no metadata to carry that origin in — `recordError` takes none — so the RUM sink folds a differing origin into the error's name instead (`[cart] TypeError`). @@ -2175,7 +2592,7 @@ error's name instead (`[cart] TypeError`). | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `provideUwtTelemetryRumSink()` | Builds a second AWS client — one visitor becomes two sessions. Listed next to `'broadcast'`, it is a second destination, so bootstrap fails | | `UWT_RUM_CREDENTIALS_PROVIDER` | Nothing in a fragment needs AWS credentials | -| `UWT_LOG_API_PROVIDER` | `'broadcast'` mode already binds it to the forwarding provider. One bound after it wins, and the fragment ships its own logs — the shell never sees them | +| `UWT_LOG_API_PROVIDER` | `'broadcast'` mode already binds it to the forwarding provider. One bound after it wins, and the fragment ships its own logs — the shell, and its Event Inspector, never see them | | `provideUwtTelemetryBroadcastHost()` | A host receives its own realm's messages too, so beside the forwarding sink it would forward them again, forever. It detects this, warns (`... cannot also host it ...`) and stays inactive | #### A realm that both forwards and hosts @@ -2255,7 +2672,16 @@ never injects `UwtLoggerService` sends none in the first place. With `mirrorErrorsToRum`, each error-level log line leaves a fragment as two messages — the record for the log provider, then its mirrored error for the sink — with no id in common. Each destination still receives it once: the -log backend gets the record, RUM gets the error. +log backend gets the record, RUM gets the error. The host links them for its +monitor anyway, so the shell's Event Inspector shows the mirror against +its record as the fragment's own does: an `error` arriving straight after an +error-level record from the same realm, with the name and message the logger +mirrors for it, gets that record's sequence as `relatedSequence`. Both are +posted in the same task, so nothing of that fragment's can arrive between +them. A mirror that doesn't match exactly is shown unlinked. On the shared +channel with two tabs open, though, the leading tab's host hears both tabs' +fragments, whose messages can interleave: a mirror can then link to the +other tab's record with the same name and message. ### A fragment that also deploys standalone diff --git a/projects/ngx-ui-watchtower/README.md b/projects/ngx-ui-watchtower/README.md index 9d8c910..fab609f 100644 --- a/projects/ngx-ui-watchtower/README.md +++ b/projects/ngx-ui-watchtower/README.md @@ -28,6 +28,11 @@ npm install aws-rum-web # only for the AWS RUM sink Requires Angular 21 or 22 and RxJS 7.8. +**Upgrading with an older cps-ui-kit:** the Event Inspector needs cps-ui-kit +21.33 or later (or 22). npm checks that range whenever cps-ui-kit is +installed, so an app on an older version can't install this release until it +upgrades cps-ui-kit, even if it never uses the Inspector. + ## Setup ```ts @@ -129,7 +134,11 @@ export class MyLogBackend implements UwtLogApiProvider { Batching, retries and authentication are up to your backend class. If it batches, add a `flush()` method — the library calls it when the page is -hidden or closed. +hidden or closed. Send from it with `navigator.sendBeacon` or a `keepalive` +request every time, not only when the page says it is hidden: Safari still +says `visible` while a page that is reloading or navigating away hides, and +cancels a plain request then. For the same reason, the example above can lose +a record written as the page goes, in Safari. ### Using a different destination @@ -350,7 +359,7 @@ Fragment code doesn't change. Give each fragment its own `application` name. ## Seeing what is sent -Run in DevTools, no reload needed: +**In the console** — run in DevTools, no reload needed: ```js localStorage.setItem('debugScenario', 'true'); @@ -358,6 +367,40 @@ localStorage.setItem('debugBI', 'true'); localStorage.setItem('debugLogger', 'true'); // or 'checkout,cart' for some loggers ``` +**In the app** — add the Event Inspector (needs `cps-ui-kit` 21.33+ or 22): + +```ts +import { provideUwtEventInspector } from '@absaoss-cps/ngx-ui-watchtower/event-inspector'; + +providers: [provideUwtEventInspector()]; +``` + +Open it with **Shift+E+I** (hold Shift, press E, then I), outside a text field. +It lists everything the app sends from that moment on in one timeline, newest +first, showing every kind or just BI, scenarios or logs, with search, filters +and JSON export. It opens as a dialog, with a Transparency slider; its Dock +buttons move it to its own window or a pane on the left, right or bottom (on a +screen at least 640px wide). + +To also see what happened before you opened it, keep the latest events from +the app's start — at the cost of copying every event handed over, popup open +or not: + +```ts +provideUwtEventInspector({ captureBeforeOpen: 100 }); // per kind +``` + +A pane pushes the page aside, but elements fixed to the window stay put. To +keep them beside the pane, use the size it publishes on `` +(`--uwt-inspector-inset-left`, `-right` or `-bottom`): + +```css +.app-header { + left: var(--uwt-inspector-inset-left, 0px); + right: var(--uwt-inspector-inset-right, 0px); +} +``` + ## Testing ```ts @@ -381,6 +424,10 @@ TestBed.configureTestingModule({ - It is safe under server-side rendering. - A scenario sends one event when it ends, not one per step — AWS RUM allows 200 events per session by default. +- RUM's own telemetry defaults to errors, performance and same-origin HTTP; + set `telemetries` in the RUM config to change it. `mirrorErrorsToRum` and + `recordError` need `'errors'` in that list; without it, errors aren't + mirrored to RUM and the console warns once. ## License diff --git a/projects/ngx-ui-watchtower/event-inspector/ng-package.json b/projects/ngx-ui-watchtower/event-inspector/ng-package.json new file mode 100644 index 0000000..e09fb3f --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../node_modules/ng-packagr/ng-entrypoint.schema.json", + "lib": { + "entryFile": "src/public-api.ts" + } +} diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/cps-slider/cps-slider.component.html b/projects/ngx-ui-watchtower/event-inspector/src/lib/cps-slider/cps-slider.component.html new file mode 100644 index 0000000..8ce44a1 --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/cps-slider/cps-slider.component.html @@ -0,0 +1,46 @@ +
+ @if (label) { + + } + @if (infoTooltip) { + + + } + + @if (showValue) { + + } +
diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/cps-slider/cps-slider.component.scss b/projects/ngx-ui-watchtower/event-inspector/src/lib/cps-slider/cps-slider.component.scss new file mode 100644 index 0000000..81c9016 --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/cps-slider/cps-slider.component.scss @@ -0,0 +1,124 @@ +@mixin thumb($color) { + box-sizing: border-box; + width: 1rem; + height: 1rem; + border: 0.125rem solid #fff; + border-radius: 50%; + background-color: $color; + box-shadow: 0 0 0 0.0625rem $color; + cursor: pointer; +} + +@mixin track($fill, $rest) { + // The track is the input's own background: a thin band across the middle, + // filled up to the value. + background: linear-gradient( + to right, + $fill var(--cps-slider-fill, 0%), + $rest var(--cps-slider-fill, 0%) + ) + center / 100% 0.25rem no-repeat; +} + +:host { + min-width: max-content; + display: inline-block; + + .cps-slider-container { + line-height: normal; + display: flex; + align-items: center; + gap: 0.625rem; + } + + .cps-slider-label, + .cps-slider-value { + font-size: 1rem; + font-family: 'Source Sans Pro', sans-serif; + font-style: normal; + color: var(--cps-color-text-dark); + } + + .cps-slider-label { + user-select: none; + } + + .cps-slider-value { + min-width: 2.5rem; + font-variant-numeric: tabular-nums; + } + + .cps-slider-info-circle ::ng-deep cps-icon i { + width: 0.875rem; + height: 0.875rem; + } + + .cps-slider-input { + appearance: none; + height: 1.5rem; + margin: 0; + cursor: pointer; + @include track(var(--cps-color-calm), var(--cps-color-line-mid)); + + &::-webkit-slider-thumb { + appearance: none; + @include thumb(var(--cps-color-calm)); + } + + &::-moz-range-thumb { + @include thumb(var(--cps-color-calm)); + } + + &::-moz-range-track { + background: transparent; + } + + &:focus-visible { + outline: none; + + &::-webkit-slider-thumb { + box-shadow: + 0 0 0 0.125rem var(--cps-color-calm), + 0 0 0 0.1875rem var(--cps-color-calm-highlighten); + } + + &::-moz-range-thumb { + box-shadow: + 0 0 0 0.125rem var(--cps-color-calm), + 0 0 0 0.1875rem var(--cps-color-calm-highlighten); + } + } + + // Forced colours drop the track's gradient and the focus shadow. + @media (forced-colors: active) { + border: 0.0625rem solid CanvasText; + + &:focus-visible { + outline: 0.125rem solid Highlight; + outline-offset: 0.125rem; + } + } + } + + .cps-slider-disabled { + .cps-slider-label, + .cps-slider-value { + color: var(--cps-color-text-mild); + } + + .cps-slider-input { + cursor: default; + @include track(var(--cps-color-text-light), var(--cps-color-line-light)); + + &::-webkit-slider-thumb { + @include thumb(var(--cps-color-text-light)); + cursor: default; + } + + &::-moz-range-thumb { + @include thumb(var(--cps-color-text-light)); + cursor: default; + } + } + } +} diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/cps-slider/cps-slider.component.spec.ts b/projects/ngx-ui-watchtower/event-inspector/src/lib/cps-slider/cps-slider.component.spec.ts new file mode 100644 index 0000000..ac7b76d --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/cps-slider/cps-slider.component.spec.ts @@ -0,0 +1,112 @@ +import { Component } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { CpsSliderComponent } from './cps-slider.component'; + +describe('CpsSliderComponent', () => { + let fixture: ComponentFixture; + const el = () => fixture.nativeElement as HTMLElement; + const input = () => + el().querySelector('[data-testid="cps-slider-input"]') as HTMLInputElement; + + /** What a user's drag or key press does to the native input. */ + function slideTo(value: number): void { + input().value = String(value); + input().dispatchEvent(new Event('input')); + fixture.detectChanges(); + } + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [CpsSliderComponent] + }).compileComponents(); + fixture = TestBed.createComponent(CpsSliderComponent); + }); + + it('should label the range input and show its value with the suffix', () => { + fixture.componentRef.setInput('label', 'Transparency'); + fixture.componentRef.setInput('max', 70); + fixture.componentRef.setInput('value', 30); + fixture.componentRef.setInput('showValue', true); + fixture.componentRef.setInput('valueSuffix', '%'); + fixture.detectChanges(); + + const label = el().querySelector('label') as HTMLLabelElement; + expect(label.textContent).toBe('Transparency'); + expect(label.htmlFor).toBe(input().id); + expect(input().type).toBe('range'); + expect(input().max).toBe('70'); + expect(input().value).toBe('30'); + expect(input().getAttribute('aria-valuetext')).toBe('30%'); + expect( + el().querySelector('[data-testid="cps-slider-value"]')?.textContent + ).toBe('30%'); + }); + + it('should emit the new value as a number and move the fill', () => { + const emitted: number[] = []; + fixture.componentInstance.valueChanged.subscribe((v) => emitted.push(v)); + fixture.componentRef.setInput('min', 0); + fixture.componentRef.setInput('max', 80); + fixture.componentRef.setInput('step', 5); + fixture.detectChanges(); + + slideTo(20); + + expect(emitted).toEqual([20]); + expect(input().style.getPropertyValue('--cps-slider-fill')).toBe('25%'); + }); + + it('should use ariaLabel over label for the accessible name', () => { + fixture.componentRef.setInput('label', 'Transparency'); + fixture.componentRef.setInput('ariaLabel', 'Window transparency'); + fixture.detectChanges(); + + expect(input().getAttribute('aria-label')).toBe('Window transparency'); + }); + + it('should not move when disabled', () => { + fixture.componentRef.setInput('disabled', true); + fixture.detectChanges(); + + expect(input().disabled).toBe(true); + expect(el().querySelector('.cps-slider-container')?.classList).toContain( + 'cps-slider-disabled' + ); + }); + + describe('in a reactive form', () => { + @Component({ + imports: [CpsSliderComponent, ReactiveFormsModule], + template: `` + }) + class HostComponent { + readonly control = new FormControl(40); + } + + let host: ComponentFixture; + const hostInput = () => + host.nativeElement.querySelector('input') as HTMLInputElement; + + beforeEach(() => { + host = TestBed.createComponent(HostComponent); + host.detectChanges(); + }); + + it('should show the control value and write user changes back', () => { + expect(hostInput().value).toBe('40'); + + hostInput().value = '60'; + hostInput().dispatchEvent(new Event('input')); + + expect(host.componentInstance.control.value).toBe(60); + }); + + it('should follow the control being disabled', () => { + host.componentInstance.control.disable(); + host.detectChanges(); + + expect(hostInput().disabled).toBe(true); + }); + }); +}); diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/cps-slider/cps-slider.component.ts b/projects/ngx-ui-watchtower/event-inspector/src/lib/cps-slider/cps-slider.component.ts new file mode 100644 index 0000000..b8baff7 --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/cps-slider/cps-slider.component.ts @@ -0,0 +1,177 @@ +import { Component, EventEmitter, inject, Input, Output } from '@angular/core'; +import { ControlValueAccessor, NgControl } from '@angular/forms'; +import { CpsInfoCircleComponent, CpsTooltipPosition } from 'cps-ui-kit'; + +let nextId = 0; + +/** + * CpsSliderComponent picks a number from a range by dragging a thumb along a + * track, or with the arrow, Page Up/Down, Home and End keys. + * + * Written to cps-ui-kit's conventions, which has no slider yet, and kept in + * its own folder so it can move into the kit unchanged. Depends only on + * Angular and cps-ui-kit's public API. + * @group Components + */ +@Component({ + selector: 'cps-slider', + imports: [CpsInfoCircleComponent], + templateUrl: './cps-slider.component.html', + styleUrl: './cps-slider.component.scss' +}) +export class CpsSliderComponent implements ControlValueAccessor { + /** + * Label of the slider component. + * @group Props + */ + @Input() label = ''; + + /** + * Aria label for the slider component, used for accessibility, it takes precedence over label. + * @group Props + */ + @Input() ariaLabel = ''; + + /** + * Lowest value of the slider. + * @group Props + */ + @Input() min = 0; + + /** + * Highest value of the slider. + * @group Props + */ + @Input() max = 100; + + /** + * Difference between two neighbouring values. + * @group Props + */ + @Input() step = 1; + + /** + * Width of the track, of type number denoting pixels or string. + * @group Props + */ + @Input() width: number | string = '10rem'; + + /** + * Determines whether the current value is shown next to the track. + * @group Props + */ + @Input() showValue = false; + + /** + * Text appended to the shown and announced value, e.g. '%'. + * @group Props + */ + @Input() valueSuffix = ''; + + /** + * Determines whether the slider component is disabled. + * @group Props + */ + @Input() disabled = false; + + /** + * When it is not an empty string, an info icon is displayed to show text for more info. + * @group Props + */ + @Input() infoTooltip = ''; + + /** + * InfoTooltip class for styling. + * @group Props + */ + @Input() infoTooltipClass = 'cps-tooltip-content'; + + /** + * Size of infoTooltip, of type number denoting pixels or string. + * @group Props + */ + @Input() infoTooltipMaxWidth: number | string = '100%'; + + /** + * Determines whether the infoTooltip is persistent. + * @group Props + */ + @Input() infoTooltipPersistent = false; + + /** + * Position of infoTooltip, it can be 'top', 'bottom', 'left' or 'right'. + * @group Props + */ + @Input() infoTooltipPosition: CpsTooltipPosition = 'top'; + + /** + * Value of the slider component. + * @default 0 + * @group Props + */ + @Input() value = 0; + + /** + * Callback to invoke on value change. + * @param {number} number - value changed. + * @group Emits + */ + @Output() valueChanged = new EventEmitter(); + + readonly inputId = `cps-slider-${nextId++}`; + + private readonly _control = inject(NgControl, { + self: true, + optional: true + }); + + // eslint-disable-next-line @typescript-eslint/no-empty-function + onChange = (_value: number) => {}; + + // eslint-disable-next-line @typescript-eslint/no-empty-function + onTouched = () => {}; + + constructor() { + if (this._control) { + this._control.valueAccessor = this; + } + } + + get cvtWidth(): string { + return typeof this.width === 'number' ? `${this.width}px` : this.width; + } + + /** How far along the track the value is, for the filled part. */ + get fill(): string { + const span = this.max - this.min; + const ratio = span > 0 ? (this.value - this.min) / span : 0; + return `${Math.min(Math.max(ratio, 0), 1) * 100}%`; + } + + get valueText(): string { + return `${this.value}${this.valueSuffix}`; + } + + registerOnChange(fn: (value: number) => void): void { + this.onChange = fn; + } + + registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } + + writeValue(value: number | null): void { + this.value = typeof value === 'number' ? value : this.min; + } + + setDisabledState(disabled: boolean): void { + this.disabled = disabled; + } + + updateValueEvent(event: Event): void { + const value = Number((event.target as HTMLInputElement).value); + this.value = value; + this.onChange(value); + this.valueChanged.emit(value); + } +} diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-event-inspector.service/uwt-event-inspector.service.spec.ts b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-event-inspector.service/uwt-event-inspector.service.spec.ts new file mode 100644 index 0000000..980eb26 --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-event-inspector.service/uwt-event-inspector.service.spec.ts @@ -0,0 +1,597 @@ +import { ApplicationRef, PLATFORM_ID } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; +import { provideNoopAnimations } from '@angular/platform-browser/animations'; +import { Subject } from 'rxjs'; +import { + UWT_TELEMETRY_IDENTITY, + UwtTelemetryMonitor +} from '@absaoss-cps/ngx-ui-watchtower'; +import { CPS_LIVE_ANNOUNCER_SERVICE, CpsDialogService } from 'cps-ui-kit'; +import { + UWT_EVENT_INSPECTOR_CONFIG, + UwtEventInspectorConfig +} from '../uwt-inspector.models/uwt-inspector.models'; +import { UwtInspectorComponent } from '../uwt-inspector/uwt-inspector.component'; +import { UWT_INSPECTOR_PREFERENCES_KEY } from '../uwt-inspector-preferences/uwt-inspector-preferences'; +import { UwtInspectorSession } from '../uwt-inspector-session/uwt-inspector-session'; +import * as windowHost from '../uwt-inspector-window/uwt-inspector-window'; +import { UwtInspectorRing } from '../uwt-inspector-ring/uwt-inspector-ring'; +import { UwtEventInspectorService } from './uwt-event-inspector.service'; + +/** Stands in for CpsDialogService, recording what was opened. */ +class FakeDialogs { + readonly opened: { component: unknown; config: Record }[] = + []; + + readonly destroyed = new Subject(); + containsFocus = false; + readonly instance = { + containsFocus: () => this.containsFocus, + focusInspector: jest.fn() + }; + + readonly close = jest.fn(() => this.destroyed.next()); + readonly destroy = jest.fn(() => this.destroyed.next()); + /** Whether the dialog has rendered; one that hasn't can't animate shut. */ + shown = true; + + open(component: unknown, config: Record) { + this.opened.push({ component, config }); + return { + componentInstance: this.instance, + onDestroy: this.destroyed.asObservable(), + close: this.close, + destroy: this.destroy, + isOpen: () => this.shown + }; + } +} + +type KeyInit = ConstructorParameters[1]; + +/** Presses one key on `target`; with none given, the default Shift+E+I. */ +function press(init?: KeyInit, target: Element = document.body): KeyboardEvent { + if (!init) { + press({ code: 'KeyE', key: 'E', shiftKey: true }, target); + return press({ code: 'KeyI', key: 'I', shiftKey: true }, target); + } + const event = new KeyboardEvent('keydown', { + ...init, + bubbles: true, + cancelable: true, + composed: true + }); + target.dispatchEvent(event); + return event; +} + +describe('UwtEventInspectorService', () => { + let dialogs: FakeDialogs; + + function create( + config: Partial = {}, + platform = 'browser' + ): UwtEventInspectorService { + TestBed.resetTestingModule(); + dialogs = new FakeDialogs(); + TestBed.configureTestingModule({ + providers: [ + { provide: CpsDialogService, useValue: dialogs }, + { provide: PLATFORM_ID, useValue: platform }, + { provide: UWT_EVENT_INSPECTOR_CONFIG, useValue: config }, + { + provide: UWT_TELEMETRY_IDENTITY, + useValue: { application: 'shop', environment: 'test', version: '1' } + }, + { + provide: CPS_LIVE_ANNOUNCER_SERVICE, + useValue: { announce: jest.fn(), clear: jest.fn() } + }, + provideNoopAnimations() + ] + }); + return TestBed.inject(UwtEventInspectorService); + } + + afterEach(() => { + TestBed.resetTestingModule(); + jest.restoreAllMocks(); + localStorage.removeItem(UWT_INSPECTOR_PREFERENCES_KEY); + }); + + const pane = () => document.querySelector('uwt-inspector-pane'); + const remember = (preferences: object) => + localStorage.setItem( + UWT_INSPECTOR_PREFERENCES_KEY, + JSON.stringify(preferences) + ); + const stored = () => + JSON.parse(localStorage.getItem(UWT_INSPECTOR_PREFERENCES_KEY) ?? '{}'); + /** A stand-in window host, recording what the service asked of it. */ + function fakeWindow() { + const host = { + dock: 'window' as const, + focus: jest.fn(), + containsFocus: jest.fn(() => false), + destroy: jest.fn() + }; + const open = jest + .spyOn(windowHost, 'uwtOpenInspectorWindow') + .mockReturnValue(host); + return { host, open }; + } + + it('should open the popup, non-modal and named, on the default shortcut', () => { + create().install(); + + const event = press(); + + expect(dialogs.opened).toHaveLength(1); + expect(dialogs.opened[0].component).toBe(UwtInspectorComponent); + expect(dialogs.opened[0].config).toMatchObject({ + modal: false, + ariaLabel: 'Event Inspector', + closeOnEscape: false, + styleClass: 'uwt-inspector-dialog' + }); + expect(event.defaultPrevented).toBe(true); + expect(TestBed.inject(UwtEventInspectorService).isOpen()).toBe(true); + }); + + it('should ignore ordinary typing and near misses', () => { + create().install(); + + press({ code: 'KeyI', shiftKey: true }); + press({ code: 'KeyE' }); + press({ code: 'ShiftLeft', shiftKey: true }); + press({ code: 'KeyI', shiftKey: true }); + press({ code: 'KeyE', shiftKey: true }); + press({ code: 'KeyX', shiftKey: true }); + press({ code: 'KeyI', shiftKey: true }); + + expect(dialogs.opened).toHaveLength(0); + }); + + it('should ignore Shift+E+I typed into a text field', () => { + create().install(); + const field = document.body.appendChild(document.createElement('input')); + + press(undefined, field); + + expect(dialogs.opened).toHaveLength(0); + field.remove(); + }); + + it('should ignore Shift+E+I typed into a text field inside a shadow root', () => { + create().install(); + const host = document.body.appendChild(document.createElement('div')); + const field = host + .attachShadow({ mode: 'open' }) + .appendChild(document.createElement('input')); + + press(undefined, field); + + expect(dialogs.opened).toHaveLength(0); + host.remove(); + }); + + it('should bring focus back to an open popup, and close it from inside', () => { + create().install(); + press(); + dialogs.instance.focusInspector.mockClear(); + + dialogs.containsFocus = false; + press(); + expect(dialogs.instance.focusInspector).toHaveBeenCalledTimes(1); + expect(dialogs.close).not.toHaveBeenCalled(); + + dialogs.containsFocus = true; + press(); + expect(dialogs.close).toHaveBeenCalledTimes(1); + expect(TestBed.inject(UwtEventInspectorService).isOpen()).toBe(false); + }); + + it('should open fresh after the previous popup was destroyed', () => { + create().install(); + press(); + dialogs.destroyed.next(); + + press(); + expect(dialogs.opened).toHaveLength(2); + }); + + it('should use custom shortcuts instead of the defaults', () => { + create({ + shortcuts: [{ code: 'KeyD', ctrl: true, alt: true, shift: true }] + }).install(); + + press(); + expect(dialogs.opened).toHaveLength(0); + press({ code: 'KeyD', ctrlKey: true, altKey: true, shiftKey: true }); + expect(dialogs.opened).toHaveLength(1); + }); + + it('should not listen with no shortcuts, but still open on request', () => { + const service = create({ shortcuts: [] }); + service.install(); + + press(); + expect(dialogs.opened).toHaveLength(0); + + service.open(); + expect(dialogs.opened).toHaveLength(1); + }); + + it.each([ + ['disabled', { enabled: false }], + ['disabled by a function', { enabled: () => false }] + ])('should do nothing when %s', (_label, config) => { + const listen = jest.spyOn(document, 'addEventListener'); + const service = create(config); + service.install(); + + press(); + service.open(); + expect(dialogs.opened).toHaveLength(0); + expect(listen).not.toHaveBeenCalledWith('keydown', expect.anything(), true); + }); + + it('should treat a throwing enabled() as disabled', () => { + jest.spyOn(console, 'error').mockImplementation(() => undefined); + const service = create({ + enabled: () => { + throw new Error('auth not ready'); + } + }); + service.install(); + service.open(); + expect(dialogs.opened).toHaveLength(0); + }); + + it('should not listen during a server-side render', () => { + const listen = jest.spyOn(document, 'addEventListener'); + create({}, 'server').install(); + press(); + expect(dialogs.opened).toHaveLength(0); + expect(listen).not.toHaveBeenCalledWith('keydown', expect.anything(), true); + }); + + it('should stop listening when the application is destroyed', () => { + create().install(); + TestBed.resetTestingModule(); + + press(); + expect(dialogs.opened).toHaveLength(0); + }); + + it('should open where it was last', () => { + remember({ dock: 'right' }); + create().open(); + + expect(dialogs.opened).toHaveLength(0); + expect(pane()?.classList).toContain('uwt-inspector-pane--right'); + }); + + it('should open a dialog instead of a pane that fails, leaving none behind', () => { + remember({ dock: 'right' }); + const service = create(); + jest + .spyOn(TestBed.inject(ApplicationRef), 'attachView') + .mockImplementationOnce(() => { + throw new Error('no room'); + }); + jest.spyOn(console, 'error').mockImplementation(() => undefined); + + service.open(); + + expect(pane()).toBeNull(); + expect(dialogs.opened).toHaveLength(1); + expect(service.dock()).toBe('dialog'); + }); + + it('should take away a dialog closed before it is shown, which its close would leave', () => { + const service = create(); + dialogs.shown = false; + + service.open(); + service.close(); + + expect(dialogs.destroy).toHaveBeenCalledTimes(1); + expect(dialogs.close).not.toHaveBeenCalled(); + }); + + it('should give focus back to where it was as a pane closes', () => { + remember({ dock: 'right' }); + const button = document.body.appendChild(document.createElement('button')); + button.focus(); + const service = create(); + + service.open(); + expect(pane()?.contains(document.activeElement)).toBe(true); + service.close(); + + expect(document.activeElement).toBe(button); + button.remove(); + }); + + it('should take an open pane away quietly when the app is destroyed', () => { + remember({ dock: 'right' }); + create().open(); + const warn = jest.spyOn(console, 'warn'); + + TestBed.resetTestingModule(); + + expect(pane()).toBeNull(); + expect(warn).not.toHaveBeenCalled(); + }); + + it('should move between docks, keeping the session, and remember the dock', () => { + const service = create(); + service.open(); + const store = TestBed.inject(UwtInspectorSession).store; + + service.setDock('left'); + + expect(dialogs.close).toHaveBeenCalledTimes(1); + expect(pane()?.classList).toContain('uwt-inspector-pane--left'); + expect(service.isOpen()).toBe(true); + expect(TestBed.inject(UwtInspectorSession).store).toBe(store); + expect(stored().dock).toBe('left'); + + service.close(); + expect(pane()).toBeNull(); + expect(service.isOpen()).toBe(false); + expect(TestBed.inject(UwtTelemetryMonitor).observed).toBe(false); + }); + + it('should open a window titled after the app, where it was last', () => { + remember({ window: { width: 900, height: 700, left: 10, top: 20 } }); + const { host, open } = fakeWindow(); + const service = create(); + + service.setDock('window'); + + expect(open).toHaveBeenCalledWith( + expect.objectContaining({ + title: 'Event Inspector · shop', + name: 'ngx-ui-watchtower-event-inspector:shop', + geometry: { width: 900, height: 700, left: 10, top: 20 } + }) + ); + expect(host.focus).toHaveBeenCalled(); + expect(dialogs.opened).toHaveLength(0); + }); + + it('should end the session when the user closes the window', () => { + const { host, open } = fakeWindow(); + const service = create(); + service.setDock('window'); + const options = open.mock.calls[0][0]; + + options.onGeometry({ width: 800, height: 600, left: 1, top: 2 }); + options.onClosed(); + + expect(service.isOpen()).toBe(false); + expect(host.destroy).toHaveBeenCalled(); + expect(stored().window).toEqual({ + width: 800, + height: 600, + left: 1, + top: 2 + }); + }); + + it('should start afresh when the window is reloaded, as closing and opening it again', () => { + const { host, open } = fakeWindow(); + const service = create(); + service.setDock('window'); + const session = TestBed.inject(UwtInspectorSession); + const history = session.store; + + open.mock.calls[0][0].onReloaded(); + + // A new history, in the same window, still open. + expect(session.store).not.toBe(history); + expect(service.isOpen()).toBe(true); + expect(host.destroy).not.toHaveBeenCalled(); + }); + + it('should open a dialog, warning once, when the browser blocks the window', () => { + jest.spyOn(windowHost, 'uwtOpenInspectorWindow').mockReturnValue(undefined); + const warn = jest + .spyOn(console, 'warn') + .mockImplementation(() => undefined); + const service = create(); + + service.setDock('window'); + service.close(); + service.setDock('window'); + + expect(dialogs.opened).toHaveLength(2); + expect(warn).toHaveBeenCalledTimes(1); + expect(stored().dock).toBe('window'); + }); + + it('should end the session when the user closes the dialog, but not on a move', () => { + const service = create(); + service.open(); + service.setDock('bottom'); + expect(service.isOpen()).toBe(true); + + service.setDock('dialog'); + dialogs.destroyed.next(); + expect(service.isOpen()).toBe(false); + expect(pane()).toBeNull(); + }); + + it('should move focus into a pane it opens, so the shortcut closes it next', () => { + remember({ dock: 'right' }); + const service = create(); + service.install(); + + press(); + expect(pane()?.contains(document.activeElement)).toBe(true); + + // Pressed where focus is, not on the page: in a field it would type. + press(undefined, document.activeElement!); + expect(service.isOpen()).toBe(false); + }); + + it('should say where it is shown, which a blocked window makes a dialog', () => { + const service = create(); + expect(service.dock()).toBeUndefined(); + + service.setDock('left'); + expect(service.dock()).toBe('left'); + + jest.spyOn(windowHost, 'uwtOpenInspectorWindow').mockReturnValue(undefined); + jest.spyOn(console, 'warn').mockImplementation(() => undefined); + service.setDock('window'); + expect(service.dock()).toBe('dialog'); + + service.close(); + expect(service.dock()).toBeUndefined(); + }); + + it('should leave the inspector as it is when asked for the dock it is in', () => { + const service = create(); + service.setDock('right'); + const first = pane(); + + service.setDock('right'); + + expect(pane()).toBe(first); + expect(document.querySelectorAll('uwt-inspector-pane')).toHaveLength(1); + service.close(); + + const { host, open } = fakeWindow(); + service.setDock('window'); + service.setDock('window'); + expect(open).toHaveBeenCalledTimes(1); + expect(host.destroy).not.toHaveBeenCalled(); + expect(host.focus).toHaveBeenCalledTimes(2); + }); + + describe('captureBeforeOpen', () => { + const bi = (name: string) => ({ + kind: 'bi' as const, + eventType: 'com.uwt.bi', + payload: { eventName: name, eventTime: '', application: 'shop' }, + destination: 'sink' as const, + origin: { forwarded: false as const } + }); + const monitor = () => TestBed.inject(UwtTelemetryMonitor); + + it('should keep events from before opening, even with no shortcut', () => { + const service = create({ captureBeforeOpen: 5, shortcuts: [] }); + service.install(); + monitor().publish(bi('before')); + + service.open(); + expect( + TestBed.inject(UwtInspectorSession).store.entries().bi + ).toHaveLength(1); + }); + + it('should keep them coming after closing, for the next opening', () => { + const service = create({ captureBeforeOpen: 5 }); + service.install(); + service.open(); + service.close(); + expect(monitor().observed).toBe(true); + + monitor().publish(bi('while closed')); + service.open(); + expect( + TestBed.inject(UwtInspectorSession).store.entries().bi + ).toHaveLength(1); + }); + + it('should keep no more of each kind than the inspector shows', () => { + create({ captureBeforeOpen: 50, maxEventsPerSection: 2 }).install(); + ['a', 'b', 'c'].forEach((n) => monitor().publish(bi(n))); + + expect(TestBed.inject(UwtInspectorRing).snapshot().events).toHaveLength( + 2 + ); + }); + + it('should keep nothing by default, when disabled, or on the server', () => { + create().install(); + expect(monitor().observed).toBe(false); + + create({ captureBeforeOpen: 5, enabled: false }).install(); + expect(monitor().observed).toBe(false); + + create({ captureBeforeOpen: 5 }, 'server').install(); + expect(monitor().observed).toBe(false); + }); + }); + + describe('on a screen too narrow for a pane', () => { + let change: ((event: { matches: boolean }) => void) | undefined; + /** Whether the app's window is narrower than 640px right now. */ + let narrow = false; + + /** + * The app's window, as `matchMedia` reports it: `narrow` when asked. A + * spy, as `ng test` makes `matchMedia` writable but not configurable. + */ + function screenIsNarrow(matches: boolean): void { + narrow = matches; + jest.spyOn(window, 'matchMedia').mockImplementation( + (query: string) => + ({ + get matches() { + return narrow; + }, + media: query, + addEventListener: (_: string, listener: typeof change) => + (change = listener), + removeEventListener: jest.fn() + }) as unknown as MediaQueryList + ); + } + + afterEach(() => { + change = undefined; + }); + + it('should open a remembered pane as a dialog, keeping the pane for later', () => { + screenIsNarrow(true); + remember({ dock: 'right' }); + const service = create(); + + service.open(); + + expect(pane()).toBeNull(); + expect(dialogs.opened).toHaveLength(1); + expect(stored().dock).toBe('right'); + service.close(); + }); + + it('should go by the width when it opens, before a resize is reported', () => { + screenIsNarrow(false); + remember({ dock: 'right' }); + const service = create(); + + narrow = true; // no change event yet + service.open(); + + expect(pane()).toBeNull(); + expect(dialogs.opened).toHaveLength(1); + service.close(); + }); + + it('should tell the inspector whether a pane fits, as the window is resized', () => { + screenIsNarrow(false); + create(); + const controls = TestBed.inject(UwtInspectorSession).controls; + expect(window.matchMedia).toHaveBeenCalledWith('(width < 640px)'); + expect(controls?.panesFit()).toBe(true); + + change?.({ matches: true }); + expect(controls?.panesFit()).toBe(false); + }); + }); +}); diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-event-inspector.service/uwt-event-inspector.service.ts b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-event-inspector.service/uwt-event-inspector.service.ts new file mode 100644 index 0000000..0619c44 --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-event-inspector.service/uwt-event-inspector.service.ts @@ -0,0 +1,403 @@ +import { DOCUMENT, isPlatformBrowser } from '@angular/common'; +import { + ApplicationRef, + ComponentRef, + createComponent, + DestroyRef, + EnvironmentInjector, + inject, + Injectable, + NgZone, + PLATFORM_ID, + signal +} from '@angular/core'; +import { UWT_TELEMETRY_IDENTITY } from '@absaoss-cps/ngx-ui-watchtower'; +import { CpsDialogRef, CpsDialogService } from 'cps-ui-kit'; +import { + UWT_EVENT_INSPECTOR_CONFIG, + UwtEventInspectorConfig, + UwtInspectorDock, + UwtInspectorPaneSide +} from '../uwt-inspector.models/uwt-inspector.models'; +import { UWT_DEFAULT_INSPECTOR_CONFIG } from '../uwt-inspector-store/uwt-inspector-store'; +import { + UWT_DEFAULT_INSPECTOR_SHORTCUTS, + uwtMatchesShortcut +} from '../uwt-inspector-shortcut/uwt-inspector-shortcut'; +import { UwtInspectorComponent } from '../uwt-inspector/uwt-inspector.component'; +import { UwtInspectorPaneComponent } from '../uwt-inspector-pane/uwt-inspector-pane.component'; +import { + uwtInspectorWindowName, + uwtOpenInspectorWindow +} from '../uwt-inspector-window/uwt-inspector-window'; +import { + UwtInspectorHost, + UwtInspectorSession +} from '../uwt-inspector-session/uwt-inspector-session'; +import { UwtInspectorPreferenceStorage } from '../uwt-inspector-preferences/uwt-inspector-preferences'; +import { UwtInspectorRing } from '../uwt-inspector-ring/uwt-inspector-ring'; +import { + uwtSafe, + uwtSafeVoid +} from '../uwt-inspector-internal.util/uwt-inspector-internal.util'; + +/** Narrower than 640 CSS pixels, the app's window takes no pane. */ +const NARROW = '(width < 640px)'; + +/** + * Opens and closes the Event Inspector, moves it between docks, and + * listens for its keyboard shortcut once {@link provideUwtEventInspector} + * installs it. + * + * It opens where it was last — a dialog the first time, and on a screen + * too narrow for a pane — and the application stays usable while events + * arrive. With it open, the shortcut returns focus to it from the + * application, and closes it when focus is already inside. + * + * @example + * ```typescript + * // From an app's own "Support" menu, when the shortcut is disabled: + * inject(UwtEventInspectorService).open(); + * ``` + * + * @group Services + */ +@Injectable({ providedIn: 'root' }) +export class UwtEventInspectorService { + private readonly dialogs = inject(CpsDialogService); + private readonly document = inject(DOCUMENT); + private readonly zone = inject(NgZone); + private readonly appRef = inject(ApplicationRef); + private readonly environment = inject(EnvironmentInjector); + private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID)); + private readonly destroyRef = inject(DestroyRef); + private readonly session = inject(UwtInspectorSession); + private readonly preferences = inject(UwtInspectorPreferenceStorage); + private readonly ring = inject(UwtInspectorRing); + private readonly identity = inject(UWT_TELEMETRY_IDENTITY, { + optional: true + }); + + private readonly config: UwtEventInspectorConfig = { + ...UWT_DEFAULT_INSPECTOR_CONFIG, + shortcuts: UWT_DEFAULT_INSPECTOR_SHORTCUTS, + ...inject(UWT_EVENT_INSPECTOR_CONFIG, { optional: true }) + }; + + private readonly enabled = uwtSafe( + 'enabled', + () => + typeof this.config.enabled === 'function' + ? this.config.enabled() + : this.config.enabled, + false + ); + + private host?: UwtInspectorHost; + private installed = false; + private warnedBlocked = false; + private lastKey?: string; + + private readonly opened = signal(false); + + /** + * What had focus in the app as the inspector opened, for a pane to give + * it back as it closes; the dialog does so itself. + */ + private focusedBefore?: HTMLElement; + + /** Whether the inspector is currently open, in any dock. */ + readonly isOpen = this.opened.asReadonly(); + + private readonly shownIn = signal(undefined); + + /** + * Where the inspector is shown while open — which can differ from the dock + * asked for, as a blocked window or a narrow screen makes it a dialog — + * and `undefined` while closed. + */ + readonly dock = this.shownIn.asReadonly(); + + /** Whether the app's window is wide enough for a pane beside it. */ + private readonly panesFit = signal(true); + + constructor() { + this.session.controls = { + setDock: (dock) => this.setDock(dock), + close: () => this.close(), + panesFit: this.panesFit.asReadonly() + }; + this.destroyRef.onDestroy(() => this.close()); + uwtSafeVoid('width', () => this.watchWidth()); + } + + /** + * Starts listening for the shortcut, and keeping events from before + * opening when `captureBeforeOpen` asks for it. Called by + * {@link provideUwtEventInspector}; does nothing on the server, when + * disabled, or a second time. + */ + install(): void { + if (this.installed || !this.isBrowser || !this.enabled) { + return; + } + this.installed = true; + uwtSafeVoid('captureBeforeOpen', () => + this.ring.start( + Math.min(this.config.captureBeforeOpen, this.config.maxEventsPerSection) + ) + ); + if (this.config.shortcuts.length === 0) { + return; + } + + // Capture phase, so it works even where a component stops propagation. + // Outside Angular's zone, so ordinary typing triggers no change detection. + this.zone.runOutsideAngular(() => + this.document.addEventListener('keydown', this.onKeydown, true) + ); + this.destroyRef.onDestroy(() => + this.document.removeEventListener('keydown', this.onKeydown, true) + ); + } + + /** Opens the inspector where it was last, or focuses it when open. */ + open(): void { + if (!this.enabled || !this.isBrowser) { + return; + } + if (this.host) { + this.host.focus(); + return; + } + const focused = this.document.activeElement; + this.focusedBefore = + focused instanceof HTMLElement && focused !== this.document.body + ? focused + : undefined; + this.show(this.preferences.read().dock); + } + + /** + * Moves the inspector to `dock`, opening it there when closed, and + * remembers the choice. Its captured events, filters and pause carry + * over. Opening a window must happen during a click or key press, or the + * browser blocks it; the inspector then opens as a dialog. + */ + setDock(dock: UwtInspectorDock): void { + if (!this.enabled || !this.isBrowser) { + return; + } + this.preferences.update({ dock }); + if (this.host?.dock === dock) { + // Already there: rebuilding would only lose its page and expanded rows. + this.open(); + return; + } + const previous = this.host; + this.host = undefined; + uwtSafeVoid('setDock', () => previous?.destroy()); + this.show(dock); + } + + /** Closes the inspector, discarding what it captured. */ + close(): void { + const host = this.host; + const giveBack = + host && host.dock !== 'dialog' && host.dock !== 'window' + ? uwtSafe('focus', () => host.containsFocus(), false) + : false; + const before = this.focusedBefore; + this.host = undefined; + this.focusedBefore = undefined; + uwtSafeVoid('close', () => host?.destroy()); + if (giveBack && before?.isConnected) { + uwtSafeVoid('focus', () => before.focus()); + } + this.session.end(); + this.opened.set(false); + this.shownIn.set(undefined); + } + + /** + * What the shortcut does: open when closed; with the inspector open, + * close it if focus is inside, otherwise bring focus back to it. + */ + toggle(): void { + if (!this.host) { + this.open(); + } else if (this.host.containsFocus()) { + this.close(); + } else { + this.host.focus(); + } + } + + private readonly onKeydown = (event: KeyboardEvent): void => + uwtSafeVoid('shortcut', () => { + // A shortcut like Shift+E+I needs the key pressed before this one. + const previous = this.lastKey; + this.lastKey = event.code; + if ( + !this.config.shortcuts.some((s) => + uwtMatchesShortcut(event, s, previous) + ) + ) { + return; + } + event.preventDefault(); + this.zone.run(() => this.toggle()); + }); + + /** Keeps {@link panesFit} up to date as the app's window is resized. */ + private watchWidth(): void { + const narrow = this.isBrowser + ? this.document.defaultView?.matchMedia?.(NARROW) + : undefined; + if (!narrow) { + return; + } + this.panesFit.set(!narrow.matches); + const onChange = (event: MediaQueryListEvent) => + this.panesFit.set(!event.matches); + narrow.addEventListener('change', onChange); + this.destroyRef.onDestroy(() => + narrow.removeEventListener('change', onChange) + ); + } + + /** Whether the app's window is too narrow for a pane, right now. */ + private isNarrow(): boolean { + // A fresh query: one made earlier catches up with a resize a frame late. + return !!this.document.defaultView?.matchMedia?.(NARROW).matches; + } + + /** Shows it in `dock`; a pane on a narrow screen shows as a dialog. */ + private show(dock: UwtInspectorDock): void { + uwtSafeVoid('open', () => { + // A window or pane that fails to open leaves nothing behind, and the + // dialog stands in, as for a blocked window. + this.host = + dock === 'window' + ? (uwtSafe('window', () => this.openWindow(), undefined) ?? + this.openDialog()) + : dock === 'dialog' || this.isNarrow() + ? this.openDialog() + : (uwtSafe('pane', () => this.openPane(dock), undefined) ?? + this.openDialog()); + this.opened.set(true); + this.shownIn.set(this.host.dock); + }); + // A dialog focuses itself once rendered; a pane or window is ready now. + uwtSafeVoid('focus', () => this.host?.focus()); + } + + /** The user closed it themselves: the dialog's ✕ or Escape, the window. */ + private closedByUser(host: UwtInspectorHost): void { + if (this.host === host) { + this.close(); + } + } + + private openDialog(): UwtInspectorHost { + const ref: CpsDialogRef = this.dialogs.open( + UwtInspectorComponent, + { + headerTitle: 'Event Inspector', + ariaLabel: 'Event Inspector', + modal: false, + draggable: true, + resizable: true, + maximizable: true, + // The dialog's own Escape works anywhere on the page; the inspector + // closes on Escape from inside it instead. Replace this with the + // kit's own focus-scoped Escape once it has one: + // https://github.com/AbsaOSS/cps-shared-ui/issues/951 + closeOnEscape: false, + // Docked right at half the screen, so the application stays + // visible and clickable beside it, but never narrower than 40rem + // (nearly all of a small screen), so the tables have room; + // resizable, and maximizable for a closer look. + position: 'right', + width: 'clamp(min(40rem, 96vw), 50vw, 64rem)', + height: '88vh', + minWidth: 'min(22rem, 96vw)', + // The inspector itself, not its search field, where the shortcut + // types; see focusInspector(). + autoFocus: 'uwt-inspector', + // Hooks the title's alignment; see the inspector's styles. + styleClass: 'uwt-inspector-dialog' + } + ); + const host: UwtInspectorHost = { + dock: 'dialog', + focus: () => ref.componentInstance?.focusInspector(), + containsFocus: () => !!ref.componentInstance?.containsFocus(), + // Its close waits for the hide animation, which a dialog closed before + // it was ever shown never has: that one goes at once. + destroy: () => (ref.isOpen() ? ref.close() : ref.destroy()) + }; + ref.onDestroy.subscribe(() => this.closedByUser(host)); + return host; + } + + private openPane(side: UwtInspectorPaneSide): UwtInspectorHost { + const element = this.document.body.appendChild( + this.document.createElement('uwt-inspector-pane') + ); + let created: ComponentRef | undefined; + // Destroying the view detaches it from the app, unless the app, being + // destroyed, already has: detaching it here would warn (NG0406). + const destroy = () => { + created?.destroy(); + element.remove(); + }; + try { + created = createComponent(UwtInspectorPaneComponent, { + environmentInjector: this.environment, + hostElement: element + }); + created.setInput('side', side); + this.appRef.attachView(created.hostView); + created.changeDetectorRef.detectChanges(); + } catch (error) { + // Half open: nothing left behind, for the caller to open a dialog. + destroy(); + throw error; + } + const ref = created; + return { + dock: side, + focus: () => ref.instance.content().focusInspector(), + containsFocus: () => ref.instance.content().containsFocus(), + destroy + }; + } + + private openWindow(): UwtInspectorHost | undefined { + const application = this.identity?.application; + const host = uwtOpenInspectorWindow({ + document: this.document, + environment: this.environment, + appRef: this.appRef, + title: application + ? `Event Inspector · ${application}` + : 'Event Inspector', + name: uwtInspectorWindowName(application), + geometry: this.preferences.read().window, + onClosed: () => host && this.closedByUser(host), + // A reload starts over, as on any page: a new session, in the same window. + onReloaded: () => this.session.end(), + onKeydown: this.onKeydown, + onGeometry: (window) => this.preferences.update({ window }) + }); + if (!host && !this.warnedBlocked) { + this.warnedBlocked = true; + // eslint-disable-next-line no-console + console.warn( + '[ngx-ui-watchtower] the browser blocked the Event Inspector window; it opens as a dialog instead. Open it from a click or key press, or allow pop-ups for this site' + ); + } + return host; + } +} diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-dock-switcher/uwt-inspector-dock-switcher.component.html b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-dock-switcher/uwt-inspector-dock-switcher.component.html new file mode 100644 index 0000000..d2a4510 --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-dock-switcher/uwt-inspector-dock-switcher.component.html @@ -0,0 +1,117 @@ + + + + + @switch (dock) { + @case ('dialog') { + + } + @case ('window') { + + } + @case ('left') { + + } + @case ('bottom') { + + } + @case ('right') { + + } + } + + +@if (compact()) { + + + + + +} @else { +
+ @for (option of options(); track option.dock) { + + } +
+} diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-dock-switcher/uwt-inspector-dock-switcher.component.scss b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-dock-switcher/uwt-inspector-dock-switcher.component.scss new file mode 100644 index 0000000..fff53cc --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-dock-switcher/uwt-inspector-dock-switcher.component.scss @@ -0,0 +1,166 @@ +// Styled like cps-button-toggle: calm outline, the current option filled. +:host { + display: inline-flex; + align-items: center; + gap: var(--uwt-inspector-space-m); + font-size: 1rem; + font-family: 'Source Sans Pro', sans-serif; + color: var(--cps-color-text-dark); +} + +.uwt-inspector-dock__group { + display: inline-flex; +} + +.uwt-inspector-dock__option { + display: inline-flex; + align-items: center; + justify-content: center; + width: 2.25rem; + height: 2rem; + margin: 0; + padding: 0; + border: 0.0625rem solid var(--cps-color-calm); + background-color: #fff; + color: var(--cps-color-calm); + cursor: pointer; + + & + & { + margin-left: -0.0625rem; + } + + &:first-child { + border-radius: 0.25rem 0 0 0.25rem; + } + + &:last-child { + border-radius: 0 0.25rem 0.25rem 0; + } + + &:hover { + background-color: var(--cps-color-calm-lighten5); + } + + &[aria-pressed='true'] { + background-color: var(--cps-color-calm); + color: #fff; + cursor: default; + } + + &:focus-visible { + position: relative; + z-index: 1; + outline: 0.0625rem solid var(--cps-color-calm); + outline-offset: 0.125rem; + box-shadow: 0 0 0 0.25rem var(--cps-color-calm-highlighten); + } +} + +svg { + width: 1rem; + height: 1rem; + fill: none; + stroke: currentColor; + stroke-width: 1.2; +} + +.filled { + fill: currentColor; + stroke: none; +} + +// Narrow: the current dock and a chevron, opening a list of every dock, each +// named — the buttons' icons alone need their tooltips. +.uwt-inspector-dock__trigger { + display: inline-flex; + align-items: center; + gap: 0.25rem; + height: 2rem; + padding: 0 0.5rem; + border: 0.0625rem solid var(--cps-color-calm); + border-radius: 0.25rem; + background-color: #fff; + color: var(--cps-color-calm); + cursor: pointer; + + &:hover { + background-color: var(--cps-color-calm-lighten5); + } + + &[aria-expanded='true'] { + background-color: var(--cps-color-calm); + color: #fff; + } + + &:focus-visible { + outline: 0.0625rem solid var(--cps-color-calm); + outline-offset: 0.125rem; + box-shadow: 0 0 0 0.25rem var(--cps-color-calm-highlighten); + } +} + +.uwt-inspector-dock__chevron { + width: 0.75rem; + height: 0.75rem; +} + +// In the overlay cps-menu draws, so it names its own font and colour. +.uwt-inspector-dock__menu { + display: flex; + flex-direction: column; + min-width: 11rem; + padding: 0.25rem; + font-family: 'Source Sans Pro', sans-serif; + font-size: 0.875rem; + color: var(--cps-color-text-dark); +} + +.uwt-inspector-dock__item { + display: flex; + align-items: center; + gap: 0.75rem; + min-height: 2.25rem; + padding: 0 0.75rem; + border: 0; + border-radius: 0.25rem; + background: none; + color: inherit; + font: inherit; + text-align: left; + cursor: pointer; + + &:hover { + background-color: var(--cps-color-calm-lighten5); + } + + &[aria-checked='true'] { + font-weight: 600; + color: var(--cps-color-calm); + } + + &:focus-visible { + outline: 0.0625rem solid var(--cps-color-calm); + outline-offset: -0.0625rem; + background-color: var(--cps-color-calm-lighten5); + } +} + +.uwt-inspector-dock__item-label { + flex: 1; +} + +.uwt-inspector-dock__check { + stroke-width: 1.6; +} + +// Forced colours drop the fill that marks the current dock: it takes the +// system's selection colours instead. +@media (forced-colors: active) { + .uwt-inspector-dock__trigger[aria-expanded='true'], + .uwt-inspector-dock__option[aria-pressed='true'] { + forced-color-adjust: none; + border-color: Highlight; + background-color: Highlight; + color: HighlightText; + } +} diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-dock-switcher/uwt-inspector-dock-switcher.component.spec.ts b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-dock-switcher/uwt-inspector-dock-switcher.component.spec.ts new file mode 100644 index 0000000..f4fe3a5 --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-dock-switcher/uwt-inspector-dock-switcher.component.spec.ts @@ -0,0 +1,148 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { UwtInspectorDock } from '../uwt-inspector.models/uwt-inspector.models'; +import { UwtInspectorDockSwitcherComponent } from './uwt-inspector-dock-switcher.component'; + +describe('UwtInspectorDockSwitcherComponent', () => { + let fixture: ComponentFixture; + let chosen: UwtInspectorDock[]; + const buttons = () => + Array.from( + (fixture.nativeElement as HTMLElement).querySelectorAll('button') + ); + + beforeEach(() => { + TestBed.configureTestingModule({ imports: [NoopAnimationsModule] }); + fixture = TestBed.createComponent(UwtInspectorDockSwitcherComponent); + chosen = []; + fixture.componentInstance.dockChange.subscribe((d) => chosen.push(d)); + fixture.componentRef.setInput('dock', 'right'); + fixture.detectChanges(); + }); + + it('should offer every dock, named, with the current one pressed', () => { + expect(buttons().map((b) => b.getAttribute('aria-label'))).toEqual([ + 'Dialog', + 'Separate window', + 'Dock left', + 'Dock bottom', + 'Dock right' + ]); + expect( + buttons().map((b) => b.getAttribute('aria-pressed') === 'true') + ).toEqual([false, false, false, false, true]); + }); + + it('should offer only the dialog and the window without room for a pane', () => { + fixture.componentRef.setInput('panes', false); + fixture.detectChanges(); + + expect(buttons().map((b) => b.getAttribute('data-dock'))).toEqual([ + 'dialog', + 'window' + ]); + }); + + it('should ask for another dock, and ignore the current one', () => { + buttons()[4].click(); + buttons()[1].click(); + + expect(chosen).toEqual(['window']); + }); + + describe('compact', () => { + const trigger = () => + (fixture.nativeElement as HTMLElement).querySelector( + '.uwt-inspector-dock__trigger' + )!; + const items = () => + Array.from( + document.querySelectorAll('[role="menuitemradio"]') + ); + /** Opens the menu; its animation callbacks run once the app is stable. */ + const open = async () => { + trigger().click(); + fixture.detectChanges(); + await fixture.whenStable(); + fixture.detectChanges(); + }; + + beforeEach(() => { + fixture.componentRef.setInput('compact', true); + fixture.detectChanges(); + }); + + afterEach(() => fixture.destroy()); + + it('should show the current dock as one button that opens a menu, not the five', () => { + expect(buttons()).toEqual([trigger()]); + expect(trigger().getAttribute('aria-haspopup')).toBe('menu'); + expect(trigger().getAttribute('aria-expanded')).toBe('false'); + expect(trigger().getAttribute('aria-label')).toBe( + 'Dock position: Dock right' + ); + }); + + it('should list every dock by name in the menu, the current one checked', async () => { + await open(); + + expect(trigger().getAttribute('aria-expanded')).toBe('true'); + expect(items().map((i) => i.textContent?.trim())).toEqual([ + 'Dialog', + 'Separate window', + 'Dock left', + 'Dock bottom', + 'Dock right' + ]); + expect(items().map((i) => i.getAttribute('aria-checked'))).toEqual([ + 'false', + 'false', + 'false', + 'false', + 'true' + ]); + }); + + it('should offer only the dialog and the window without room for a pane', async () => { + fixture.componentRef.setInput('panes', false); + fixture.detectChanges(); + await open(); + + expect(items().map((i) => i.getAttribute('data-dock'))).toEqual([ + 'dialog', + 'window' + ]); + }); + + it('should ask for the dock chosen, and close; the current one asks for nothing', async () => { + await open(); + items()[2].click(); + fixture.detectChanges(); + await fixture.whenStable(); + fixture.detectChanges(); + + expect(chosen).toEqual(['left']); + expect(trigger().getAttribute('aria-expanded')).toBe('false'); + + await open(); + items()[4].click(); + expect(chosen).toEqual(['left']); + }); + + it('should move between the items with the arrow keys, Home and End', async () => { + await open(); + const press = (key: string) => { + document.activeElement?.dispatchEvent( + new KeyboardEvent('keydown', { key, bubbles: true, cancelable: true }) + ); + return document.activeElement; + }; + items()[4].focus(); + + expect(press('ArrowDown')).toBe(items()[0]); + expect(press('ArrowUp')).toBe(items()[4]); + expect(press('Home')).toBe(items()[0]); + expect(press('End')).toBe(items()[4]); + }); + }); +}); diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-dock-switcher/uwt-inspector-dock-switcher.component.ts b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-dock-switcher/uwt-inspector-dock-switcher.component.ts new file mode 100644 index 0000000..fcb8f1c --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-dock-switcher/uwt-inspector-dock-switcher.component.ts @@ -0,0 +1,84 @@ +import { NgTemplateOutlet } from '@angular/common'; +import { + ChangeDetectionStrategy, + Component, + computed, + input, + output, + signal +} from '@angular/core'; +import { CpsMenuComponent, CpsTooltipDirective } from 'cps-ui-kit'; +import { + UWT_INSPECTOR_DOCKS, + UwtInspectorDock +} from '../uwt-inspector.models/uwt-inspector.models'; + +const DOCK_LABEL: Record = { + dialog: 'Dialog', + window: 'Separate window', + left: 'Dock left', + bottom: 'Dock bottom', + right: 'Dock right' +}; + +/** + * Moves the inspector between docks, like Chrome DevTools' dock side: one + * icon per dock, the current one pressed. cps-ui-kit has no dock icons, so + * they are drawn here. Without room for a pane, only the dialog and the + * window are offered. Where the inspector is too narrow for the buttons, one + * button shows the current dock and opens a menu of them all. + */ +@Component({ + selector: 'uwt-inspector-dock-switcher', + imports: [CpsMenuComponent, CpsTooltipDirective, NgTemplateOutlet], + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: './uwt-inspector-dock-switcher.component.html', + styleUrl: './uwt-inspector-dock-switcher.component.scss' +}) +export class UwtInspectorDockSwitcherComponent { + readonly dock = input.required(); + /** Whether the panes are offered; not on a narrow screen. */ + readonly panes = input(true); + /** Too narrow for the buttons: a menu instead. */ + readonly compact = input(false); + readonly dockChange = output(); + + protected readonly options = computed(() => + UWT_INSPECTOR_DOCKS.filter( + (dock) => this.panes() || dock === 'dialog' || dock === 'window' + ).map((dock) => ({ dock, label: DOCK_LABEL[dock] })) + ); + + protected readonly open = signal(false); + + protected readonly currentLabel = computed(() => DOCK_LABEL[this.dock()]); + + /** Arrow keys, Home and End move between the menu's items. */ + protected onMenuKey(event: KeyboardEvent): void { + const items = Array.from( + (event.currentTarget as HTMLElement).querySelectorAll( + '[role="menuitemradio"]' + ) + ); + const at = items.indexOf( + (event.currentTarget as HTMLElement).ownerDocument + .activeElement as HTMLElement + ); + const next: Record = { + ArrowDown: (at + 1) % items.length, + ArrowUp: (at - 1 + items.length) % items.length, + Home: 0, + End: items.length - 1 + }; + if (event.key in next && items.length) { + event.preventDefault(); + items[next[event.key]].focus(); + } + } + + protected choose(dock: UwtInspectorDock): void { + if (dock !== this.dock()) { + this.dockChange.emit(dock); + } + } +} diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-export/uwt-inspector-export.spec.ts b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-export/uwt-inspector-export.spec.ts new file mode 100644 index 0000000..ab54e5a --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-export/uwt-inspector-export.spec.ts @@ -0,0 +1,135 @@ +import { UwtTelemetryObservedEvent } from '@absaoss-cps/ngx-ui-watchtower'; +import { + uwtBuildInspectorExport, + uwtInspectorFilename, + uwtDownloadJson, + uwtSafeStringify +} from './uwt-inspector-export'; + +function bi(sequence: number): UwtTelemetryObservedEvent { + return { + kind: 'bi', + eventType: 'com.uwt.bi', + sequence, + capturedAt: '2026-01-01T10:00:00.000Z', + destination: 'sink', + origin: { forwarded: false }, + payload: { + eventName: `e${sequence}`, + eventTime: '2026-01-01T10:00:00.000Z', + application: 'app', + metadata: { nested: 'kept' } + } + }; +} + +const context = { + section: 'bi' as const, + startedAt: '2026-01-01T09:00:00.000Z', + paused: false, + app: { + application: 'app', + environment: 'test', + version: '1.0.0', + eventNamespace: 'com.uwt' + }, + session: { sessionId: 's-1' }, + userAgent: 'jest', + activeFilters: { + text: 'e2', + fields: [ + { id: '1', path: 'kind', operator: 'equals' as const, value: 'bi' } + ] + }, + count: { captured: 3, droppedOldest: 0 }, + now: new Date('2026-01-01T10:05:00.000Z') +}; + +describe('uwtBuildInspectorExport', () => { + it('should contain every event given, in hand-off order, whatever the filters', () => { + const doc = uwtBuildInspectorExport([bi(3), bi(1), bi(2)], context); + + expect(doc.events.map((e) => e.sequence)).toEqual([1, 2, 3]); + // Filters are recorded for context, not applied. + expect(doc.activeFilters.text).toBe('e2'); + }); + + it('should carry the format, capture window and app identity', () => { + const doc = uwtBuildInspectorExport([bi(1)], context); + + expect(doc).toMatchObject({ + format: 'ngx-ui-watchtower-event-inspector', + formatVersion: 2, + section: 'bi', + exportedAt: '2026-01-01T10:05:00.000Z', + capture: { + startedAt: '2026-01-01T09:00:00.000Z', + endedAt: '2026-01-01T10:05:00.000Z', + paused: false + }, + app: { application: 'app', eventNamespace: 'com.uwt' }, + session: { sessionId: 's-1' }, + count: { captured: 3, droppedOldest: 0 } + }); + }); + + it('should round-trip as valid JSON with nested payloads intact', () => { + const json = uwtSafeStringify(uwtBuildInspectorExport([bi(1)], context), 2); + const parsed = JSON.parse(json); + expect(parsed.events[0].payload.metadata.nested).toBe('kept'); + }); +}); + +describe('uwtSafeStringify', () => { + it('should replace a cycle instead of throwing', () => { + const value: Record = { a: 1 }; + value.self = value; + expect(JSON.parse(uwtSafeStringify(value))).toEqual({ + a: 1, + self: '[Circular]' + }); + }); + + it('should keep an object that is shared, but not circular', () => { + const shared = { x: 1 }; + expect(JSON.parse(uwtSafeStringify({ a: shared, b: shared }))).toEqual({ + a: { x: 1 }, + b: { x: 1 } + }); + }); + + it('should write a bigint as text', () => { + expect(uwtSafeStringify({ n: BigInt(12) })).toBe('{"n":"12"}'); + }); +}); + +describe('uwtInspectorFilename', () => { + it('should name the app, kind and time, and nothing identifying', () => { + const at = new Date(2026, 0, 2, 3, 4, 5); + expect(uwtInspectorFilename('my app/x', 'logging', at)).toBe( + 'ngx-ui-watchtower-events-my-app-x-logging-20260102-030405.json' + ); + expect(uwtInspectorFilename('shop', 'all', at)).toBe( + 'ngx-ui-watchtower-events-shop-all-20260102-030405.json' + ); + }); +}); + +describe('uwtDownloadJson', () => { + it('should click a temporary link to a JSON blob, then remove it', () => { + const created = jest.fn(() => 'blob:x'); + Object.assign(URL, { + createObjectURL: created, + revokeObjectURL: jest.fn() + }); + const click = jest + .spyOn(HTMLAnchorElement.prototype, 'click') + .mockImplementation(() => undefined); + + uwtDownloadJson(document, 'f.json', '{}'); + + expect(created).toHaveBeenCalledWith(expect.any(Blob)); + expect(click).toHaveBeenCalledTimes(1); + expect(document.querySelector('a[download]')).toBeNull(); + }); +}); diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-export/uwt-inspector-export.ts b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-export/uwt-inspector-export.ts new file mode 100644 index 0000000..07e02fd --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-export/uwt-inspector-export.ts @@ -0,0 +1,121 @@ +import { UwtTelemetryObservedEvent } from '@absaoss-cps/ngx-ui-watchtower'; +import { + UwtInspectorExport, + UwtInspectorFilterState, + UwtInspectorKindFilter +} from '../uwt-inspector.models/uwt-inspector.models'; + +/** What the export needs to know beyond the events themselves. */ +export interface UwtInspectorExportContext { + section: UwtInspectorKindFilter; + startedAt: string; + paused: boolean; + app: UwtInspectorExport['app']; + session: UwtInspectorExport['session']; + userAgent: string; + activeFilters: UwtInspectorFilterState; + count: UwtInspectorExport['count']; + now?: Date; +} + +/** + * The downloadable document for a kind, or every kind: every event captured + * of it, in the order they were handed over, regardless of the filters on + * screen. + */ +export function uwtBuildInspectorExport( + events: readonly UwtTelemetryObservedEvent[], + context: UwtInspectorExportContext +): UwtInspectorExport { + const now = (context.now ?? new Date()).toISOString(); + return { + format: 'ngx-ui-watchtower-event-inspector', + formatVersion: 2, + section: context.section, + exportedAt: now, + capture: { + startedAt: context.startedAt, + endedAt: now, + paused: context.paused + }, + app: context.app, + session: context.session, + userAgent: context.userAgent, + activeFilters: context.activeFilters, + count: context.count, + events: [...events].sort((a, b) => a.sequence - b.sequence) + }; +} + +/** + * `JSON.stringify` that never throws on a payload: a repeated reference + * becomes `"[Circular]"`, and a `bigint` becomes its decimal text. + */ +export function uwtSafeStringify(value: unknown, indent?: number): string { + const ancestors: object[] = []; + return JSON.stringify( + value, + function (this: unknown, _key: string, v: unknown) { + if (typeof v === 'bigint') { + return v.toString(); + } + if (v === null || typeof v !== 'object') { + return v; + } + // `this` is the object holding v: unwind to it, then check for a loop. + while (ancestors.length && ancestors[ancestors.length - 1] !== this) { + ancestors.pop(); + } + if (ancestors.includes(v)) { + return '[Circular]'; + } + ancestors.push(v); + return v; + }, + indent + ); +} + +/** + * `ngx-ui-watchtower-events---.json`, + * the kind being `all`, `bi`, `scenario` or `logging` — no session or user + * id, since the name ends up in folders and tickets. + */ +export function uwtInspectorFilename( + application: string, + section: UwtInspectorKindFilter, + at: Date +): string { + const pad = (n: number) => String(n).padStart(2, '0'); + const stamp = + `${at.getFullYear()}${pad(at.getMonth() + 1)}${pad(at.getDate())}-` + + `${pad(at.getHours())}${pad(at.getMinutes())}${pad(at.getSeconds())}`; + const app = application.replace(/[^a-z0-9._-]+/gi, '-') || 'app'; + return `ngx-ui-watchtower-events-${app}-${section}-${stamp}.json`; +} + +/** + * Saves `json` as a file through a temporary link. The browser may still + * block it without telling the page, so the popup also offers copying. + */ +export function uwtDownloadJson( + doc: Document, + filename: string, + json: string +): void { + const url = URL.createObjectURL( + new Blob([json], { type: 'application/json' }) + ); + const link = doc.createElement('a'); + link.href = url; + link.download = filename; + link.style.display = 'none'; + doc.body.appendChild(link); + try { + link.click(); + } finally { + link.remove(); + // Revoked after the click has been handed to the browser. + setTimeout(() => URL.revokeObjectURL(url), 0); + } +} diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-export/uwt-inspector-exporter.ts b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-export/uwt-inspector-exporter.ts new file mode 100644 index 0000000..07cd4b2 --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-export/uwt-inspector-exporter.ts @@ -0,0 +1,155 @@ +import { DOCUMENT } from '@angular/common'; +import { inject, Injectable } from '@angular/core'; +import { + UWT_DEFAULT_EVENT_NAMESPACE, + UWT_TELEMETRY_IDENTITY, + UwtTelemetrySink +} from '@absaoss-cps/ngx-ui-watchtower'; +import { CPS_LIVE_ANNOUNCER_SERVICE, CpsNotificationService } from 'cps-ui-kit'; +import { + UwtInspectorKindFilter, + UwtInspectorSectionId +} from '../uwt-inspector.models/uwt-inspector.models'; +import { UwtInspectorStore } from '../uwt-inspector-store/uwt-inspector-store'; +import { + uwtBuildInspectorExport, + uwtInspectorFilename, + uwtDownloadJson, + uwtSafeStringify +} from './uwt-inspector-export'; + +/** What each kind calls its events, when telling people about them. */ +export const UWT_INSPECTOR_SECTION_NOUN: Record = + { bi: 'BI', scenario: 'scenario', logging: 'log' }; + +/** `events`, or `BI events`: what an export of `kind` holds. */ +export function uwtInspectorEventsNoun(kind: UwtInspectorKindFilter): string { + return kind === 'all' + ? 'events' + : `${UWT_INSPECTOR_SECTION_NOUN[kind]} events`; +} + +/** `1 BI event`, `3 events`: a number of a kind's events. */ +export function uwtInspectorEventCount( + count: number, + kind: UwtInspectorKindFilter +): string { + const noun = uwtInspectorEventsNoun(kind); + return `${count} ${count === 1 ? noun.slice(0, -1) : noun}`; +} + +/** + * Downloads or copies the captured events of a kind, or of every kind, as + * JSON. Provided by the popup, beside its {@link UwtInspectorStore}. + */ +@Injectable() +export class UwtInspectorExporter { + private readonly store = inject(UwtInspectorStore); + private readonly identity = inject(UWT_TELEMETRY_IDENTITY, { + optional: true + }); + + private readonly sink = inject(UwtTelemetrySink, { optional: true }); + private readonly document = inject(DOCUMENT); + private readonly announcer = inject(CPS_LIVE_ANNOUNCER_SERVICE, { + optional: true + }); + + private readonly notifications = inject(CpsNotificationService, { + optional: true + }); + + /** Saves the kind's events as a JSON file. */ + download(kind: UwtInspectorKindFilter): void { + try { + const json = this.json(kind); + uwtDownloadJson( + this.document, + uwtInspectorFilename( + this.identity?.application ?? 'app', + kind, + new Date() + ), + json + ); + this.announce( + `Downloaded ${uwtInspectorEventCount(this.store.count(kind).captured, kind)}.` + ); + } catch (error) { + this.fail("Couldn't create the download", error); + } + } + + /** + * Puts the kind's events on the clipboard, for when a browser blocks + * downloads. + * + * @returns whether copying worked + */ + async copy(kind: UwtInspectorKindFilter): Promise { + try { + // The inspector's own window: in a separate window, the app's + // clipboard refuses, as the app's page doesn't have focus. + const view = this.document.defaultView ?? window; + await view.navigator.clipboard.writeText(this.json(kind)); + this.announce( + `${uwtInspectorEventCount(this.store.count(kind).captured, kind)} copied as JSON.` + ); + return true; + } catch (error) { + this.fail("Couldn't copy to the clipboard", error); + return false; + } + } + + /** The kind's export document, serialized. */ + json(kind: UwtInspectorKindFilter): string { + return uwtSafeStringify( + uwtBuildInspectorExport(this.store.events(kind), { + section: kind, + startedAt: this.store.startedAt(), + paused: this.store.paused(), + app: { + application: this.identity?.application ?? '', + environment: this.identity?.environment ?? '', + version: this.identity?.version ?? '', + eventNamespace: + this.identity?.eventNamespace ?? UWT_DEFAULT_EVENT_NAMESPACE + }, + session: this.session(), + userAgent: this.document.defaultView?.navigator.userAgent ?? '', + activeFilters: this.store.filters(), + count: this.store.count(kind) + }), + 2 + ); + } + + private session(): { sessionId?: string; userId?: string } { + try { + return { + sessionId: this.sink?.getSessionId(), + userId: this.sink?.getUserId() + }; + } catch { + return {}; + } + } + + private announce(message: string): void { + try { + this.announcer?.announce(message, 'polite'); + } catch { + // Announcements are a courtesy; never let one break the popup. + } + } + + private fail(message: string, error: unknown): void { + const detail = error instanceof Error ? error.message : String(error); + try { + this.notifications?.error(message, detail); + } catch { + // Nothing more to do; the popup keeps working. + } + } +} diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-filter-bar/uwt-inspector-filter-bar.component.html b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-filter-bar/uwt-inspector-filter-bar.component.html new file mode 100644 index 0000000..708c667 --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-filter-bar/uwt-inspector-filter-bar.component.html @@ -0,0 +1,115 @@ +
+
+ + + + +
+ + @if (builderOpen()) { +
+ + + + + @if (needsValue()) { + + + } + + +
+ +
+ How filters work +

+ Search matches any field's value or name, ignoring case. A filter + compares one field, and every filter must match. For example: +

+
    +
  • + Event name equals theme_changed shows + that event only. +
  • +
  • + Step name contains pay shows scenarios + with any step like it; its path, steps[].name, covers + every step, and steps[2].name only the third. +
  • +
+

+ Filters narrow what is shown. Export still holds every event captured of + the kind shown. +

+
+ } + + @if (chips().length > 0 || anyActive()) { +
+ Filters: +
    + @for (chip of chips(); track chip.id) { +
  • + + +
  • + } +
+ + +
+ } +
diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-filter-bar/uwt-inspector-filter-bar.component.scss b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-filter-bar/uwt-inspector-filter-bar.component.scss new file mode 100644 index 0000000..2f99ed1 --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-filter-bar/uwt-inspector-filter-bar.component.scss @@ -0,0 +1,106 @@ +:host { + display: block; +} + +.uwt-inspector-filter-bar { + display: flex; + flex-direction: column; + gap: var(--uwt-inspector-space-m); +} + +.uwt-inspector-filter-bar__row, +.uwt-inspector-filter-bar__builder, +.uwt-inspector-filter-bar__active { + display: flex; + flex-wrap: wrap; + align-items: flex-end; + gap: var(--uwt-inspector-space-m); +} + +.uwt-inspector-filter-bar__active { + align-items: center; +} + +// The buttons beside the fields are as tall as the fields, so a row reads as +// one line: the kit's small button is 2rem, its inputs 2.375rem. +:host + ::ng-deep + :is(.uwt-inspector-filter-bar__row, .uwt-inspector-filter-bar__builder) + cps-button + .cps-button { + min-height: 2.375rem; +} + +.uwt-inspector-filter-bar__search { + flex: 1 1 18rem; +} + +// The search box has no label above it, so its button centres on it; the +// builder's labelled fields keep theirs level at the bottom. +.uwt-inspector-filter-bar__row { + align-items: center; +} + +.uwt-inspector-filter-bar__path { + flex: 2 1 14rem; +} + +.uwt-inspector-filter-bar__operator { + flex: 1 1 9rem; +} + +.uwt-inspector-filter-bar__value { + flex: 2 1 12rem; +} + +.uwt-inspector-filter-bar__label { + font-size: 0.8125rem; + color: var(--cps-text-secondary); +} + +.uwt-inspector-filter-bar__chips { + display: flex; + flex-wrap: wrap; + gap: var(--uwt-inspector-space-s); + margin: 0; + padding: 0; + list-style: none; +} + +.uwt-inspector-filter-bar__help { + font-size: 0.8125rem; + color: var(--cps-text-primary); + + summary { + width: fit-content; + color: var(--cps-color-calm); + cursor: pointer; + + &:focus-visible { + outline: 2px solid var(--cps-ring-color); + outline-offset: 2px; + } + } + + p, + ul { + max-width: 75ch; + margin: 0.375rem 0 0; + } + + ul { + padding-inline-start: 1.25rem; + } + + code { + font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + } +} + +// In forced colours a solid button loses its fill, and with it its edge. +// Specific enough to beat cps-button's own `border: 0`. +@media (forced-colors: active) { + :host ::ng-deep cps-button button.cps-button.cps-button--solid { + border: 0.0625rem solid ButtonText; + } +} diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-filter-bar/uwt-inspector-filter-bar.component.spec.ts b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-filter-bar/uwt-inspector-filter-bar.component.spec.ts new file mode 100644 index 0000000..ca32e2a --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-filter-bar/uwt-inspector-filter-bar.component.spec.ts @@ -0,0 +1,177 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { UwtInspectorStore } from '../uwt-inspector-store/uwt-inspector-store'; +import { UwtInspectorFilterBarComponent } from './uwt-inspector-filter-bar.component'; + +describe('UwtInspectorFilterBarComponent', () => { + let fixture: ComponentFixture; + let store: UwtInspectorStore; + const bar = () => + fixture.componentInstance as unknown as { + builderOpen: { (): boolean; set(v: boolean): void }; + onPath(v: unknown): void; + onOperator(v: unknown): void; + value: { set(v: string): void }; + add(): void; + canAdd(): boolean; + pathOptions(): { + label: string; + value: string; + info: string; + alias: string; + }[]; + }; + const el = () => fixture.nativeElement as HTMLElement; + const addFilter = () => + Array.from(el().querySelectorAll('button')).find( + (b) => b.textContent?.trim() === 'Add filter' + ); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [UwtInspectorFilterBarComponent, NoopAnimationsModule], + providers: [UwtInspectorStore] + }).compileComponents(); + store = TestBed.inject(UwtInspectorStore); + fixture = TestBed.createComponent(UwtInspectorFilterBarComponent); + fixture.detectChanges(); + }); + + it('should offer fields by name, with the path below, found by either', () => { + expect(bar().pathOptions()).toContainEqual({ + label: 'Forwarded from', + value: 'origin.application', + info: 'origin.application', + alias: 'origin.application' + }); + expect(bar().pathOptions()).toContainEqual({ + label: 'Time handed over', + value: 'capturedAt', + info: 'capturedAt', + alias: 'capturedAt' + }); + }); + + it('should close after adding, ready for the next, with focus back on Add filter', () => { + bar().builderOpen.set(true); + fixture.detectChanges(); + bar().onPath('kind'); + bar().onOperator('exists'); + bar().add(); + fixture.detectChanges(); + TestBed.tick(); + + expect(bar().builderOpen()).toBe(false); + expect(el().querySelector('[aria-label="New filter"]')).toBeNull(); + expect(document.activeElement).toBe(addFilter()); + + bar().builderOpen.set(true); + fixture.detectChanges(); + expect(bar().canAdd()).toBe(false); // nothing carried over + }); + + it('should add on Enter, but not on the Enter a list takes to open or pick', () => { + bar().builderOpen.set(true); + fixture.detectChanges(); + bar().onPath('kind'); + bar().onOperator('equals'); + bar().value.set('bi'); + fixture.detectChanges(); + const group = el().querySelector('[aria-label="New filter"]')!; + const enter = (target: Element) => + target.dispatchEvent( + new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }) + ); + + enter(group.querySelector('cps-select [role="combobox"]')!); + expect(store.filters().fields).toHaveLength(0); + + enter(group.querySelector('cps-input input')!); + expect(store.filters().fields).toHaveLength(1); + }); + + it('should explain how filters work beside the builder', () => { + expect(el().querySelector('details')).toBeNull(); + bar().builderOpen.set(true); + fixture.detectChanges(); + + const help = el().querySelector('details'); + expect(help?.querySelector('summary')?.textContent).toBe( + 'How filters work' + ); + // Not inside the fields' group, where Enter adds the filter. + expect(help?.closest('[aria-label="New filter"]')).toBeNull(); + }); + + it('should need a field, and a value unless checking presence', () => { + bar().builderOpen.set(true); + fixture.detectChanges(); + expect(bar().canAdd()).toBe(false); + + bar().onPath('metadata.theme'); + expect(bar().canAdd()).toBe(false); + bar().value.set('dark'); + expect(bar().canAdd()).toBe(true); + + bar().value.set(''); + bar().onOperator('exists'); + expect(bar().canAdd()).toBe(true); + }); + + it('should name its search field', () => { + const el = fixture.nativeElement as HTMLElement; + expect( + el + .querySelector('.uwt-inspector-search input') + ?.getAttribute('aria-label') + ).toBe('Search events'); + }); + + it('should add a filter as a removable chip with a spoken name', () => { + bar().onPath('metadata.theme'); + bar().onOperator('equals'); + bar().value.set('dark'); + bar().add(); + fixture.detectChanges(); + TestBed.tick(); + + expect(store.filters().fields).toEqual([ + expect.objectContaining({ + path: 'metadata.theme', + operator: 'equals', + value: 'dark' + }) + ]); + const close = fixture.nativeElement.querySelector( + 'cps-chip button' + ) as HTMLButtonElement; + expect(close.getAttribute('aria-label')).toBe( + 'Remove filter metadata.theme equals "dark"' + ); + + close.focus(); + close.click(); + fixture.detectChanges(); + TestBed.tick(); + expect(store.filters().fields).toHaveLength(0); + // The chip gone, focus is on Add filter, not the page. + expect(document.activeElement).toBe(addFilter()); + }); + + it('should clear the text and every filter together', () => { + store.setText('x'); + store.addFilter({ path: 'kind', operator: 'exists', value: '' }); + fixture.detectChanges(); + + const clearBtn = Array.from( + (fixture.nativeElement as HTMLElement).querySelectorAll('button') + ).find((b) => b.textContent?.trim() === 'Clear filters')!; + clearBtn.focus(); + clearBtn.click(); + fixture.detectChanges(); + TestBed.tick(); + + expect(store.filters()).toEqual({ text: '', fields: [] }); + expect(document.activeElement).toBe(addFilter()); + }); +}); diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-filter-bar/uwt-inspector-filter-bar.component.ts b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-filter-bar/uwt-inspector-filter-bar.component.ts new file mode 100644 index 0000000..b9d4763 --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-filter-bar/uwt-inspector-filter-bar.component.ts @@ -0,0 +1,178 @@ +import { + afterNextRender, + ChangeDetectionStrategy, + Component, + computed, + DestroyRef, + ElementRef, + inject, + Injector, + signal, + viewChild +} from '@angular/core'; +import { + CpsAutocompleteComponent, + CpsButtonComponent, + CpsChipComponent, + CpsInputComponent, + CpsSelectComponent +} from 'cps-ui-kit'; +import { UwtInspectorFilterOperator } from '../uwt-inspector.models/uwt-inspector.models'; +import { UwtInspectorStore } from '../uwt-inspector-store/uwt-inspector-store'; +import { + uwtDescribeFilter, + uwtFieldName, + uwtOperatorNeedsValue +} from '../uwt-inspector-filter/uwt-inspector-filter'; + +/** Typing settles for this long before filtering runs, in milliseconds. */ +const SEARCH_DEBOUNCE_MS = 150; + +/** + * The timeline's free-text search, field-filter builder and active + * filters. They apply to every kind of event. + */ +@Component({ + selector: 'uwt-inspector-filter-bar', + imports: [ + CpsInputComponent, + CpsAutocompleteComponent, + CpsSelectComponent, + CpsButtonComponent, + CpsChipComponent + ], + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: './uwt-inspector-filter-bar.component.html', + styleUrl: './uwt-inspector-filter-bar.component.scss' +}) +export class UwtInspectorFilterBarComponent { + protected readonly store = inject(UwtInspectorStore); + + protected readonly filters = this.store.filters; + + protected readonly operators: { + label: string; + value: UwtInspectorFilterOperator; + }[] = [ + { label: 'contains', value: 'contains' }, + { label: 'equals', value: 'equals' }, + { label: 'does not contain', value: 'not-contains' }, + { label: 'exists', value: 'exists' }, + { label: 'is missing', value: 'missing' } + ]; + + protected readonly builderOpen = signal(false); + protected readonly path = signal(''); + protected readonly operator = signal('contains'); + + protected readonly value = signal(''); + + /** Each field by name, its path below it; either finds it. */ + protected readonly pathOptions = computed(() => + this.store.knownPaths().map((path) => { + const name = uwtFieldName(path); + return { + label: name, + value: path, + info: name === path ? '' : path, + alias: path + }; + }) + ); + + protected readonly needsValue = computed(() => + uwtOperatorNeedsValue(this.operator()) + ); + + protected readonly canAdd = computed( + () => !!this.path().trim() && (!this.needsValue() || !!this.value().trim()) + ); + + protected readonly chips = computed(() => + this.filters().fields.map((f) => ({ + id: f.id, + text: uwtDescribeFilter(f) + })) + ); + + protected readonly anyActive = computed(() => { + const f = this.filters(); + return !!f.text.trim() || f.fields.length > 0; + }); + + private readonly injector = inject(Injector); + private readonly addFilterButton = viewChild('addFilter', { + read: ElementRef + }); + + private searchTimer?: ReturnType; + + constructor() { + inject(DestroyRef).onDestroy(() => clearTimeout(this.searchTimer)); + } + + protected onSearch(text: string): void { + clearTimeout(this.searchTimer); + this.searchTimer = setTimeout( + () => this.store.setText(text ?? ''), + SEARCH_DEBOUNCE_MS + ); + } + + protected onPath(value: unknown): void { + this.path.set(typeof value === 'string' ? value : ''); + } + + protected onOperator(value: unknown): void { + const match = this.operators.find((o) => o.value === value); + this.operator.set(match?.value ?? 'contains'); + } + + protected add(): void { + if (!this.canAdd()) { + return; + } + this.store.addFilter({ + path: this.path().trim(), + operator: this.operator(), + value: this.needsValue() ? this.value().trim() : '' + }); + // Done: the builder closes, ready for the next one, and focus goes + // back to the button that opened it. + this.builderOpen.set(false); + this.path.set(''); + this.operator.set('contains'); + this.value.set(''); + this.focusAddFilter(); + } + + /** Enter adds the filter — except in a list, where it opens or picks. */ + protected onEnter(event: Event): void { + const target = event.target as Element | null; + if (!event.defaultPrevented && !target?.closest?.('[role="combobox"]')) { + this.add(); + } + } + + protected remove(id: string): void { + this.store.removeFilter(id); + this.focusAddFilter(); + } + + protected clearAll(): void { + clearTimeout(this.searchTimer); + this.store.clearFilters(); + this.focusAddFilter(); + } + + /** Focus to Add filter, as the control just used goes away. */ + private focusAddFilter(): void { + afterNextRender( + () => + (this.addFilterButton()?.nativeElement as HTMLElement | undefined) + ?.querySelector('button') + ?.focus(), + { injector: this.injector } + ); + } +} diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-filter/uwt-inspector-filter.spec.ts b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-filter/uwt-inspector-filter.spec.ts new file mode 100644 index 0000000..594ba66 --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-filter/uwt-inspector-filter.spec.ts @@ -0,0 +1,259 @@ +import { UwtTelemetryObservedEvent } from '@absaoss-cps/ngx-ui-watchtower'; +import { + UwtInspectorFieldFilter, + UwtInspectorFilterState +} from '../uwt-inspector.models/uwt-inspector.models'; +import { + uwtDescribeFilter, + uwtFieldName, + uwtInspectorSectionOf, + uwtFlattenEvent, + uwtKnownPaths, + uwtMatchesFilters +} from './uwt-inspector-filter'; + +const base = { + sequence: 7, + capturedAt: '2026-01-01T10:00:00.000Z', + destination: 'sink' as const, + origin: { forwarded: false as const } +}; + +const scenario: UwtTelemetryObservedEvent = { + ...base, + kind: 'scenario', + eventType: 'com.uwt.scenario', + payload: { + scenarioId: 'abc-123', + scenarioName: 'load-dashboard', + status: 'failure', + startTime: '2026-01-01T09:59:59.000Z', + delta: 812, + elapsed: 5000, + stepCount: 2, + steps: [ + { name: 'scenario-start', startOffset: 0 }, + { name: 'fetch', startOffset: 10, status: 'success' }, + { name: 'render', startOffset: 400, status: 'failure' } + ], + metadata: { theme: 'Dark', retry: null }, + application: 'app' + } +}; + +function state( + text = '', + fields: Partial[] = [] +): UwtInspectorFilterState { + return { + text, + fields: fields.map((f, i) => ({ + id: String(i), + path: '', + operator: 'contains', + value: '', + ...f + })) + }; +} + +describe('uwtFlattenEvent', () => { + const fields = uwtFlattenEvent(scenario); + const byPath = (p: string) => fields.find((f) => f.path === p); + + it('should expose nested payload fields at their own paths', () => { + expect(byPath('metadata.theme')?.value).toBe('Dark'); + expect(byPath('scenarioName')?.value).toBe('load-dashboard'); + }); + + it('should index array items, with an index-free pattern', () => { + expect(byPath('steps[2].name')).toEqual({ + path: 'steps[2].name', + pattern: 'steps[].name', + value: 'render' + }); + }); + + it('should include the envelope', () => { + expect(byPath('kind')?.value).toBe('scenario'); + expect(byPath('eventType')?.value).toBe('com.uwt.scenario'); + expect(byPath('origin.forwarded')?.value).toBe('false'); + expect(byPath('sequence')?.value).toBe('7'); + }); + + it('should keep null as text, and address empty containers', () => { + expect(byPath('metadata.retry')?.value).toBe('null'); + const empty = uwtFlattenEvent({ + ...base, + kind: 'bi', + eventType: 'com.uwt.bi', + payload: { tags: [], extra: {} } + }); + expect(empty.map((f) => f.path)).toEqual( + expect.arrayContaining(['tags', 'extra']) + ); + }); + + it('should survive a circular payload', () => { + const payload: Record = { a: 1 }; + payload.self = payload; + const event = { + ...base, + kind: 'unknown', + eventType: 'x', + payload + } as unknown as UwtTelemetryObservedEvent; + + const result = uwtFlattenEvent(event); + expect(result.find((f) => f.path === 'self')?.value).toBe('[Circular]'); + }); + + it('should cap the number of fields for a huge payload', () => { + const payload = Object.fromEntries( + Array.from({ length: 5000 }, (_, i) => [`k${i}`, i]) + ); + const result = uwtFlattenEvent({ + ...base, + kind: 'bi', + eventType: 'com.uwt.bi', + payload + }); + expect(result.length).toBeLessThanOrEqual(2000); + // The envelope stays whole, so the event can still be found by kind. + expect(result.find((f) => f.path === 'kind')?.value).toBe('bi'); + expect(result.find((f) => f.path === 'eventType')?.value).toBe( + 'com.uwt.bi' + ); + }); + + it('should show an object used twice at both places, as it is no cycle', () => { + const shared = { theme: 'dark' }; + const result = uwtFlattenEvent({ + ...base, + kind: 'bi', + eventType: 'com.uwt.bi', + payload: { a: shared, b: shared } + }); + expect(result.find((f) => f.path === 'a.theme')?.value).toBe('dark'); + expect(result.find((f) => f.path === 'b.theme')?.value).toBe('dark'); + }); +}); + +describe('uwtMatchesFilters', () => { + const fields = uwtFlattenEvent(scenario); + const matches = (s: UwtInspectorFilterState) => uwtMatchesFilters(fields, s); + + it('should match everything with no filters', () => { + expect(matches(state())).toBe(true); + }); + + it('should match free text anywhere, ignoring case', () => { + expect(matches(state('DASHBOARD'))).toBe(true); + expect(matches(state('dark'))).toBe(true); + expect(matches(state('metadata.the'))).toBe(true); // path text too + expect(matches(state('nowhere'))).toBe(false); + }); + + it('should apply contains, equals and not-contains to one path', () => { + expect(matches(state('', [{ path: 'scenarioName', value: 'dash' }]))).toBe( + true + ); + expect( + matches( + state('', [{ path: 'scenarioName', operator: 'equals', value: 'dash' }]) + ) + ).toBe(false); + expect( + matches( + state('', [ + { path: 'status', operator: 'not-contains', value: 'success' } + ]) + ) + ).toBe(true); + }); + + it('should match any array item by pattern, or one item by index', () => { + expect( + matches(state('', [{ path: 'steps[].name', value: 'render' }])) + ).toBe(true); + expect( + matches(state('', [{ path: 'steps[1].name', value: 'render' }])) + ).toBe(false); + }); + + it('should treat exists and missing as presence checks', () => { + expect( + matches(state('', [{ path: 'metadata.theme', operator: 'exists' }])) + ).toBe(true); + expect( + matches(state('', [{ path: 'error.message', operator: 'missing' }])) + ).toBe(true); + }); + + it('should require every filter and the text together', () => { + expect( + matches( + state('dashboard', [ + { path: 'status', operator: 'equals', value: 'failure' }, + { path: 'metadata.theme', value: 'light' } + ]) + ) + ).toBe(false); + }); + + it('should find nothing for a path that does not exist', () => { + expect(matches(state('', [{ path: 'level', value: 'warn' }]))).toBe(false); + }); +}); + +describe('helpers', () => { + it('should put each kind in its section', () => { + expect(uwtInspectorSectionOf('bi')).toBe('bi'); + expect(uwtInspectorSectionOf('unknown')).toBe('bi'); + expect(uwtInspectorSectionOf('scenario-step')).toBe('scenario'); + expect(uwtInspectorSectionOf('error')).toBe('logging'); + }); + + it('should describe a filter as a short sentence', () => { + expect( + uwtDescribeFilter({ + id: '1', + path: 'level', + operator: 'not-contains', + value: 'log' + }) + ).toBe('level does not contain "log"'); + expect( + uwtDescribeFilter({ + id: '1', + path: 'error', + operator: 'exists', + value: '' + }) + ).toBe('error exists'); + }); + + it('should suggest envelope paths first, then the most common', () => { + const paths = uwtKnownPaths([ + uwtFlattenEvent(scenario), + uwtFlattenEvent(scenario) + ]); + expect(paths.slice(0, 5)).toEqual([ + 'kind', + 'eventType', + 'destination', + 'origin.application', + 'capturedAt' + ]); + expect(paths).toContain('steps[].name'); + expect(new Set(paths).size).toBe(paths.length); + }); + + it("should name the library's own fields, and metadata, for people", () => { + expect(uwtFieldName('eventName')).toBe('Event name'); + expect(uwtFieldName('steps[].name')).toBe('Step name'); + expect(uwtFieldName('metadata.theme')).toBe('Metadata: theme'); + // An app's own field keeps its path. + expect(uwtFieldName('details.cart.items')).toBe('details.cart.items'); + }); +}); diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-filter/uwt-inspector-filter.ts b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-filter/uwt-inspector-filter.ts new file mode 100644 index 0000000..0197397 --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-filter/uwt-inspector-filter.ts @@ -0,0 +1,259 @@ +import { + UwtTelemetryEventKind, + UwtTelemetryObservedEvent +} from '@absaoss-cps/ngx-ui-watchtower'; +import { + UwtInspectorField, + UwtInspectorFieldFilter, + UwtInspectorFilterOperator, + UwtInspectorFilterState, + UwtInspectorSectionId +} from '../uwt-inspector.models/uwt-inspector.models'; + +/** Deeper structures are cut off, so a pathological payload stays cheap. */ +const MAX_DEPTH = 12; +/** Leaves per event considered for filtering. */ +const MAX_FIELDS = 2000; +/** Characters of each value considered for matching. */ +export const MAX_VALUE_CHARS = 2000; + +/** + * Which section an event is shown in. An event forwarded from another + * realm with an unrecognised type is shown with BI, alongside the other + * custom events. + */ +export function uwtInspectorSectionOf( + kind: UwtTelemetryEventKind +): UwtInspectorSectionId { + switch (kind) { + case 'scenario': + case 'scenario-step': + return 'scenario'; + case 'log': + case 'error': + return 'logging'; + default: + return 'bi'; + } +} + +/** + * Every leaf of an event as `path = value` text, done once when the event + * arrives so filtering never walks payloads again. + * + * Payload fields come first, at their own paths (`metadata.theme`, + * `steps[2].name`); the envelope follows (`kind`, `eventType`, + * `destination`, `origin.application`, `sequence`, `capturedAt`), always + * whole: a huge payload gets the room left. Cycles, excessive depth and + * oversized strings are cut short rather than failing. + */ +export function uwtFlattenEvent( + event: UwtTelemetryObservedEvent +): UwtInspectorField[] { + const { payload, ...envelope } = event; + const own = flatten(envelope, MAX_FIELDS); + return [...flatten(payload, MAX_FIELDS - own.length), ...own]; +} + +/** `value`'s leaves, at most `max` of them. */ +function flatten(value: unknown, max: number): UwtInspectorField[] { + const fields: UwtInspectorField[] = []; + // The objects above the one visited: one seen twice elsewhere is shared, + // not circular. + const ancestors = new Set(); + + const visit = (value: unknown, path: string, depth: number): void => { + if (fields.length >= max) { + return; + } + if (value === null || typeof value !== 'object') { + if (value !== undefined) { + push(fields, path, String(value)); + } + return; + } + if (ancestors.has(value)) { + push(fields, path, '[Circular]'); + return; + } + if (depth >= MAX_DEPTH) { + push(fields, path, '[Too deep]'); + return; + } + + const entries: [string, unknown][] = Array.isArray(value) + ? value.map((v, i) => [`[${i}]`, v]) + : Object.entries(value).map(([k, v]) => [path ? `.${k}` : k, v]); + + if (entries.length === 0) { + // Still addressable, so `exists` can find an empty object or array. + push(fields, path, ''); + return; + } + ancestors.add(value); + for (const [suffix, child] of entries) { + visit(child, path + suffix, depth + 1); + } + ancestors.delete(value); + }; + + visit(value, '', 0); + return fields; +} + +function push(fields: UwtInspectorField[], path: string, value: string) { + fields.push({ + path, + pattern: path.replace(/\[\d+\]/g, '[]'), + value: + value.length > MAX_VALUE_CHARS ? value.slice(0, MAX_VALUE_CHARS) : value + }); +} + +/** + * Whether an event's fields satisfy every active filter. + * + * - Free text: case-insensitive, matched against every value and path. + * - A field filter's path matches a field's exact path or its pattern, so + * `steps[].name` covers every step and `steps[2].name` only the third. + * With several matching fields, one is enough. + * - Everything is combined with AND. + */ +export function uwtMatchesFilters( + fields: readonly UwtInspectorField[], + filters: UwtInspectorFilterState +): boolean { + const text = filters.text.trim().toLowerCase(); + if ( + text && + !fields.some( + (f) => + f.value.toLowerCase().includes(text) || + f.path.toLowerCase().includes(text) + ) + ) { + return false; + } + return filters.fields.every((filter) => matchesField(fields, filter)); +} + +function matchesField( + fields: readonly UwtInspectorField[], + filter: UwtInspectorFieldFilter +): boolean { + const path = filter.path.trim().toLowerCase(); + const value = filter.value.trim().toLowerCase(); + const atPath = fields.filter( + (f) => f.path.toLowerCase() === path || f.pattern.toLowerCase() === path + ); + + switch (filter.operator) { + case 'exists': + return atPath.length > 0; + case 'missing': + return atPath.length === 0; + case 'equals': + return atPath.some((f) => f.value.toLowerCase() === value); + case 'not-contains': + return !atPath.some((f) => f.value.toLowerCase().includes(value)); + case 'contains': + default: + return atPath.some((f) => f.value.toLowerCase().includes(value)); + } +} + +/** The library's own fields as people read them; others keep their path. */ +const FIELD_NAMES: Record = { + kind: 'Kind', + eventType: 'Event type', + destination: 'Handed to', + 'origin.application': 'Forwarded from', + capturedAt: 'Time handed over', + eventName: 'Event name', + feature: 'Feature', + scenarioId: 'Scenario id', + scenarioName: 'Scenario', + name: 'Name', + status: 'Status', + delta: 'Duration (ms)', + stepDelta: 'Step duration (ms)', + stepCount: 'Step count', + 'steps[].name': 'Step name', + 'steps[].status': 'Step status', + level: 'Level', + logger: 'Logger', + message: 'Message', + context: 'Context', + correlationId: 'Correlation id', + 'error.name': 'Error name', + 'error.message': 'Error message', + application: 'Application', + environment: 'Environment', + version: 'Version', + sessionId: 'Session id', + userId: 'User id' +}; + +/** + * A field path as people read it: `eventName` is "Event name", and + * `metadata.theme` is "Metadata: theme". Other paths are left as they are. + */ +export function uwtFieldName(path: string): string { + const metadata = /^metadata\.(.+)$/.exec(path); + return FIELD_NAMES[path] ?? (metadata ? `Metadata: ${metadata[1]}` : path); +} + +const OPERATOR_TEXT: Record = { + contains: 'contains', + equals: 'equals', + 'not-contains': 'does not contain', + exists: 'exists', + missing: 'is missing' +}; + +/** A filter as a short sentence, for chips and screen readers. */ +export function uwtDescribeFilter(filter: UwtInspectorFieldFilter): string { + const op = OPERATOR_TEXT[filter.operator]; + return filter.operator === 'exists' || filter.operator === 'missing' + ? `${filter.path} ${op}` + : `${filter.path} ${op} "${filter.value}"`; +} + +/** Whether an operator compares against a value. */ +export function uwtOperatorNeedsValue( + operator: UwtInspectorFilterOperator +): boolean { + return operator !== 'exists' && operator !== 'missing'; +} + +/** + * Field paths to suggest, from everything captured so far: envelope fields + * first, then the rest by how often they occur. + */ +export function uwtKnownPaths( + fieldSets: readonly (readonly UwtInspectorField[])[] +): string[] { + const envelope = [ + 'kind', + 'eventType', + 'destination', + 'origin.application', + 'capturedAt' + ]; + const counts = new Map(); + for (const fields of fieldSets) { + for (const f of new Set(fields.map((x) => x.pattern))) { + if ( + !envelope.includes(f) && + f !== 'sequence' && + f !== 'origin.forwarded' + ) { + counts.set(f, (counts.get(f) ?? 0) + 1); + } + } + } + const rest = [...counts.entries()] + .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])) + .map(([path]) => path); + return [...envelope, ...rest]; +} diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-internal.util/uwt-inspector-internal.util.ts b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-internal.util/uwt-inspector-internal.util.ts new file mode 100644 index 0000000..5675854 --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-internal.util/uwt-inspector-internal.util.ts @@ -0,0 +1,56 @@ +import { isPlatformBrowser } from '@angular/common'; +import { inject, isDevMode, PLATFORM_ID } from '@angular/core'; + +/** + * A local copy of `@absaoss-cps/ngx-ui-watchtower`'s fail-open helpers — + * `uwtSafe`/`uwtSafeVoid`/`uwtIsBrowser`/`uwtIsDevMode`. + * + * Not imported from the main entry point for the same reason as + * `@absaoss-cps/ngx-ui-watchtower/rum`'s copy: ng-packagr fixes each entry point's `rootDir` + * to its own `src`, and the main entry keeps these internal on purpose. + * See `rum/src/lib/uwt-rum-internal.util`. + */ + +/** Runs `fn`, returning `fallback` instead of ever throwing. */ +export function uwtSafe(operation: string, fn: () => T, fallback: T): T { + try { + return fn(); + } catch (error) { + reportSuppressed(operation, error); + return fallback; + } +} + +/** Void-returning form of {@link uwtSafe}. */ +export function uwtSafeVoid(operation: string, fn: () => void): void { + uwtSafe(operation, fn, undefined); +} + +/** `isDevMode()` throws outside an Angular context; this never does. */ +export function uwtIsDevMode(): boolean { + try { + return isDevMode(); + } catch { + return false; + } +} + +/** Whether this runs in a browser. Needs an injection context. */ +export function uwtIsBrowser(): boolean { + return isPlatformBrowser(inject(PLATFORM_ID)); +} + +function reportSuppressed(operation: string, error: unknown): void { + if (!uwtIsDevMode()) { + return; + } + try { + // eslint-disable-next-line no-console + console.error( + `[ngx-ui-watchtower] eventInspector.${operation} failed`, + error + ); + } catch { + // A patched/throwing console must never escape suppression. + } +} diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-json/uwt-inspector-json.component.html b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-json/uwt-inspector-json.component.html new file mode 100644 index 0000000..a0505b4 --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-json/uwt-inspector-json.component.html @@ -0,0 +1,36 @@ +
+
+ + + {{ + copied() === 'copied' + ? 'Payload copied' + : copied() === 'failed' + ? 'Copying failed' + : '' + }} +
+ @if (truncated()) { +

+ Large payload: showing the first {{ shownChars() }} characters. The + download contains all of it. +

+ } +
{{ shown() }}
+
diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-json/uwt-inspector-json.component.scss b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-json/uwt-inspector-json.component.scss new file mode 100644 index 0000000..bd56f4a --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-json/uwt-inspector-json.component.scss @@ -0,0 +1,43 @@ +:host { + display: block; +} + +.uwt-inspector-json { + display: flex; + flex-direction: column; + gap: 0.25rem; +} + +.uwt-inspector-json__actions { + display: flex; + justify-content: flex-end; +} + +.uwt-inspector-json__notice { + margin: 0; + font-size: 0.8125rem; + color: var(--cps-color-warn-darken3); +} + +.uwt-inspector-json__code { + margin: 0; + padding: 0.75rem; + max-height: 24rem; + overflow: auto; + background: var(--cps-surface-muted); + color: var(--cps-text-primary); + border: 1px solid var(--cps-border-color); + border-radius: var(--cps-radius-sm); + font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + font-size: 0.75rem; + line-height: 1.5; + // Wraps inside the row, so a long value never widens the table. + white-space: pre-wrap; + overflow-wrap: anywhere; + tab-size: 2; + + &:focus-visible { + outline: 2px solid var(--cps-ring-color); + outline-offset: 2px; + } +} diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-json/uwt-inspector-json.component.spec.ts b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-json/uwt-inspector-json.component.spec.ts new file mode 100644 index 0000000..f07bda1 --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-json/uwt-inspector-json.component.spec.ts @@ -0,0 +1,153 @@ +import { DOCUMENT } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { UwtInspectorJsonComponent } from './uwt-inspector-json.component'; + +describe('UwtInspectorJsonComponent', () => { + let fixture: ComponentFixture; + const pre = () => fixture.nativeElement.querySelector('pre') as HTMLElement; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [UwtInspectorJsonComponent, NoopAnimationsModule] + }).compileComponents(); + fixture = TestBed.createComponent(UwtInspectorJsonComponent); + }); + + afterEach(() => jest.restoreAllMocks()); + + it('should show the payload as indented JSON in a named, focusable region', () => { + fixture.componentRef.setInput('value', { a: { b: 1 } }); + fixture.componentRef.setInput('label', 'Payload of event #3'); + fixture.detectChanges(); + + expect(JSON.parse(pre().textContent ?? '')).toEqual({ a: { b: 1 } }); + expect(pre().textContent).toContain('\n "a"'); + expect(pre().getAttribute('aria-label')).toBe('Payload of event #3'); + expect(pre().getAttribute('tabindex')).toBe('0'); + }); + + it('should truncate a huge payload in view, and say so', () => { + fixture.componentRef.setInput('value', { big: 'x'.repeat(100_000) }); + fixture.componentRef.setInput('maxChars', 1000); + fixture.detectChanges(); + + expect(pre().textContent!.length).toBe(1000); + expect(fixture.nativeElement.textContent).toContain( + 'showing the first 1,000 characters' + ); + expect(fixture.nativeElement.textContent).toContain( + 'The download contains all of it.' + ); + }); + + it('should show at most 65,536 characters when maxChars is larger', () => { + fixture.componentRef.setInput('value', { big: 'x'.repeat(200_000) }); + fixture.componentRef.setInput('maxChars', 100_000); + fixture.detectChanges(); + + expect(pre().textContent!.length).toBe(64 * 1024); + expect(fixture.nativeElement.textContent).toContain( + 'showing the first 65,536 characters' + ); + }); + + it('should show nothing of a payload when maxChars is negative', () => { + fixture.componentRef.setInput('value', { big: 'x'.repeat(1000) }); + fixture.componentRef.setInput('maxChars', -10); + fixture.detectChanges(); + + expect(pre().textContent).toBe(''); + expect(fixture.nativeElement.textContent).toContain( + 'showing the first 0 characters' + ); + }); + + it('should measure the limit in characters, not bytes', () => { + // 600 Cyrillic letters: about 600 characters of JSON, but 1,200+ bytes. + fixture.componentRef.setInput('value', { text: 'ж'.repeat(600) }); + fixture.componentRef.setInput('maxChars', 1000); + fixture.detectChanges(); + + expect(fixture.nativeElement.textContent).not.toContain('Large payload'); + expect(pre().textContent).toContain('ж'.repeat(600)); + }); + + it('should not cut a truncated view through the middle of a surrogate pair', () => { + const before = '{\n "big": "'; + // Lines the first emoji up so the cut would fall inside it. + const padding = 'x'.repeat(1000 - 1 - before.length); + fixture.componentRef.setInput('value', { + big: padding + '😀'.repeat(10) + }); + fixture.componentRef.setInput('maxChars', 1000); + fixture.detectChanges(); + + const shown = pre().textContent!; + expect(shown.length).toBe(1000 - 1); + expect(shown.endsWith('x')).toBe(true); + }); + + it('should render a circular value instead of failing', () => { + const value: Record = { a: 1 }; + value.self = value; + fixture.componentRef.setInput('value', value); + fixture.detectChanges(); + expect(pre().textContent).toContain('"[Circular]"'); + }); + + it('should name the copy button by its visible label, which says what it copies', () => { + fixture.componentRef.setInput('value', { a: 1 }); + fixture.componentRef.setInput('copyLabel', 'Copy event'); + fixture.detectChanges(); + + const copy = fixture.nativeElement.querySelector('cps-button button'); + expect(copy.textContent.trim()).toBe('Copy event'); + expect(copy.getAttribute('aria-label')).toBe('Copy event'); + }); + + it('should copy the full JSON', async () => { + const writeText = jest.fn(() => Promise.resolve()); + Object.assign(navigator, { clipboard: { writeText } }); + fixture.componentRef.setInput('value', { a: 1 }); + fixture.detectChanges(); + + await ( + fixture.componentInstance as unknown as { copy(): Promise } + ).copy(); + fixture.detectChanges(); + + expect(writeText).toHaveBeenCalledWith('{\n "a": 1\n}'); + expect(fixture.nativeElement.textContent).toContain('Payload copied'); + }); + + it("should copy through its own window's clipboard, as a separate window has its own", async () => { + const frame = document.body.appendChild(document.createElement('iframe')); + const own = jest.fn(() => Promise.resolve()); + const app = jest.fn(() => Promise.resolve()); + Object.assign(frame.contentWindow!.navigator, { + clipboard: { writeText: own } + }); + Object.assign(navigator, { clipboard: { writeText: app } }); + TestBed.resetTestingModule(); + TestBed.configureTestingModule({ + imports: [UwtInspectorJsonComponent, NoopAnimationsModule] + }); + TestBed.overrideComponent(UwtInspectorJsonComponent, { + add: { + providers: [{ provide: DOCUMENT, useValue: frame.contentDocument }] + } + }); + const inWindow = TestBed.createComponent(UwtInspectorJsonComponent); + inWindow.componentRef.setInput('value', { a: 1 }); + inWindow.detectChanges(); + + await ( + inWindow.componentInstance as unknown as { copy(): Promise } + ).copy(); + + expect(own).toHaveBeenCalledWith('{\n "a": 1\n}'); + expect(app).not.toHaveBeenCalled(); + frame.remove(); + }); +}); diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-json/uwt-inspector-json.component.ts b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-json/uwt-inspector-json.component.ts new file mode 100644 index 0000000..bda08fa --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-json/uwt-inspector-json.component.ts @@ -0,0 +1,105 @@ +import { + ChangeDetectionStrategy, + Component, + computed, + DOCUMENT, + inject, + input, + signal +} from '@angular/core'; +import { CpsButtonComponent } from 'cps-ui-kit'; +import { uwtSafeStringify } from '../uwt-inspector-export/uwt-inspector-export'; +import { UWT_DEFAULT_INSPECTOR_CONFIG } from '../uwt-inspector-store/uwt-inspector-store'; + +/** + * The most characters a truncated view shows, even when `maxChars` allows + * more. + */ +const TRUNCATED_VIEW_CHARS = 64 * 1024; + +/** + * The first `length` characters of `text`, one fewer if the cut would split + * a surrogate pair — half an emoji renders as a replacement character. + */ +function cutAt(text: string, length: number): string { + const last = text.charCodeAt(length - 1); + const isHighSurrogate = last >= 0xd800 && last <= 0xdbff; + return text.slice(0, isHighSurrogate ? length - 1 : length); +} + +/** + * A payload as indented JSON, with a copy button. + * + * The only piece of the popup not taken from cps-ui-kit, which has no code + * or JSON display. It uses the kit's colour and radius tokens. + */ +@Component({ + selector: 'uwt-inspector-json', + imports: [CpsButtonComponent], + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: './uwt-inspector-json.component.html', + styleUrl: './uwt-inspector-json.component.scss' +}) +export class UwtInspectorJsonComponent { + /** What to show. */ + readonly value = input.required(); + + /** Accessible name of the scrollable block. */ + readonly label = input('Event payload'); + + /** The copy button's label, naming what it copies. */ + readonly copyLabel = input('Copy JSON'); + + /** JSON length, in characters, above which the view is truncated. */ + readonly maxChars = input(UWT_DEFAULT_INSPECTOR_CONFIG.maxPayloadCharsInView); + + /** + * How many characters a truncated view shows: `maxChars`, capped at + * `TRUNCATED_VIEW_CHARS`. Never negative — `slice` would read a negative + * length as "all but the last few". + */ + private readonly viewChars = computed(() => + Math.max(0, Math.min(TRUNCATED_VIEW_CHARS, this.maxChars())) + ); + + /** How much a truncated view shows, as the notice words it. */ + protected readonly shownChars = computed(() => + this.viewChars().toLocaleString('en-US') + ); + + protected readonly copied = signal<'idle' | 'copied' | 'failed'>('idle'); + + protected readonly full = computed(() => { + try { + return uwtSafeStringify(this.value(), 2) ?? 'undefined'; + } catch (error) { + return `Couldn't render this payload: ${ + error instanceof Error ? error.message : String(error) + }`; + } + }); + + protected readonly truncated = computed( + () => this.full().length > this.maxChars() + ); + + protected readonly shown = computed(() => + this.truncated() ? cutAt(this.full(), this.viewChars()) : this.full() + ); + + /** + * The inspector's own window: in a separate window, the app's clipboard + * refuses, as the app's page doesn't have focus. + */ + private readonly view = inject(DOCUMENT).defaultView; + + protected async copy(): Promise { + try { + await (this.view ?? window).navigator.clipboard.writeText(this.full()); + this.copied.set('copied'); + } catch { + this.copied.set('failed'); + } + setTimeout(() => this.copied.set('idle'), 2000); + } +} diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-logo/uwt-inspector-logo.component.html b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-logo/uwt-inspector-logo.component.html new file mode 100644 index 0000000..701ff4b --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-logo/uwt-inspector-logo.component.html @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-logo/uwt-inspector-logo.component.scss b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-logo/uwt-inspector-logo.component.scss new file mode 100644 index 0000000..fbe148e --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-logo/uwt-inspector-logo.component.scss @@ -0,0 +1,11 @@ +:host { + display: inline-flex; + flex-shrink: 0; + width: 1.75rem; + height: 1.75rem; +} + +svg { + width: 100%; + height: 100%; +} diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-logo/uwt-inspector-logo.component.ts b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-logo/uwt-inspector-logo.component.ts new file mode 100644 index 0000000..8b97210 --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-logo/uwt-inspector-logo.component.ts @@ -0,0 +1,14 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; + +/** + * The Angular UI Watchtower mark, drawn inline: a library can't rely on the + * app serving an asset. Decorative — the title beside it names the window. + */ +@Component({ + selector: 'uwt-inspector-logo', + changeDetection: ChangeDetectionStrategy.OnPush, + host: { 'aria-hidden': 'true' }, + templateUrl: './uwt-inspector-logo.component.html', + styleUrl: './uwt-inspector-logo.component.scss' +}) +export class UwtInspectorLogoComponent {} diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-pane/uwt-inspector-pane.component.html b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-pane/uwt-inspector-pane.component.html new file mode 100644 index 0000000..7dea2c7 --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-pane/uwt-inspector-pane.component.html @@ -0,0 +1,16 @@ + + diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-pane/uwt-inspector-pane.component.scss b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-pane/uwt-inspector-pane.component.scss new file mode 100644 index 0000000..df6ada8 --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-pane/uwt-inspector-pane.component.scss @@ -0,0 +1,104 @@ +// Above the app, below cps-ui-kit's overlays (tooltips use 2000). +:host { + position: fixed; + z-index: 1000; + display: flex; + flex-direction: column; + background-color: #fff; + box-shadow: 0 0.0625rem 0.5rem #0000004d; +} + +:host(.uwt-inspector-pane--left) { + top: 0; + bottom: 0; + left: 0; +} + +:host(.uwt-inspector-pane--right) { + top: 0; + right: 0; + bottom: 0; +} + +:host(.uwt-inspector-pane--bottom) { + right: 0; + bottom: 0; + left: 0; +} + +uwt-inspector { + flex: 1; + min-height: 0; +} + +// A thin strip on the inner edge, with a short grip in its middle. +.uwt-inspector-pane__handle { + position: absolute; + z-index: 1; + touch-action: none; + + &::after { + content: ''; + position: absolute; + border-radius: 0.0625rem; + background-color: var(--cps-color-line-dark); + } + + &:hover, + &:focus-visible { + outline: none; + background-color: var(--cps-color-calm); + } + + // Forced colours drop backgrounds: the grip keeps a system colour, and + // focus shows as an outline. + @media (forced-colors: active) { + &::after { + forced-color-adjust: none; + background-color: CanvasText; + } + + &:focus-visible { + outline: 0.125rem solid Highlight; + outline-offset: -0.125rem; + } + } + + :host(.uwt-inspector-pane--left) &, + :host(.uwt-inspector-pane--right) & { + top: 0; + bottom: 0; + width: 0.375rem; + cursor: ew-resize; + + &::after { + top: calc(50% - 1rem); + left: 0.125rem; + width: 0.125rem; + height: 2rem; + } + } + + :host(.uwt-inspector-pane--left) & { + right: -0.1875rem; + } + + :host(.uwt-inspector-pane--right) & { + left: -0.1875rem; + } + + :host(.uwt-inspector-pane--bottom) & { + top: -0.1875rem; + right: 0; + left: 0; + height: 0.375rem; + cursor: ns-resize; + + &::after { + top: 0.125rem; + left: calc(50% - 1rem); + width: 2rem; + height: 0.125rem; + } + } +} diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-pane/uwt-inspector-pane.component.spec.ts b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-pane/uwt-inspector-pane.component.spec.ts new file mode 100644 index 0000000..7702c21 --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-pane/uwt-inspector-pane.component.spec.ts @@ -0,0 +1,191 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { UWT_TELEMETRY_IDENTITY } from '@absaoss-cps/ngx-ui-watchtower'; +import { CPS_LIVE_ANNOUNCER_SERVICE } from 'cps-ui-kit'; +import { UwtInspectorPaneSide } from '../uwt-inspector.models/uwt-inspector.models'; +import { UWT_INSPECTOR_PREFERENCES_KEY } from '../uwt-inspector-preferences/uwt-inspector-preferences'; +import { UwtInspectorSession } from '../uwt-inspector-session/uwt-inspector-session'; +import { UwtInspectorPaneComponent } from './uwt-inspector-pane.component'; + +// jsdom's viewport is 1024×768, and 1rem is 16px. +describe('UwtInspectorPaneComponent', () => { + let fixture: ComponentFixture; + + const host = () => fixture.nativeElement as HTMLElement; + const handle = () => + host().querySelector('.uwt-inspector-pane__handle') as HTMLElement; + const stored = () => + JSON.parse(localStorage.getItem(UWT_INSPECTOR_PREFERENCES_KEY) ?? '{}'); + + function open(side: UwtInspectorPaneSide): void { + fixture = TestBed.createComponent(UwtInspectorPaneComponent); + fixture.componentRef.setInput('side', side); + fixture.detectChanges(); + } + + function key(key: string): void { + handle().dispatchEvent(new KeyboardEvent('keydown', { key })); + fixture.detectChanges(); + } + + function pointer(type: string, clientX: number, clientY = 0): void { + handle().dispatchEvent( + new MouseEvent(type, { button: 0, clientX, clientY, bubbles: true }) + ); + fixture.detectChanges(); + } + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [UwtInspectorPaneComponent, NoopAnimationsModule], + providers: [ + { + provide: UWT_TELEMETRY_IDENTITY, + useValue: { application: 'app', environment: 'test', version: '1' } + }, + { + provide: CPS_LIVE_ANNOUNCER_SERVICE, + useValue: { announce: jest.fn(), clear: jest.fn() } + } + ] + }).compileComponents(); + }); + + afterEach(() => { + fixture?.destroy(); + TestBed.inject(UwtInspectorSession).end(); + localStorage.removeItem(UWT_INSPECTOR_PREFERENCES_KEY); + document.body.removeAttribute('style'); + }); + + it('should open at 36rem, at most half the width, and push the app aside', () => { + open('right'); + + expect(host().getAttribute('role')).toBe('region'); + expect(host().classList).toContain('uwt-inspector-pane--right'); + expect(host().style.width).toBe('512px'); + expect(document.body.style.paddingRight).toBe('512px'); + expect(host().querySelector('uwt-inspector')).not.toBeNull(); + }); + + it('should publish its size for the app, and take it back when it closes', () => { + const inset = (side: string) => + document.documentElement.style.getPropertyValue( + `--uwt-inspector-inset-${side}` + ); + open('right'); + expect(inset('right')).toBe('512px'); + expect(inset('left')).toBe(''); + expect(inset('bottom')).toBe(''); + + key('ArrowLeft'); + expect(inset('right')).toBe('544px'); + + fixture.destroy(); + expect(inset('right')).toBe(''); + }); + + it('should return to its chosen size when a squeezed window grows back', () => { + const width = (px: number) => { + Object.defineProperty(window, 'innerWidth', { + value: px, + configurable: true, + writable: true + }); + window.dispatchEvent(new Event('resize')); + fixture.detectChanges(); + }; + open('right'); + + try { + width(600); + expect(host().style.width).toBe('480px'); + width(1024); + expect(host().style.width).toBe('512px'); + expect(stored().paneSize).toBeUndefined(); + } finally { + width(1024); + } + }); + + it('should give the app its padding back when it closes', () => { + document.body.style.paddingLeft = '7px'; + open('left'); + expect(document.body.style.paddingLeft).toBe('512px'); + + fixture.destroy(); + expect(document.body.style.paddingLeft).toBe('7px'); + }); + + it('should open at its remembered size, within 30rem and 80% of the width', () => { + localStorage.setItem( + UWT_INSPECTOR_PREFERENCES_KEY, + JSON.stringify({ paneSize: { right: 2000 } }) + ); + open('right'); + expect(host().style.width).toBe('819px'); + + fixture.destroy(); + localStorage.setItem( + UWT_INSPECTOR_PREFERENCES_KEY, + JSON.stringify({ paneSize: { right: 100 } }) + ); + open('right'); + expect(host().style.width).toBe('480px'); + }); + + it('should resize with the arrow keys, and remember the size', () => { + open('right'); + + key('ArrowLeft'); + expect(host().style.width).toBe('544px'); + key('ArrowRight'); + key('ArrowRight'); + expect(host().style.width).toBe('480px'); + expect(stored().paneSize).toEqual({ right: 480 }); + + key('ArrowUp'); + expect(host().style.width).toBe('480px'); + }); + + it('should resize by dragging its inner edge, and remember the size', () => { + open('right'); + + pointer('pointerdown', 512); + pointer('pointermove', 400); + expect(host().style.width).toBe('624px'); + expect(document.body.style.paddingRight).toBe('624px'); + expect(stored().paneSize).toBeUndefined(); + + pointer('pointerup', 400); + expect(stored().paneSize).toEqual({ right: 624 }); + + pointer('pointermove', 100); + expect(host().style.width).toBe('624px'); + }); + + it('should dock at the bottom, 45% high, resized upwards', () => { + open('bottom'); + + expect(host().style.height).toBe('346px'); + expect(document.body.style.paddingBottom).toBe('346px'); + key('ArrowUp'); + expect(host().style.height).toBe('378px'); + pointer('pointerdown', 0, 400); + pointer('pointermove', 0, 300); + expect(host().style.height).toBe('468px'); + }); + + it('should name its edge as a separator with its size and limits', () => { + open('left'); + + expect(handle().getAttribute('role')).toBe('separator'); + expect(handle().getAttribute('aria-orientation')).toBe('vertical'); + expect(handle().getAttribute('aria-label')).toBe( + 'Resize the Event Inspector' + ); + expect(handle().getAttribute('aria-valuenow')).toBe('512'); + expect(handle().getAttribute('aria-valuemin')).toBe('480'); + expect(handle().getAttribute('aria-valuemax')).toBe('819'); + }); +}); diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-pane/uwt-inspector-pane.component.ts b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-pane/uwt-inspector-pane.component.ts new file mode 100644 index 0000000..daaf093 --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-pane/uwt-inspector-pane.component.ts @@ -0,0 +1,185 @@ +import { DOCUMENT } from '@angular/common'; +import { + ChangeDetectionStrategy, + Component, + inject, + input, + OnDestroy, + OnInit, + signal, + viewChild +} from '@angular/core'; +import { UwtInspectorPaneSide } from '../uwt-inspector.models/uwt-inspector.models'; +import { UwtInspectorComponent } from '../uwt-inspector/uwt-inspector.component'; +import { UwtInspectorPreferenceStorage } from '../uwt-inspector-preferences/uwt-inspector-preferences'; + +/** Narrowest a left or right pane gets, in rem, so its tables stay usable. */ +const SIDE_MIN_REM = 30; +/** Lowest a bottom pane gets, in rem. */ +const BOTTOM_MIN_REM = 12; +/** A pane never takes more than this share of the viewport. */ +const MAX_SHARE = 0.8; +/** How far one arrow key press resizes, in rem. */ +const STEP_REM = 2; + +/** + * The inspector docked beside or below the app, like Chrome DevTools. It + * makes room by padding the page's `body` on its side, so content in the + * normal flow moves aside. Elements placed against the window — fixed + * headers, `100vh` shells — can't be moved from here, so it also publishes + * its size as `--uwt-inspector-inset-left`, `-right` or `-bottom` on + * ``, for the app's CSS to use. Resized by dragging or with the arrow + * keys on its inner edge, and remembers its size per side. + */ +@Component({ + selector: 'uwt-inspector-pane', + imports: [UwtInspectorComponent], + changeDetection: ChangeDetectionStrategy.OnPush, + host: { + role: 'region', + 'aria-label': 'Event Inspector', + '[class.uwt-inspector-pane--left]': "side() === 'left'", + '[class.uwt-inspector-pane--right]': "side() === 'right'", + '[class.uwt-inspector-pane--bottom]': "side() === 'bottom'", + '[style.width.px]': "side() === 'bottom' ? null : size()", + '[style.height.px]': "side() === 'bottom' ? size() : null", + '(window:resize)': 'resize(chosen)' + }, + templateUrl: './uwt-inspector-pane.component.html', + styleUrl: './uwt-inspector-pane.component.scss' +}) +export class UwtInspectorPaneComponent implements OnInit, OnDestroy { + readonly side = input.required(); + + /** The inspector inside, for focus. */ + readonly content = viewChild.required(UwtInspectorComponent); + + private readonly document = inject(DOCUMENT); + private readonly preferences = inject(UwtInspectorPreferenceStorage); + + protected readonly size = signal(0); + protected readonly limits = signal({ min: 0, max: 0 }); + + /** The size last chosen, which a smaller window squeezes only for a while. */ + protected chosen = 0; + + private padding = ''; + private paddingBefore = ''; + private inset = ''; + private dragging = false; + + ngOnInit(): void { + this.padding = `padding-${this.side()}`; + this.paddingBefore = this.document.body.style.getPropertyValue( + this.padding + ); + this.inset = `--uwt-inspector-inset-${this.side()}`; + this.chosen = + this.preferences.read().paneSize[this.side()] ?? this.defaultSize(); + this.resize(this.chosen); + } + + ngOnDestroy(): void { + this.document.body.style.setProperty(this.padding, this.paddingBefore); + this.document.documentElement.style.removeProperty(this.inset); + } + + /** Sets the size within its limits, and makes room for it. */ + protected resize(size: number): void { + const rem = this.rem(); + const bottom = this.side() === 'bottom'; + const view = this.document.defaultView; + const extent = (bottom ? view?.innerHeight : view?.innerWidth) ?? 0; + const min = (bottom ? BOTTOM_MIN_REM : SIDE_MIN_REM) * rem; + const max = Math.max(extent * MAX_SHARE, 0); + const clamped = Math.round(Math.min(max, Math.max(min, size))); + this.limits.set({ + min: Math.round(Math.min(min, max)), + max: Math.round(max) + }); + this.size.set(clamped); + this.document.body.style.setProperty(this.padding, `${clamped}px`); + this.document.documentElement.style.setProperty(this.inset, `${clamped}px`); + } + + protected startDrag(event: PointerEvent): void { + if (event.button !== 0) { + return; + } + event.preventDefault(); + (event.target as HTMLElement).setPointerCapture?.(event.pointerId); + this.dragging = true; + } + + protected drag(event: PointerEvent): void { + if (!this.dragging) { + return; + } + const view = this.document.defaultView; + const side = this.side(); + this.choose( + side === 'left' + ? event.clientX + : side === 'right' + ? (view?.innerWidth ?? 0) - event.clientX + : (view?.innerHeight ?? 0) - event.clientY + ); + } + + protected endDrag(): void { + if (this.dragging) { + this.dragging = false; + this.save(); + } + } + + /** Arrow keys toward the app grow the pane; away from it, shrink it. */ + protected resizeByKey(event: KeyboardEvent): void { + const grow = { left: 'ArrowRight', right: 'ArrowLeft', bottom: 'ArrowUp' }; + const shrink = { + left: 'ArrowLeft', + right: 'ArrowRight', + bottom: 'ArrowDown' + }; + const side = this.side(); + if (event.key !== grow[side] && event.key !== shrink[side]) { + return; + } + event.preventDefault(); + const step = (event.key === grow[side] ? 1 : -1) * STEP_REM * this.rem(); + this.choose(this.size() + step); + this.save(); + } + + /** Resizes on the user's behalf, and keeps that as the size to return to. */ + private choose(size: number): void { + this.resize(size); + this.chosen = this.size(); + } + + private save(): void { + this.preferences.update({ + paneSize: { + ...this.preferences.read().paneSize, + [this.side()]: this.size() + } + }); + } + + private defaultSize(): number { + const view = this.document.defaultView; + return this.side() === 'bottom' + ? (view?.innerHeight ?? 0) * 0.45 + : Math.min(36 * this.rem(), (view?.innerWidth ?? 0) * 0.5); + } + + private rem(): number { + const view = this.document.defaultView; + const size = view + ? parseFloat( + view.getComputedStyle(this.document.documentElement).fontSize + ) + : NaN; + return Number.isFinite(size) && size > 0 ? size : 16; + } +} diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-preferences/uwt-inspector-preferences.spec.ts b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-preferences/uwt-inspector-preferences.spec.ts new file mode 100644 index 0000000..480105b --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-preferences/uwt-inspector-preferences.spec.ts @@ -0,0 +1,96 @@ +import { TestBed } from '@angular/core/testing'; +import { + UWT_INSPECTOR_PREFERENCES_KEY, + UwtInspectorPreferenceStorage +} from './uwt-inspector-preferences'; + +describe('UwtInspectorPreferenceStorage', () => { + let storage: UwtInspectorPreferenceStorage; + const store = (value: unknown) => + localStorage.setItem( + UWT_INSPECTOR_PREFERENCES_KEY, + typeof value === 'string' ? value : JSON.stringify(value) + ); + + beforeEach(() => { + storage = TestBed.inject(UwtInspectorPreferenceStorage); + }); + + afterEach(() => { + localStorage.removeItem(UWT_INSPECTOR_PREFERENCES_KEY); + jest.restoreAllMocks(); + }); + + it('should default to an opaque dialog, with nothing remembered', () => { + expect(storage.read()).toEqual({ + dock: 'dialog', + transparency: 0, + paneSize: {} + }); + }); + + it('should read back what was stored', () => { + const preferences = { + dock: 'bottom', + transparency: 25, + paneSize: { left: 500, right: 640, bottom: 300 }, + window: { width: 1000, height: 700, left: -1200, top: 40 } + }; + store(preferences); + + expect(storage.read()).toEqual(preferences); + }); + + it('should bring a transparency above the 30% cap down to it', () => { + store({ transparency: 70 }); + + expect(storage.read().transparency).toBe(30); + }); + + it.each([ + ['malformed JSON', '{not json'], + ['not an object', '"right"'], + [ + 'invalid values', + { + dock: 'top', + transparency: -5, + paneSize: { left: -5, right: 'wide', bottom: Infinity }, + window: { width: 0, height: 700, left: 0, top: 0 } + } + ] + ])('should fall back to the defaults for %s', (_label, value) => { + store(value); + + expect(storage.read()).toEqual({ + dock: 'dialog', + transparency: 0, + paneSize: {} + }); + }); + + it('should replace only what it is given', () => { + store({ dock: 'left', transparency: 20, paneSize: { left: 500 } }); + + storage.update({ dock: 'window' }); + + expect(storage.read()).toEqual({ + dock: 'window', + transparency: 20, + paneSize: { left: 500 } + }); + }); + + it('should carry on when storage throws', () => { + jest.spyOn(console, 'error').mockImplementation(() => undefined); + jest.spyOn(Storage.prototype, 'getItem').mockImplementation(() => { + throw new Error('blocked'); + }); + jest.spyOn(Storage.prototype, 'setItem').mockImplementation(() => { + throw new Error('blocked'); + }); + + expect(() => storage.update({ dock: 'right' })).not.toThrow(); + expect(storage.read().dock).toBe('dialog'); + }); +}); diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-preferences/uwt-inspector-preferences.ts b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-preferences/uwt-inspector-preferences.ts new file mode 100644 index 0000000..69deed6 --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-preferences/uwt-inspector-preferences.ts @@ -0,0 +1,127 @@ +import { DOCUMENT } from '@angular/common'; +import { inject, Injectable } from '@angular/core'; +import { + UWT_INSPECTOR_DOCKS, + UwtInspectorDock, + UwtInspectorPaneSide +} from '../uwt-inspector.models/uwt-inspector.models'; +import { + uwtSafe, + uwtSafeVoid +} from '../uwt-inspector-internal.util/uwt-inspector-internal.util'; + +/** + * How see-through the dialog can get, in percent. Text fades with it, so + * past this it gets hard to read. + */ +export const UWT_INSPECTOR_MAX_TRANSPARENCY = 30; + +/** Where this browser remembers the Event Inspector's settings. */ +export const UWT_INSPECTOR_PREFERENCES_KEY = 'ngx-ui-watchtower.eventInspector'; + +/** The separate window's size and position, in CSS pixels. */ +export interface UwtInspectorWindowGeometry { + width: number; + height: number; + left: number; + top: number; +} + +/** What the Event Inspector remembers between openings, in this browser. */ +export interface UwtInspectorPreferences { + dock: UwtInspectorDock; + /** The dialog's transparency, in percent. */ + transparency: number; + /** Each pane's width (left, right) or height (bottom), in pixels. */ + paneSize: Partial>; + window?: UwtInspectorWindowGeometry; +} + +const PANE_SIDES: readonly UwtInspectorPaneSide[] = ['left', 'right', 'bottom']; + +/** + * Reads and writes {@link UwtInspectorPreferences} as one `localStorage` + * entry. Anything missing or invalid reads as its default, and storage + * that throws — a private window, blocked site data — is ignored. + */ +@Injectable({ providedIn: 'root' }) +export class UwtInspectorPreferenceStorage { + private readonly document = inject(DOCUMENT); + + read(): UwtInspectorPreferences { + const stored = uwtSafe( + 'preferences.read', + () => + this.document.defaultView?.localStorage.getItem( + UWT_INSPECTOR_PREFERENCES_KEY + ), + undefined + ); + return sanitize(parse(stored)); + } + + /** Replaces the given preferences, keeping the others. */ + update(changes: Partial): void { + uwtSafeVoid('preferences.write', () => + this.document.defaultView?.localStorage.setItem( + UWT_INSPECTOR_PREFERENCES_KEY, + JSON.stringify({ ...this.read(), ...changes }) + ) + ); + } +} + +function parse(json: string | null | undefined): unknown { + try { + return json ? JSON.parse(json) : undefined; + } catch { + return undefined; + } +} + +function sanitize(raw: unknown): UwtInspectorPreferences { + const value = isRecord(raw) ? raw : {}; + const dock = UWT_INSPECTOR_DOCKS.find((d) => d === value.dock); + const transparency = value.transparency; + const paneSize: UwtInspectorPreferences['paneSize'] = {}; + const sizes = isRecord(value.paneSize) ? value.paneSize : {}; + for (const side of PANE_SIDES) { + if (isPositive(sizes[side])) { + paneSize[side] = sizes[side]; + } + } + const geometry = value.window; + const window = + isRecord(geometry) && + isPositive(geometry.width) && + isPositive(geometry.height) && + Number.isFinite(geometry.left) && + Number.isFinite(geometry.top) + ? (geometry as unknown as UwtInspectorWindowGeometry) + : undefined; + + return { + dock: dock ?? 'dialog', + transparency: + typeof transparency === 'number' && transparency >= 0 + ? Math.min(transparency, UWT_INSPECTOR_MAX_TRANSPARENCY) + : 0, + paneSize, + ...(window && { + window: { + width: window.width, + height: window.height, + left: window.left, + top: window.top + } + }) + }; +} + +function isRecord(value: unknown): value is Record { + return typeof value === 'object' && value !== null; +} + +function isPositive(value: unknown): value is number { + return typeof value === 'number' && Number.isFinite(value) && value > 0; +} diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-ring/uwt-inspector-ring.spec.ts b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-ring/uwt-inspector-ring.spec.ts new file mode 100644 index 0000000..3e28a3a --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-ring/uwt-inspector-ring.spec.ts @@ -0,0 +1,101 @@ +import { TestBed } from '@angular/core/testing'; +import { + UwtTelemetryMonitor, + UwtTelemetryPublishInput +} from '@absaoss-cps/ngx-ui-watchtower'; +import { UwtInspectorRing } from './uwt-inspector-ring'; + +const bi = (name: string): UwtTelemetryPublishInput => ({ + kind: 'bi', + eventType: 'com.uwt.bi', + payload: { eventName: name, eventTime: '', application: 'app' }, + destination: 'sink', + origin: { forwarded: false } +}); + +const log = (message: string): UwtTelemetryPublishInput => ({ + kind: 'log', + payload: { + timestamp: '', + level: 'log', + message, + application: 'app', + environment: 'test', + version: '1' + }, + destination: 'log-provider', + origin: { forwarded: false } +}); + +describe('UwtInspectorRing', () => { + let monitor: UwtTelemetryMonitor; + let ring: UwtInspectorRing; + + const names = () => + ring + .snapshot() + .events.map((e) => + e.kind === 'bi' + ? (e.payload as { eventName: string }).eventName + : (e.payload as { message: string }).message + ); + + beforeEach(() => { + TestBed.resetTestingModule(); + monitor = TestBed.inject(UwtTelemetryMonitor); + ring = TestBed.inject(UwtInspectorRing); + }); + + it('should observe nothing until started, nor when asked for none', () => { + expect(monitor.observed).toBe(false); + ring.start(0); + expect(monitor.observed).toBe(false); + + ring.start(5); + expect(monitor.observed).toBe(true); + }); + + it('should keep the latest of each kind, oldest first, counting drops', () => { + ring.start(2); + ['a', 'b', 'c'].forEach((n) => monitor.publish(bi(n))); + monitor.publish(log('kept')); + + expect(names()).toEqual(['b', 'c', 'kept']); + expect(ring.snapshot().dropped).toEqual({ bi: 1, scenario: 0, logging: 0 }); + }); + + it('should start once, keeping its first limit', () => { + ring.start(1); + ring.start(5); + ['a', 'b'].forEach((n) => monitor.publish(bi(n))); + + expect(names()).toEqual(['b']); + }); + + it('should say when it began, and begin again when cleared', () => { + expect(ring.snapshot().startedAt).toBeUndefined(); + jest.useFakeTimers({ now: new Date('2026-01-01T09:00:00.000Z') }); + ring.start(5); + expect(ring.snapshot().startedAt).toBe('2026-01-01T09:00:00.000Z'); + + monitor.publish(bi('a')); + jest.setSystemTime(new Date('2026-01-01T09:30:00.000Z')); + ring.clear(); + jest.useRealTimers(); + + expect(ring.snapshot()).toEqual({ + events: [], + dropped: { bi: 0, scenario: 0, logging: 0 }, + startedAt: '2026-01-01T09:30:00.000Z' + }); + // Still running. + monitor.publish(bi('b')); + expect(names()).toEqual(['b']); + }); + + it('should stop observing when the app is destroyed', () => { + ring.start(5); + TestBed.resetTestingModule(); + expect(monitor.observed).toBe(false); + }); +}); diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-ring/uwt-inspector-ring.ts b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-ring/uwt-inspector-ring.ts new file mode 100644 index 0000000..0f4faea --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-ring/uwt-inspector-ring.ts @@ -0,0 +1,86 @@ +import { DestroyRef, inject, Injectable } from '@angular/core'; +import { Subscription } from 'rxjs'; +import { + UwtTelemetryMonitor, + UwtTelemetryObservedEvent +} from '@absaoss-cps/ngx-ui-watchtower'; +import { UwtInspectorSectionId } from '../uwt-inspector.models/uwt-inspector.models'; +import { uwtInspectorSectionOf } from '../uwt-inspector-filter/uwt-inspector-filter'; + +/** What the ring held when the popup asked. */ +export interface UwtInspectorRingSnapshot { + /** Every kind's events, oldest first. */ + events: UwtTelemetryObservedEvent[]; + /** Events of each kind dropped to stay within the limit. */ + dropped: Record; + /** When it began keeping them: the app's start, or the last clear. */ + startedAt?: string; +} + +/** + * The latest events of each kind from the app's start, for + * `captureBeforeOpen`: the popup adopts them when it opens, so it shows what + * happened before. Started by `UwtEventInspectorService` at startup, and + * runs for the app's lifetime; closing the popup leaves it running. + */ +@Injectable({ providedIn: 'root' }) +export class UwtInspectorRing { + private readonly monitor = inject(UwtTelemetryMonitor); + + private events = empty(() => []); + private dropped = empty(() => 0); + private limit = 0; + private startedAt?: string; + private subscription?: Subscription; + + constructor() { + inject(DestroyRef).onDestroy(() => this.subscription?.unsubscribe()); + } + + /** Starts keeping up to `limit` events of each kind; once only. */ + start(limit: number): void { + if (this.subscription || !(limit >= 1)) { + return; + } + this.limit = Math.floor(limit); + this.startedAt = new Date().toISOString(); + this.subscription = this.monitor.events$.subscribe((event) => + this.keep(event) + ); + } + + snapshot(): UwtInspectorRingSnapshot { + return { + events: [ + ...this.events.bi, + ...this.events.scenario, + ...this.events.logging + ].sort((a, b) => a.sequence - b.sequence), + dropped: { ...this.dropped }, + startedAt: this.startedAt + }; + } + + /** Empties it, as the popup's Clear does; it keeps running. */ + clear(): void { + this.events = empty(() => []); + this.dropped = empty(() => 0); + if (this.subscription) { + this.startedAt = new Date().toISOString(); + } + } + + private keep(event: UwtTelemetryObservedEvent): void { + const id = uwtInspectorSectionOf(event.kind); + const kept = this.events[id]; + kept.push(event); + if (kept.length > this.limit) { + kept.shift(); + this.dropped[id]++; + } + } +} + +function empty(make: () => T): Record { + return { bi: make(), scenario: make(), logging: make() }; +} diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-session/uwt-inspector-session.spec.ts b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-session/uwt-inspector-session.spec.ts new file mode 100644 index 0000000..707bbab --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-session/uwt-inspector-session.spec.ts @@ -0,0 +1,28 @@ +import { TestBed } from '@angular/core/testing'; +import { UwtTelemetryMonitor } from '@absaoss-cps/ngx-ui-watchtower'; +import { UwtInspectorSession } from './uwt-inspector-session'; + +describe('UwtInspectorSession', () => { + it('should start capturing on first use, and keep one history', () => { + const session = TestBed.inject(UwtInspectorSession); + const monitor = TestBed.inject(UwtTelemetryMonitor); + expect(monitor.observed).toBe(false); + + const store = session.store; + + expect(monitor.observed).toBe(true); + expect(session.store).toBe(store); + session.end(); + }); + + it('should discard the history and stop capturing when it ends', () => { + const session = TestBed.inject(UwtInspectorSession); + const first = session.store; + + session.end(); + + expect(TestBed.inject(UwtTelemetryMonitor).observed).toBe(false); + expect(session.store).not.toBe(first); + session.end(); + }); +}); diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-session/uwt-inspector-session.ts b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-session/uwt-inspector-session.ts new file mode 100644 index 0000000..21a4b96 --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-session/uwt-inspector-session.ts @@ -0,0 +1,58 @@ +import { + createEnvironmentInjector, + EnvironmentInjector, + inject, + Injectable, + Signal +} from '@angular/core'; +import { UwtInspectorDock } from '../uwt-inspector.models/uwt-inspector.models'; +import { UwtInspectorStore } from '../uwt-inspector-store/uwt-inspector-store'; + +/** One way of showing the inspector: a dialog, a pane or a window. */ +export interface UwtInspectorHost { + /** Where it shows the inspector. */ + readonly dock: UwtInspectorDock; + /** Moves focus into the inspector. */ + focus(): void; + /** Whether keyboard focus is inside the inspector. */ + containsFocus(): boolean; + /** Takes the inspector off the screen; the session carries on. */ + destroy(): void; +} + +/** What the inspector's own controls ask for: another dock, or closing. */ +export interface UwtInspectorControls { + setDock(dock: UwtInspectorDock): void; + close(): void; + /** Whether the app's window is wide enough for a pane beside it. */ + readonly panesFit: Signal; +} + +/** + * One opening of the Event Inspector, from open to close, whichever docks + * it moves between. Holds the captured history, so moving the inspector + * keeps its events, filters and pause. + */ +@Injectable({ providedIn: 'root' }) +export class UwtInspectorSession { + private readonly parent = inject(EnvironmentInjector); + private injector?: EnvironmentInjector; + + /** Set by `UwtEventInspectorService`, which shows the inspector. */ + controls?: UwtInspectorControls; + + /** The captured history, started on first use. */ + get store(): UwtInspectorStore { + this.injector ??= createEnvironmentInjector( + [UwtInspectorStore], + this.parent + ); + return this.injector.get(UwtInspectorStore); + } + + /** Discards the history and stops capturing. */ + end(): void { + this.injector?.destroy(); + this.injector = undefined; + } +} diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-shortcut/uwt-inspector-shortcut.spec.ts b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-shortcut/uwt-inspector-shortcut.spec.ts new file mode 100644 index 0000000..4a4ae6b --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-shortcut/uwt-inspector-shortcut.spec.ts @@ -0,0 +1,100 @@ +import { + UWT_DEFAULT_INSPECTOR_SHORTCUTS, + uwtMatchesShortcut +} from './uwt-inspector-shortcut'; + +type Mods = Partial< + Pick +>; + +function key( + code: string, + mods: Mods, + keyChar = 'I', + altGraph = false, + target: EventTarget | null = null +) { + const event = new KeyboardEvent('keydown', { code, key: keyChar, ...mods }); + Object.defineProperty(event, 'getModifierState', { + value: (k: string) => k === 'AltGraph' && altGraph + }); + Object.defineProperty(event, 'target', { value: target }); + return event; +} + +const [DEFAULT] = UWT_DEFAULT_INSPECTOR_SHORTCUTS; +const shift = { shiftKey: true }; +const CUSTOM = { code: 'Digit8', ctrl: true, alt: true, shift: true }; +const customMods = { ctrlKey: true, altKey: true, shiftKey: true }; + +describe('uwtMatchesShortcut', () => { + it('should match Shift+I pressed right after Shift+E', () => { + expect(uwtMatchesShortcut(key('KeyI', shift), DEFAULT, 'KeyE')).toBe(true); + }); + + it.each([ + ['nothing before', undefined], + ['another key before', 'KeyX'], + ['Shift pressed again in between', 'ShiftLeft'] + ])('should not match Shift+I with %s', (_label, previous) => { + expect(uwtMatchesShortcut(key('KeyI', shift), DEFAULT, previous)).toBe( + false + ); + }); + + it.each([ + ['Shift missing', {}], + ['an extra modifier', { ...shift, ctrlKey: true }] + ])('should not match with %s', (_label, mods) => { + expect(uwtMatchesShortcut(key('KeyI', mods), DEFAULT, 'KeyE')).toBe(false); + }); + + it.each([ + ['an input', 'input'], + ['a text area', 'textarea'] + ])('should not match while typing in %s', (_label, tag) => { + const field = document.createElement(tag); + expect( + uwtMatchesShortcut(key('KeyI', shift, 'I', false, field), DEFAULT, 'KeyE') + ).toBe(false); + }); + + it('should match a shortcut with Ctrl in a text field, where it types nothing', () => { + const field = document.createElement('input'); + expect( + uwtMatchesShortcut(key('Digit8', customMods, '*', false, field), CUSTOM) + ).toBe(true); + }); + + it('should match a key that types nothing in a text field', () => { + const field = document.createElement('input'); + expect( + uwtMatchesShortcut(key('F9', {}, 'F9', false, field), { code: 'F9' }) + ).toBe(true); + }); + + it('should match the physical key, whatever character it produces', () => { + // On macOS, ⇧⌥8 produces "°" — only the physical key is reliable. + const mac = { code: 'Digit8', shift: true, alt: true, meta: true }; + const mods = { shiftKey: true, altKey: true, metaKey: true }; + expect(uwtMatchesShortcut(key('Digit8', mods, '°'), mac)).toBe(true); + expect(uwtMatchesShortcut(key('Numpad8', mods, '°'), mac)).toBe(false); + }); + + it('should ignore a held-down repeat', () => { + expect( + uwtMatchesShortcut( + key('KeyI', { ...shift, repeat: true }), + DEFAULT, + 'KeyE' + ) + ).toBe(false); + }); + + it('should not fire while AltGr is producing a character', () => { + // AltGr reports as Ctrl+Alt on many European Windows layouts. + expect( + uwtMatchesShortcut(key('Digit8', customMods, '8', true), CUSTOM) + ).toBe(false); + }); +}); diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-shortcut/uwt-inspector-shortcut.ts b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-shortcut/uwt-inspector-shortcut.ts new file mode 100644 index 0000000..9e9ca35 --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-shortcut/uwt-inspector-shortcut.ts @@ -0,0 +1,70 @@ +import { UwtInspectorShortcut } from '../uwt-inspector.models/uwt-inspector.models'; + +/** + * The default shortcut, the same on every platform: Shift+E+I — hold Shift, + * press E, then I — for Event Inspector. + * + * No browser binds it. It types text, so it is ignored in text fields: + * typing a capital "EI" there never opens the inspector. + * + * @group Utils + */ +export const UWT_DEFAULT_INSPECTOR_SHORTCUTS: readonly UwtInspectorShortcut[] = + [{ code: 'KeyI', after: 'KeyE', shift: true }]; + +/** + * Whether `event` is exactly `shortcut`, given the key pressed just before + * it (`previous`, a `KeyboardEvent.code`). + * + * - The physical key is compared (`code`), never the produced character — + * Option turns 8 into `•` on macOS, and Shift turns it into `*`. + * - Every modifier must match; one left out must be up. + * - Held-down repeats don't count. + * - AltGr is rejected. On many European Windows layouts it reports as + * Ctrl+Alt, and typing an AltGr character must never open the popup. + * - Without Ctrl or ⌘, a key that types a character doesn't count in a text + * field, even one inside a shadow root. + */ +export function uwtMatchesShortcut( + event: KeyboardEvent, + shortcut: UwtInspectorShortcut, + previous?: string +): boolean { + if (event.repeat || event.code !== shortcut.code) { + return false; + } + if (shortcut.after !== undefined && previous !== shortcut.after) { + return false; + } + if (event.getModifierState?.('AltGraph')) { + return false; + } + if (!shortcut.ctrl && !shortcut.meta && typesIntoField(event)) { + return false; + } + return ( + event.ctrlKey === !!shortcut.ctrl && + event.altKey === !!shortcut.alt && + event.shiftKey === !!shortcut.shift && + event.metaKey === !!shortcut.meta + ); +} + +/** + * Whether `event` types a character — or starts one, as a dead key or an + * IME does — into an input, a text area or an editor. Inside a shadow root, + * `target` is only its host, so the path's first element is read instead. + */ +function typesIntoField(event: KeyboardEvent): boolean { + const key = event.key ?? ''; + if (key.length !== 1 && key !== 'Dead' && key !== 'Process') { + return false; + } + const element = (event.composedPath?.()[0] ?? + event.target) as HTMLElement | null; + return ( + !!element && + (element.isContentEditable || + ['INPUT', 'TEXTAREA', 'SELECT'].includes(element.tagName)) + ); +} diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-store/uwt-inspector-store.spec.ts b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-store/uwt-inspector-store.spec.ts new file mode 100644 index 0000000..3c2e247 --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-store/uwt-inspector-store.spec.ts @@ -0,0 +1,324 @@ +import { Injector } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; +import { + UwtTelemetryMonitor, + UwtTelemetryPublishInput +} from '@absaoss-cps/ngx-ui-watchtower'; +import { UWT_EVENT_INSPECTOR_CONFIG } from '../uwt-inspector.models/uwt-inspector.models'; +import { UwtInspectorRing } from '../uwt-inspector-ring/uwt-inspector-ring'; +import { + UWT_INSPECTOR_FLUSH_MS, + UwtInspectorStore +} from './uwt-inspector-store'; + +const bi = (name: string): UwtTelemetryPublishInput => ({ + kind: 'bi', + eventType: 'com.uwt.bi', + payload: { + eventName: name, + eventTime: '2026-01-01T00:00:00.000Z', + application: 'app' + }, + destination: 'sink', + origin: { forwarded: false } +}); + +const log = (message: string): UwtTelemetryPublishInput => ({ + kind: 'log', + payload: { + timestamp: '2026-01-01T00:00:00.000Z', + level: 'warn', + message, + logger: 'checkout', + application: 'app', + environment: 'test', + version: '1' + }, + destination: 'log-provider', + origin: { forwarded: false } +}); + +describe('UwtInspectorStore', () => { + let monitor: UwtTelemetryMonitor; + let injector: Injector; + + function createStore(max = 500): UwtInspectorStore { + injector = Injector.create({ + providers: [ + UwtInspectorStore, + { + provide: UWT_EVENT_INSPECTOR_CONFIG, + useValue: { maxEventsPerSection: max } + } + ], + parent: TestBed.inject(Injector) + }); + return injector.get(UwtInspectorStore); + } + + const flush = () => jest.advanceTimersByTime(UWT_INSPECTOR_FLUSH_MS); + const names = (store: UwtInspectorStore) => + store + .entries() + .bi.map((e) => (e.event.payload as { eventName: string }).eventName); + + beforeEach(() => { + jest.useFakeTimers(); + TestBed.resetTestingModule(); + monitor = TestBed.inject(UwtTelemetryMonitor); + }); + + function destroy(): void { + const target = injector as unknown as { + destroy(): void; + destroyed: boolean; + }; + if (target && !target.destroyed) { + target.destroy(); + } + } + + afterEach(() => { + destroy(); + jest.useRealTimers(); + }); + + it('should start empty, and ignore everything sent before it existed', () => { + monitor.publish(bi('before')); // nobody observing yet + const store = createStore(); + flush(); + + expect(store.total()).toBe(0); + }); + + it('should start with what the ring kept from before: at once, not as new, nor as received', () => { + jest.setSystemTime(new Date('2026-01-01T09:00:00.000Z')); + const ring = TestBed.inject(UwtInspectorRing); + ring.start(2); + ['a', 'b', 'c'].forEach((n) => monitor.publish(bi(n))); + jest.setSystemTime(new Date('2026-01-01T09:05:00.000Z')); + + const store = createStore(); + + expect(names(store)).toEqual(['c', 'b']); + expect(store.fresh().size).toBe(0); + expect(store.received().bi).toBe(0); + expect(store.startedAt()).toBe('2026-01-01T09:00:00.000Z'); + expect(store.count('bi')).toEqual({ captured: 2, droppedOldest: 1 }); + + // Clearing empties the ring too, so reopening won't bring them back. + store.clear(); + expect(ring.snapshot().events).toHaveLength(0); + }); + + it('should batch arrivals into one update per flush', () => { + const store = createStore(); + monitor.publish(bi('a')); + monitor.publish(bi('b')); + + expect(store.total()).toBe(0); // not on screen yet + flush(); + expect(names(store)).toEqual(['b', 'a']); // newest first + }); + + it('should keep each kind apart', () => { + const store = createStore(); + monitor.publish(bi('a')); + monitor.publish(log('careful')); + flush(); + + expect(store.entries().bi).toHaveLength(1); + expect(store.entries().logging).toHaveLength(1); + expect(store.entries().scenario).toHaveLength(0); + }); + + it('should keep at most the configured number per kind, counting drops', () => { + const store = createStore(2); + ['a', 'b', 'c', 'd'].forEach((n) => monitor.publish(bi(n))); + ['x', 'y', 'z'].forEach((m) => monitor.publish(log(m))); + flush(); + + expect(names(store)).toEqual(['d', 'c']); + expect(store.dropped().bi).toBe(2); + // A noisy kind never evicts another: each keeps its own two. + expect(store.entries().logging).toHaveLength(2); + expect(store.count('logging')).toEqual({ captured: 2, droppedOldest: 1 }); + expect(store.count('all')).toEqual({ captured: 4, droppedOldest: 3 }); + }); + + it('should clear everything and keep capturing', () => { + const store = createStore(); + monitor.publish(bi('a')); + flush(); + + store.clear(); + expect(store.total()).toBe(0); + + monitor.publish(bi('after')); + flush(); + expect(names(store)).toEqual(['after']); + }); + + it('should freeze the view while paused, but keep capturing', () => { + const store = createStore(); + monitor.publish(bi('a')); + flush(); + + store.setPaused(true); + monitor.publish(bi('b')); + monitor.publish(bi('c')); + flush(); + expect(names(store)).toEqual(['a']); + expect(store.newWhilePaused()).toBe(2); + // The export still contains them. + expect(store.events('bi')).toHaveLength(3); + + store.setPaused(false); + expect(names(store)).toEqual(['c', 'b', 'a']); + expect(store.newWhilePaused()).toBe(0); + }); + + it('should remember when it was paused, through a clear, until resumed', () => { + const store = createStore(); + expect(store.pausedAt()).toBeUndefined(); + + store.setPaused(true); + const at = store.pausedAt(); + expect(at).toBeDefined(); + store.setPaused(true); + store.clear(); + expect(store.pausedAt()).toBe(at); + + store.setPaused(false); + expect(store.pausedAt()).toBeUndefined(); + }); + + it('should include not-yet-flushed events in the download without hiding them', () => { + const store = createStore(); + monitor.publish(bi('a')); + + expect(store.events('bi')).toHaveLength(1); + expect(store.events('all')).toHaveLength(1); + expect(store.count('bi')).toEqual({ captured: 1, droppedOldest: 0 }); + + flush(); + expect(names(store)).toEqual(['a']); + }); + + it('should export one kind, or every kind, oldest first', () => { + const store = createStore(); + monitor.publish(bi('first')); + monitor.publish(log('a log line')); + monitor.publish(bi('second')); + flush(); + + const events = store.events('bi'); + expect(events.map((e) => e.kind)).toEqual(['bi', 'bi']); + expect(events[0].sequence).toBeLessThan(events[1].sequence); + expect(store.events('all').map((e) => e.kind)).toEqual(['bi', 'log', 'bi']); + expect(store.events('logging')).toHaveLength(1); + expect(store.events('scenario')).toHaveLength(0); + }); + + it('should show every kind merged newest first, or one kind', () => { + const store = createStore(); + monitor.publish(bi('a')); + monitor.publish(log('b')); + monitor.publish(bi('c')); + flush(); + + const sequences = store.filtered().map((e) => e.event.sequence); + expect(store.filtered().map((e) => e.event.kind)).toEqual([ + 'bi', + 'log', + 'bi' + ]); + expect(sequences).toEqual([...sequences].sort((x, y) => y - x)); + + store.setKind('logging'); + expect(store.filtered().map((e) => e.event.kind)).toEqual(['log']); + }); + + it('should filter every kind at once, ignoring case, and count matches per kind', () => { + const store = createStore(); + monitor.publish(bi('theme_changed')); + monitor.publish(bi('sidebar_toggled')); + monitor.publish(log('Careful now')); + flush(); + + store.setText('THEME'); + expect(store.filtered()).toHaveLength(1); + expect(store.matchCounts()).toEqual({ bi: 1, scenario: 0, logging: 0 }); + + store.setText(''); + store.addFilter({ path: 'level', operator: 'equals', value: 'warn' }); + expect(store.filtered().map((e) => e.event.kind)).toEqual(['log']); + expect(store.matchCounts()).toEqual({ bi: 0, scenario: 0, logging: 1 }); + + store.removeFilter(store.filters().fields[0].id); + expect(store.filtered()).toHaveLength(3); + + store.setText('careful'); + store.setKind('bi'); + expect(store.filtered()).toHaveLength(0); + store.clearFilters(); + expect(store.filters()).toEqual({ text: '', fields: [] }); + expect(store.filtered()).toHaveLength(2); + }); + + it('should suggest filter fields from the kind shown', () => { + const store = createStore(); + monitor.publish(bi('a')); + monitor.publish(log('b')); + flush(); + + expect(store.knownPaths()).toContain('level'); + expect(store.knownPaths()).toContain('eventName'); + + store.setKind('bi'); + expect(store.knownPaths()).not.toContain('level'); + expect(store.knownPaths()).toContain('eventName'); + }); + + it('should mark the latest arrivals as new, briefly', () => { + const store = createStore(); + monitor.publish(bi('a')); + flush(); + + const [entry] = store.entries().bi; + expect(store.fresh().has(entry.event.sequence)).toBe(true); + jest.advanceTimersByTime(3000); + expect(store.fresh().size).toBe(0); + }); + + it('should keep going after an event it cannot prepare', () => { + const store = createStore(); + const payload: Record = { eventName: 'x' }; + Object.defineProperty(payload, 'boom', { + enumerable: true, + get: () => { + throw new Error('bad getter'); + } + }); + // Delivered as-is, bypassing the monitor's copy, to simulate a bad payload. + (store as unknown as { receive(e: unknown): void }).receive({ + ...bi('x'), + payload, + sequence: 99, + capturedAt: '' + }); + monitor.publish(bi('fine')); + flush(); + + expect(store.entries().bi).toHaveLength(2); + expect(store.entries().bi[1].renderError).toBe('bad getter'); + }); + + it('should stop observing when destroyed', () => { + createStore(); + expect(monitor.observed).toBe(true); + + destroy(); + expect(monitor.observed).toBe(false); + }); +}); diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-store/uwt-inspector-store.ts b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-store/uwt-inspector-store.ts new file mode 100644 index 0000000..7c4c915 --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-store/uwt-inspector-store.ts @@ -0,0 +1,390 @@ +import { + computed, + DestroyRef, + inject, + Injectable, + signal +} from '@angular/core'; +import { + UwtTelemetryMonitor, + UwtTelemetryObservedEvent +} from '@absaoss-cps/ngx-ui-watchtower'; +import { + UWT_EVENT_INSPECTOR_CONFIG, + UwtInspectorEntry, + UwtInspectorFieldFilter, + UwtInspectorFilterState, + UwtInspectorKindFilter, + UwtInspectorSectionId, + UwtEventInspectorConfig +} from '../uwt-inspector.models/uwt-inspector.models'; +import { + uwtInspectorSectionOf, + uwtFlattenEvent, + uwtKnownPaths, + uwtMatchesFilters +} from '../uwt-inspector-filter/uwt-inspector-filter'; +import { uwtSafeStringify } from '../uwt-inspector-export/uwt-inspector-export'; +import { UwtInspectorRing } from '../uwt-inspector-ring/uwt-inspector-ring'; + +/** Library defaults, applied under whatever the application configures. */ +export const UWT_DEFAULT_INSPECTOR_CONFIG: Omit< + UwtEventInspectorConfig, + 'shortcuts' +> = { + enabled: true, + maxEventsPerSection: 500, + maxPayloadCharsInView: 256 * 1024, + captureBeforeOpen: 0 +}; + +/** How often arriving events reach the screen, in milliseconds. */ +export const UWT_INSPECTOR_FLUSH_MS = 250; +/** How long a row counts as new, in milliseconds. */ +const FRESH_MS = 3000; + +export const UWT_INSPECTOR_SECTIONS: readonly UwtInspectorSectionId[] = [ + 'bi', + 'scenario', + 'logging' +]; + +type PerSection = Record; + +function perSection(make: () => T): PerSection { + return { bi: make(), scenario: make(), logging: make() }; +} + +/** + * The inspector's captured history. Held by the `UwtInspectorSession`, so + * it lasts from opening to closing, whichever docks the inspector moves + * between: created on open — empty, or with what {@link UwtInspectorRing} + * kept from before — subscribed to {@link UwtTelemetryMonitor} for its + * lifetime, and discarded on close. + * + * Events arrive whenever the application sends them; they are batched and + * reach the screen at most every {@link UWT_INSPECTOR_FLUSH_MS}, so a + * burst causes one re-render rather than hundreds. Each kind keeps at most + * `maxEventsPerSection`, newest first, and counts what it dropped; the + * timeline shows them merged, newest first. + */ +@Injectable() +export class UwtInspectorStore { + private readonly monitor = inject(UwtTelemetryMonitor); + private readonly ring = inject(UwtInspectorRing); + private readonly config: UwtEventInspectorConfig = { + ...UWT_DEFAULT_INSPECTOR_CONFIG, + shortcuts: [], + ...inject(UWT_EVENT_INSPECTOR_CONFIG, { optional: true }) + }; + + /** Newest first. Written on every flush, paused or not. */ + private readonly buffers = perSection(() => []); + private readonly droppedCount = perSection(() => 0); + private pending: UwtInspectorEntry[] = []; + private flushTimer?: ReturnType; + private freshTimer?: ReturnType; + + /** + * When capturing began: the popup opening, the app's start with + * `captureBeforeOpen`, or the last clear. + */ + readonly startedAt = signal(new Date().toISOString()); + + /** What the popup shows: the buffers as of the last unpaused flush. */ + readonly entries = signal>( + perSection(() => []) + ); + + readonly dropped = signal>(perSection(() => 0)); + + /** Freezes what is shown. Capturing carries on underneath. */ + readonly paused = signal(false); + /** When the user paused; `undefined` while capturing live. */ + readonly pausedAt = signal(undefined); + /** Events captured while paused and not yet shown. */ + readonly newWhilePaused = signal(0); + + /** + * Events received per kind since opening or the last clear — keeps + * rising after the retention cap, unlike the kinds' lengths. + */ + readonly received = signal>(perSection(() => 0)); + + /** Sequence numbers from the latest flush, for a "new" marker. */ + readonly fresh = signal>(new Set()); + + /** Which kind the timeline shows, and exports. */ + readonly kind = signal('all'); + + /** The timeline's search text and field filters, for every kind. */ + readonly filters = signal(emptyFilters()); + + /** Each kind's entries that match the filters. */ + private readonly matching = computed< + PerSection + >(() => { + const entries = this.entries(); + const filters = this.filters(); + return perSectionFrom((id) => + isFiltering(filters) + ? entries[id].filter((e) => uwtMatchesFilters(e.fields, filters)) + : entries[id] + ); + }); + + /** How many events of each kind match the filters. */ + readonly matchCounts = computed>(() => { + const matching = this.matching(); + return perSectionFrom((id) => matching[id].length); + }); + + /** The timeline: entries of the kind shown that match, newest first. */ + readonly filtered = computed(() => + this.ofKind(this.matching(), this.kind()) + ); + + /** Field paths seen so far in the kind shown, for filter suggestions. */ + readonly knownPaths = computed(() => + uwtKnownPaths(this.ofKind(this.entries(), this.kind()).map((e) => e.fields)) + ); + + readonly total = computed(() => { + const entries = this.entries(); + return UWT_INSPECTOR_SECTIONS.reduce((n, id) => n + entries[id].length, 0); + }); + + constructor() { + this.adopt(); + // Straight after adopting: nothing can arrive in between. + const subscription = this.monitor.events$.subscribe((event) => + this.receive(event) + ); + inject(DestroyRef).onDestroy(() => { + subscription.unsubscribe(); + clearTimeout(this.flushTimer); + clearTimeout(this.freshTimer); + }); + } + + /** Empties every kind, and what was kept from before. Capturing continues. */ + clear(): void { + this.ring.clear(); + for (const id of UWT_INSPECTOR_SECTIONS) { + this.buffers[id] = []; + this.droppedCount[id] = 0; + } + this.pending = []; + this.startedAt.set(new Date().toISOString()); + this.newWhilePaused.set(0); + this.received.set(perSection(() => 0)); + this.publish(); + } + + setPaused(paused: boolean): void { + if (paused !== this.paused()) { + this.pausedAt.set(paused ? new Date().toISOString() : undefined); + } + this.paused.set(paused); + if (!paused) { + this.newWhilePaused.set(0); + this.publish(); + } + } + + setKind(kind: UwtInspectorKindFilter): void { + this.kind.set(kind); + } + + setText(text: string): void { + this.filters.update((f) => ({ ...f, text })); + } + + addFilter(filter: Omit): void { + this.filters.update((f) => ({ + ...f, + fields: [...f.fields, { ...filter, id: newId() }] + })); + } + + removeFilter(id: string): void { + this.filters.update((f) => ({ + ...f, + fields: f.fields.filter((x) => x.id !== id) + })); + } + + clearFilters(): void { + this.filters.set(emptyFilters()); + } + + /** + * Every event captured of a kind: what is shown, plus anything held back + * while paused or still waiting for the next flush. Oldest first. + */ + events(kind: UwtInspectorKindFilter): UwtTelemetryObservedEvent[] { + return [ + ...this.captured(kind).map((e) => e.event), + ...this.pendingOf(kind).map((e) => e.event) + ].sort((a, b) => a.sequence - b.sequence); + } + + /** A kind's totals for its export, counted like {@link events}. */ + count(kind: UwtInspectorKindFilter): { + captured: number; + droppedOldest: number; + } { + const ids = kind === 'all' ? UWT_INSPECTOR_SECTIONS : [kind]; + return { + captured: this.captured(kind).length + this.pendingOf(kind).length, + droppedOldest: ids.reduce((n, id) => n + this.droppedCount[id], 0) + }; + } + + /** One kind's entries, or every kind's merged newest first. */ + private ofKind( + entries: PerSection, + kind: UwtInspectorKindFilter + ): readonly UwtInspectorEntry[] { + return kind === 'all' + ? UWT_INSPECTOR_SECTIONS.flatMap((id) => entries[id]).sort( + (a, b) => b.event.sequence - a.event.sequence + ) + : entries[kind]; + } + + private captured(kind: UwtInspectorKindFilter): UwtInspectorEntry[] { + return kind === 'all' + ? UWT_INSPECTOR_SECTIONS.flatMap((id) => this.buffers[id]) + : this.buffers[kind]; + } + + private pendingOf(kind: UwtInspectorKindFilter): UwtInspectorEntry[] { + return kind === 'all' + ? this.pending + : this.pending.filter((e) => e.section === kind); + } + + /** + * Called for every observed event. Never throws: an error here would + * surface asynchronously in the application, since RxJS reports + * subscriber errors on its own. + */ + private receive(event: UwtTelemetryObservedEvent): void { + try { + this.pending.push(this.prepare(event)); + this.flushTimer ??= setTimeout( + () => this.flush(), + UWT_INSPECTOR_FLUSH_MS + ); + } catch { + // Nothing to show for this one; later events are unaffected. + } + } + + private prepare(event: UwtTelemetryObservedEvent): UwtInspectorEntry { + const section = uwtInspectorSectionOf(event.kind); + try { + return { + event, + section, + fields: uwtFlattenEvent(event), + sizeChars: uwtSafeStringify(event.payload).length + }; + } catch (error) { + return { + event, + section, + fields: [], + sizeChars: 0, + renderError: error instanceof Error ? error.message : String(error) + }; + } + } + + /** + * Takes on what the ring kept from before opening: shown at once, not + * marked new, and not counted as received since opening. + */ + private adopt(): void { + const kept = this.ring.snapshot(); + if (kept.startedAt) { + this.startedAt.set(kept.startedAt); + } + this.pending = kept.events.map((event) => this.prepare(event)); + this.drain(); + for (const id of UWT_INSPECTOR_SECTIONS) { + this.droppedCount[id] += kept.dropped[id]; + } + this.publish(); + } + + private flush(): void { + this.flushTimer = undefined; + const arrived = this.drain(); + if (arrived.length === 0) { + return; + } + this.received.update((counts) => { + const next = { ...counts }; + for (const entry of arrived) { + next[entry.section]++; + } + return next; + }); + if (this.paused()) { + this.newWhilePaused.update((n) => n + arrived.length); + return; + } + this.publish(arrived); + } + + /** Moves pending events into the buffers. Returns what moved. */ + private drain(): UwtInspectorEntry[] { + const arrived = this.pending; + this.pending = []; + const max = Math.max(1, this.config.maxEventsPerSection); + for (const entry of arrived) { + const buffer = this.buffers[entry.section]; + buffer.unshift(entry); + if (buffer.length > max) { + this.droppedCount[entry.section] += buffer.length - max; + buffer.length = max; + } + } + return arrived; + } + + private publish(arrived: readonly UwtInspectorEntry[] = []): void { + this.entries.set(perSectionFrom((id) => [...this.buffers[id]])); + this.dropped.set({ ...this.droppedCount }); + this.fresh.set(new Set(arrived.map((e) => e.event.sequence))); + clearTimeout(this.freshTimer); + if (arrived.length) { + this.freshTimer = setTimeout(() => this.fresh.set(new Set()), FRESH_MS); + } + } +} + +function perSectionFrom( + make: (id: UwtInspectorSectionId) => T +): PerSection { + return { + bi: make('bi'), + scenario: make('scenario'), + logging: make('logging') + }; +} + +function emptyFilters(): UwtInspectorFilterState { + return { text: '', fields: [] }; +} + +function isFiltering(filters: UwtInspectorFilterState): boolean { + return !!filters.text.trim() || filters.fields.length > 0; +} + +let idCounter = 0; +function newId(): string { + return `f${++idCounter}`; +} diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-timeline/uwt-inspector-rows.spec.ts b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-timeline/uwt-inspector-rows.spec.ts new file mode 100644 index 0000000..1535022 --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-timeline/uwt-inspector-rows.spec.ts @@ -0,0 +1,188 @@ +import { UwtTelemetryObservedEvent } from '@absaoss-cps/ngx-ui-watchtower'; +import { uwtInspectorSectionOf } from '../uwt-inspector-filter/uwt-inspector-filter'; +import { uwtInspectorRow } from './uwt-inspector-rows'; + +const base = { + sequence: 7, + capturedAt: '2026-01-01T00:00:00.000Z', + destination: 'sink' as const, + origin: { forwarded: false as const } +}; + +/** The row shown for `event`, without its entry. */ +function rowOf(event: UwtTelemetryObservedEvent) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { entry, time, ...row } = uwtInspectorRow({ + event, + section: uwtInspectorSectionOf(event.kind), + fields: [], + sizeChars: 0 + }); + return row; +} + +describe('uwtInspectorRow', () => { + it('should show a BI event by name, with its first metadata', () => { + expect( + rowOf({ + ...base, + kind: 'bi', + eventType: 'com.uwt.bi', + payload: { + eventName: 'theme_changed', + eventTime: '', + application: 'app', + scenarioId: 's-1', + metadata: { a: 1, b: 2, c: 3, d: 4 } + } + }) + ).toEqual({ + sequence: 7, + kind: 'BI', + name: 'theme_changed', + details: 'a=1, b=2, c=3, …', + tone: undefined, + eventType: 'com.uwt.bi', + id: 's-1', + forwardedFrom: undefined, + // No feature to offer: this event has none. + filterBy: [ + { path: 'eventName', name: 'Event name', value: 'theme_changed' } + ] + }); + }); + + it('should show a scenario with its status, duration and steps', () => { + expect( + rowOf({ + ...base, + kind: 'scenario', + eventType: 'com.uwt.scenario', + payload: { + scenarioId: 's-1', + scenarioName: 'checkout', + status: 'failure', + startTime: '', + delta: 42, + elapsed: 0, + stepCount: 1, + steps: [], + application: 'app' + } + }) + ).toMatchObject({ + kind: 'Scenario', + name: 'checkout', + details: 'failure · 42 ms · 1 step', + tone: 'error', + id: 's-1', + filterBy: [ + { path: 'scenarioName', name: 'Scenario', value: 'checkout' }, + { path: 'status', name: 'Status', value: 'failure' } + ] + }); + }); + + it('should show a step under its scenario', () => { + expect( + rowOf({ + ...base, + kind: 'scenario-step', + eventType: 'com.uwt.step', + payload: { + scenarioId: 's-1', + scenarioName: 'checkout', + name: 'pay', + status: 'abandoned', + stepDelta: 5, + application: 'app' + } as never + }) + ).toMatchObject({ + kind: 'Step', + name: 'checkout · pay', + details: 'abandoned · 5 ms', + tone: 'warn' + }); + }); + + it('should show a log by logger, with its level and message', () => { + expect( + rowOf({ + ...base, + kind: 'log', + destination: 'log-provider', + payload: { + timestamp: '', + level: 'warn', + message: 'Careful now', + logger: 'checkout', + correlationId: 'c-9', + application: 'app', + environment: 'test', + version: '1' + } + }) + ).toMatchObject({ + kind: 'Log', + name: 'checkout', + details: 'warn · Careful now', + tone: 'warn', + eventType: '', + id: 'c-9', + filterBy: [ + { path: 'logger', name: 'Logger', value: 'checkout' }, + { path: 'level', name: 'Level', value: 'warn' } + ] + }); + }); + + it('should offer to filter only by a value its filter finds again', () => { + // Matching trims the filter's value, and compares 2,000 characters. + const { filterBy } = rowOf({ + ...base, + kind: 'bi', + eventType: 'com.uwt.bi', + payload: { + eventName: 'x'.repeat(2001), + feature: ' checkout', + eventTime: '', + application: 'app' + } + }); + expect(filterBy).toEqual([]); + }); + + it('should show an error sent to RUM', () => { + expect( + rowOf({ + ...base, + kind: 'error', + payload: { name: 'TypeError', message: 'x is undefined' } + }) + ).toMatchObject({ + kind: 'Error', + name: 'TypeError', + details: '→ RUM · x is undefined', + tone: 'error', + filterBy: [] + }); + }); + + it('should show a forwarded event of an unknown type, and who sent it', () => { + expect( + rowOf({ + ...base, + origin: { forwarded: true, application: 'fragment' }, + kind: 'unknown', + eventType: 'com.other.thing', + payload: {} + }) + ).toMatchObject({ + kind: 'BI', + name: 'com.other.thing', + details: 'unrecognised event type', + forwardedFrom: 'fragment' + }); + }); +}); diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-timeline/uwt-inspector-rows.ts b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-timeline/uwt-inspector-rows.ts new file mode 100644 index 0000000..69d74d5 --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-timeline/uwt-inspector-rows.ts @@ -0,0 +1,228 @@ +import { + UwtTelemetryEventKind, + UwtTelemetryObservedEvent +} from '@absaoss-cps/ngx-ui-watchtower'; +import { UwtInspectorEntry } from '../uwt-inspector.models/uwt-inspector.models'; +import { + MAX_VALUE_CHARS, + uwtFieldName +} from '../uwt-inspector-filter/uwt-inspector-filter'; + +/** How a row's details are coloured. The words always say it too. */ +export type UwtInspectorTone = 'success' | 'warn' | 'error'; + +/** A field an open row offers to filter by, e.g. `Event name: x`. */ +export interface UwtInspectorFilterBy { + path: string; + /** The field as people read it. */ + name: string; + value: string; +} + +/** The fields worth filtering by, for each kind of event. */ +const FILTER_BY: Partial> = { + bi: ['eventName', 'feature'], + scenario: ['scenarioName', 'status'], + 'scenario-step': ['scenarioName', 'status'], + log: ['logger', 'level'] +}; + +/** One timeline row: display text, plus the entry for the detail view. */ +export interface UwtInspectorRow { + sequence: number; + time: string; + /** `BI`, `Scenario`, `Step`, `Log` or `Error`. */ + kind: string; + /** The event, scenario, step, logger or error name. */ + name: string; + details: string; + tone?: UwtInspectorTone; + /** Secondary: hidden on narrow screens. */ + eventType: string; + /** Secondary: the scenario id, or a log's correlation id. */ + id: string; + forwardedFrom?: string; + /** The row's own values of the fields worth filtering by. */ + filterBy: UwtInspectorFilterBy[]; + entry: UwtInspectorEntry; +} + +type RowText = Pick< + UwtInspectorRow, + 'kind' | 'name' | 'details' | 'tone' | 'id' +>; + +/** Reads a field as text, whatever the payload's declared type. */ +/** + * A value an `equals` filter finds again: plain, not padded — matching trims + * the filter's value — and within what matching compares. + */ +function filterable(payload: unknown, key: string): string { + const value = + payload !== null && typeof payload === 'object' + ? (payload as Record)[key] + : undefined; + if (!['string', 'number', 'boolean'].includes(typeof value)) { + return ''; + } + const shown = String(value); + return shown === shown.trim() && shown.length <= MAX_VALUE_CHARS ? shown : ''; +} + +function text(payload: unknown, key: string): string { + if (payload === null || typeof payload !== 'object') { + return ''; + } + const value = (payload as Record)[key]; + return value === undefined || value === null ? '' : String(value); +} + +/** The first three metadata pairs, `key=value`. */ +function metadataSummary(payload: unknown): string { + if (payload === null || typeof payload !== 'object') { + return ''; + } + const metadata = (payload as Record).metadata; + if (metadata === null || typeof metadata !== 'object') { + return ''; + } + const pairs = Object.entries(metadata as Record); + const shown = pairs + .slice(0, 3) + .map(([k, v]) => `${k}=${String(v)}`) + .join(', '); + return pairs.length > 3 ? `${shown}, …` : shown; +} + +function ms(value: string): string { + return value === '' ? '' : `${value} ms`; +} + +function join(...parts: string[]): string { + return parts.filter(Boolean).join(' · '); +} + +function statusTone(status: string): UwtInspectorTone | undefined { + switch (status) { + case 'success': + return 'success'; + case 'failure': + case 'timeout': + return 'error'; + case 'abandoned': + case 'incomplete': + return 'warn'; + default: + return undefined; + } +} + +function levelTone(level: string): UwtInspectorTone | undefined { + return level === 'warn' ? 'warn' : level === 'error' ? 'error' : undefined; +} + +function describe(event: UwtTelemetryObservedEvent): RowText { + const p = event.payload; + switch (event.kind) { + case 'bi': + return { + kind: 'BI', + name: text(p, 'eventName'), + details: metadataSummary(p), + id: text(p, 'scenarioId') + }; + case 'scenario': { + const status = text(p, 'status'); + const steps = text(p, 'stepCount'); + return { + kind: 'Scenario', + name: text(p, 'scenarioName'), + details: join( + status, + ms(text(p, 'delta')), + steps && `${steps} step${steps === '1' ? '' : 's'}` + ), + tone: statusTone(status), + id: text(p, 'scenarioId') + }; + } + case 'scenario-step': { + const status = text(p, 'status'); + return { + kind: 'Step', + name: join(text(p, 'scenarioName'), text(p, 'name')), + details: join(status, ms(text(p, 'stepDelta'))), + tone: statusTone(status), + id: text(p, 'scenarioId') + }; + } + case 'log': { + const level = text(p, 'level'); + return { + kind: 'Log', + name: text(p, 'logger'), + details: join(level, text(p, 'message')), + tone: levelTone(level), + id: text(p, 'correlationId') + }; + } + case 'error': + return { + kind: 'Error', + name: text(p, 'name'), + details: join('→ RUM', text(p, 'message')), + tone: 'error', + id: '' + }; + default: + // Forwarded from another app, of a type this one doesn't know. + return { + kind: 'BI', + name: 'eventType' in event ? event.eventType : '', + details: 'unrecognised event type', + id: '' + }; + } +} + +/** `HH:MM:SS.mmm` in local time — the precision that orders a burst. */ +export function uwtInspectorTime(iso: string): string { + const date = new Date(iso); + if (Number.isNaN(date.getTime())) { + return iso; + } + const pad = (n: number, width = 2) => String(n).padStart(width, '0'); + return `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad( + date.getSeconds() + )}.${pad(date.getMilliseconds(), 3)}`; +} + +export function uwtInspectorRow(entry: UwtInspectorEntry): UwtInspectorRow { + const { event } = entry; + let described: RowText; + let filterBy: UwtInspectorFilterBy[]; + try { + described = describe(event); + filterBy = (FILTER_BY[event.kind] ?? []) + .map((path) => ({ + path, + name: uwtFieldName(path), + value: filterable(event.payload, path) + })) + .filter((f) => f.value); + } catch { + described = { kind: '', name: '', details: '', id: '' }; + filterBy = []; + } + return { + sequence: event.sequence, + time: uwtInspectorTime(event.capturedAt), + ...described, + eventType: 'eventType' in event ? event.eventType : '', + forwardedFrom: event.origin.forwarded + ? (event.origin.application ?? 'another app') + : undefined, + filterBy, + entry + }; +} diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-timeline/uwt-inspector-timeline.component.html b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-timeline/uwt-inspector-timeline.component.html new file mode 100644 index 0000000..b201b8d --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-timeline/uwt-inspector-timeline.component.html @@ -0,0 +1,213 @@ +
+
+ + + + + + +
+ + + + @if (unseenNew() > 0) { +
+ {{ unseenNew() }} new at the top of the list. + + +
+ } + + + + Time + Kind + Name + Details + Event type + Id + + + + + @if (isFresh(row.sequence)) { + New: + } + {{ row.time.slice(0, 8) + }}{{ + row.time.slice(8) + }} + + {{ row.kind }} + + @if (row.forwardedFrom) { + ↪ {{ row.forwardedFrom }} + } + {{ row.name }} + + + {{ row.details }} + + + {{ row.eventType }} + + + {{ row.id }} + + + + +
+
+
+
Handed to
+
+ {{ + row.entry.event.destination === 'sink' + ? 'telemetry sink' + : 'log API provider' + }} +
+
+
+
From
+
+ {{ + row.forwardedFrom + ? 'forwarded by ' + row.forwardedFrom + : 'this app' + }} +
+
+ @if ('eventType' in row.entry.event) { +
+
Event type
+
{{ row.entry.event.eventType }}
+
+ } + @if ( + row.entry.event.kind === 'error' && row.entry.event.relatedSequence; + as related + ) { +
+
Mirrored from
+
+ @if (logSequences().has(related)) { + + } @else { + log event #{{ related }} + } +
+
+ } + @if (mirroredTo().get(row.sequence); as error) { +
+
Mirrored to RUM as
+
+ +
+
+ } +
+
Event
+
#{{ row.sequence }}, {{ row.entry.sizeChars }} characters
+
+
+ @if (row.filterBy.length > 0) { +
+ + @for (field of row.filterBy; track field.path) { + + + } +
+ } + @if (row.entry.renderError) { +

+ Couldn't prepare this event for display: + {{ row.entry.renderError }} +

+ } + + +
+
+
+
diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-timeline/uwt-inspector-timeline.component.scss b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-timeline/uwt-inspector-timeline.component.scss new file mode 100644 index 0000000..17117bd --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-timeline/uwt-inspector-timeline.component.scss @@ -0,0 +1,198 @@ +:host { + display: block; + container-type: inline-size; +} + +.uwt-inspector-timeline { + display: flex; + flex-direction: column; + gap: var(--uwt-inspector-space-m); +} + +// Export sits by the kind toggle, as it exports the kind shown. On the +// left, its menu has room to open: in a separate window the overlay is +// placed against the app's window, so it can't flip back from the edge. +.uwt-inspector-timeline__tools { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: var(--uwt-inspector-space-m) 1rem; +} + +.uwt-inspector-timeline__notice { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.8125rem; + color: var(--cps-text-secondary); +} + +// The table fits the timeline: the columns take the widths set below, a +// long value ends in an ellipsis (in full in its title and the row's +// detail), and an open row's payload wraps rather than widening the table. +// Not a fixed layout: the open row spans 100 columns, which would squeeze +// the real ones. +// A little apart from the controls above it: the table is what they act on. +:host ::ng-deep .uwt-inspector-timeline__table { + width: 100%; + margin-top: calc(var(--uwt-inspector-space-s) / 2); +} + +td { + // Lets the table make a cell narrower than its text. + max-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.uwt-inspector-timeline__time { + width: 7rem; + max-width: none; + font-variant-numeric: tabular-nums; +} + +.uwt-inspector-timeline__kind { + width: 5.5rem; +} + +.uwt-inspector-timeline__name { + width: 25%; +} + +.uwt-inspector-timeline__secondary { + width: 8rem; +} + +.uwt-inspector-timeline__forwarded { + margin-inline-end: 0.375rem; + color: var(--cps-text-secondary); + font-size: 0.75rem; +} + +// A new row is tinted, fading over the time it counts as new. The table +// draws the row, so the tint goes on its cells. +:host ::ng-deep tr:has(> .uwt-inspector-timeline__fresh) > td { + animation: uwt-inspector-fresh 3s ease-in forwards; +} + +@keyframes uwt-inspector-fresh { + from { + background-color: var(--cps-color-calm-highlighten); + } + to { + background-color: transparent; + } +} + +@media (prefers-reduced-motion: reduce) { + :host ::ng-deep tr:has(> .uwt-inspector-timeline__fresh) > td { + animation: none; + background-color: var(--cps-color-calm-highlighten); + } +} + +// The status or level is always written out; colour only supports it. +// Shades dark enough to read as text: success 5.1:1, warn 5.6:1 on white. +.uwt-inspector-timeline__tone--success { + color: var(--cps-color-success-darken2); +} + +.uwt-inspector-timeline__tone--warn { + color: var(--cps-color-warn-darken3); + font-weight: 600; +} + +.uwt-inspector-timeline__tone--error { + color: var(--cps-state-error); + font-weight: 600; +} + +.uwt-inspector-timeline__detail { + display: flex; + flex-direction: column; + gap: var(--uwt-inspector-space-m); + padding: var(--uwt-inspector-space-m) 0.25rem; +} + +.uwt-inspector-timeline__meta { + display: flex; + flex-wrap: wrap; + gap: var(--uwt-inspector-space-s) 1rem; + margin: 0; + font-size: 0.8125rem; + + div { + display: flex; + gap: var(--uwt-inspector-space-s); + } + + dt { + color: var(--cps-text-secondary); + } + + dd { + margin: 0; + color: var(--cps-text-primary); + } +} + +.uwt-inspector-timeline__link { + padding: 0; + border: 0; + background: none; + color: var(--cps-color-calm); + font: inherit; + text-decoration: underline; + cursor: pointer; + + &:focus-visible { + outline: 2px solid var(--cps-ring-color); + outline-offset: 2px; + } +} + +.uwt-inspector-timeline__filter-by { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.25rem 0.5rem; + font-size: 0.8125rem; + color: var(--cps-text-secondary); +} + +.uwt-inspector-timeline__error { + margin: 0; + color: var(--cps-state-error); + font-size: 0.8125rem; +} + +// Only with room to spare, so the details keep theirs. +@container (max-width: 64rem) { + .uwt-inspector-timeline__secondary { + display: none; + } +} + +// A phone: the milliseconds go, leaving the name and details more room. +@container (max-width: 30rem) { + .uwt-inspector-timeline__time { + width: 4.5rem; + } + + .uwt-inspector-timeline__ms { + display: none; + } +} + +// Forced colours drop the fill that marks the kind shown: it takes the +// system's selection colours instead. Important, as cps-button-toggle's +// own rule is the more specific. +@media (forced-colors: active) { + :host ::ng-deep .cps-btn-toggle-content-option.is-selected { + forced-color-adjust: none; + border-color: Highlight !important; + background: Highlight !important; + color: HighlightText !important; + } +} diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-timeline/uwt-inspector-timeline.component.spec.ts b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-timeline/uwt-inspector-timeline.component.spec.ts new file mode 100644 index 0000000..aec732a --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-timeline/uwt-inspector-timeline.component.spec.ts @@ -0,0 +1,346 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { UwtTelemetryMonitor } from '@absaoss-cps/ngx-ui-watchtower'; +import { CPS_LIVE_ANNOUNCER_SERVICE } from 'cps-ui-kit'; +import { UwtInspectorExporter } from '../uwt-inspector-export/uwt-inspector-exporter'; +import { + UWT_INSPECTOR_FLUSH_MS, + UwtInspectorStore +} from '../uwt-inspector-store/uwt-inspector-store'; +import { UwtInspectorTimelineComponent } from './uwt-inspector-timeline.component'; + +/** Rows per page, as the timeline's table is configured. */ +const PAGE = 25; + +describe('UwtInspectorTimelineComponent', () => { + let fixture: ComponentFixture; + let store: UwtInspectorStore; + let monitor: UwtTelemetryMonitor; + let announce: jest.Mock; + + const el = () => fixture.nativeElement as HTMLElement; + const shownRows = () => + el().querySelectorAll('td.uwt-inspector-timeline__time').length; + /** Whether the paginator shows; the table hides it with one page. */ + const paginatorShown = () => { + const paginator = el().querySelector('.p-paginator'); + return !!paginator && paginator.style.display !== 'none'; + }; + const ui = () => + fixture.componentInstance as unknown as { + first(): number; + onPage(state: { first?: number }): void; + }; + + /** Publishes events, every tenth one named `needle`: BI, or logs. */ + function publish(count: number, kind: 'bi' | 'log' = 'bi'): void { + for (let i = 0; i < count; i++) { + const name = i % 10 === 0 ? 'needle' : `hay-${i}`; + monitor.publish( + kind === 'bi' + ? { + kind: 'bi', + eventType: 'com.uwt.bi', + payload: { + eventName: name, + eventTime: '2026-01-01T00:00:00.000Z', + application: 'app' + }, + destination: 'sink', + origin: { forwarded: false } + } + : { + kind: 'log', + payload: { + timestamp: '2026-01-01T00:00:00.000Z', + level: 'log', + message: name, + application: 'app', + environment: 'test', + version: '1' + }, + destination: 'log-provider', + origin: { forwarded: false } + } + ); + } + jest.advanceTimersByTime(UWT_INSPECTOR_FLUSH_MS); + fixture.detectChanges(); + } + + function goToPage(page: number): void { + ui().onPage({ first: (page - 1) * PAGE }); + fixture.detectChanges(); + } + + beforeEach(async () => { + jest.useFakeTimers(); + announce = jest.fn(); + await TestBed.configureTestingModule({ + imports: [UwtInspectorTimelineComponent, NoopAnimationsModule], + providers: [ + UwtInspectorStore, + UwtInspectorExporter, + { + provide: CPS_LIVE_ANNOUNCER_SERVICE, + useValue: { announce, clear: jest.fn() } + } + ] + }).compileComponents(); + monitor = TestBed.inject(UwtTelemetryMonitor); + store = TestBed.inject(UwtInspectorStore); + fixture = TestBed.createComponent(UwtInspectorTimelineComponent); + fixture.detectChanges(); + }); + + afterEach(() => { + fixture.destroy(); + jest.useRealTimers(); + }); + + describe('paging', () => { + it('should show the page the reader chose', () => { + publish(100); + goToPage(4); + + expect(ui().first()).toBe(75); + expect(shownRows()).toBe(PAGE); + }); + + it('should stay on the page while new events arrive', () => { + publish(100); + goToPage(2); + + publish(1); + + expect(ui().first()).toBe(PAGE); + }); + + it('should go back to the first page when the filters change', () => { + publish(100); + goToPage(2); + + store.setText('hay'); + fixture.detectChanges(); + + expect(ui().first()).toBe(0); + expect(shownRows()).toBe(PAGE); + }); + + it('should go back to the first page when another kind is shown', () => { + publish(60); + publish(60, 'log'); + goToPage(2); + + store.setKind('logging'); + fixture.detectChanges(); + + expect(ui().first()).toBe(0); + }); + + it('should go back to the first page when cleared', () => { + publish(100); + goToPage(3); + + store.clear(); + fixture.detectChanges(); + expect(ui().first()).toBe(0); + + publish(1); + expect(shownRows()).toBe(1); + }); + + it('should hide the paginator while everything fits on one page', () => { + publish(PAGE); + expect(paginatorShown()).toBe(false); + + publish(1); + expect(paginatorShown()).toBe(true); + + store.setText('needle'); + fixture.detectChanges(); + expect(shownRows()).toBe(4); + expect(paginatorShown()).toBe(false); + }); + }); + + it('should offer to go back to the newest, from a later page', () => { + publish(30); + jest.advanceTimersByTime(3000); // no longer new + goToPage(2); + expect(el().textContent).not.toContain('new at the top'); + + publish(2); + expect(el().textContent).toContain('2 new at the top of the list.'); + + // Of a burst reaching this page, only those above it count. + publish(30); + expect(el().textContent).toContain('25 new at the top of the list.'); + + const newest = Array.from(el().querySelectorAll('button')).find( + (b) => b.textContent?.trim() === 'Go to newest' + )!; + newest.focus(); + newest.click(); + fixture.detectChanges(); + TestBed.tick(); + expect(ui().first()).toBe(0); + // Its notice gone, focus is on the newest event, not the page. + expect(document.activeElement?.getAttribute('data-testid')).toMatch( + /^cps-table-row-expand-btn-/ + ); + }); + + it('should count every event that arrived above the page since leaving the first', () => { + publish(100); + goToPage(2); + + publish(1); + publish(1); + publish(1); + jest.advanceTimersByTime(5000); + fixture.detectChanges(); + + expect(el().textContent).toContain('3 new at the top of the list.'); + }); + + describe('an open row', () => { + const origin = { forwarded: false as const }; + const detail = (sequence: number) => + el().querySelector( + `[data-testid="cps-table-row-expanded-content-${sequence}"]` + ); + /** A button in an open row, found by its text. */ + const buttonIn = (sequence: number, text: string) => + Array.from(detail(sequence)?.querySelectorAll('button') ?? []).find( + (b) => b.textContent?.trim() === text + ); + + function open(sequence: number): void { + el() + .querySelector( + `[data-testid="cps-table-row-expand-btn-${sequence}"]` + ) + ?.click(); + fixture.detectChanges(); + } + + function settle(): void { + jest.advanceTimersByTime(UWT_INSPECTOR_FLUSH_MS); + fixture.detectChanges(); + } + + /** A log error, 30 more log lines, then the error mirroring it to RUM. */ + function mirror(): { log: number; error: number } { + const log = monitor.publish({ + kind: 'log', + payload: { + timestamp: '', + level: 'error', + message: 'Payment failed', + application: 'app', + environment: 'test', + version: '1' + }, + destination: 'log-provider', + origin + })!; + publish(30, 'log'); + const error = monitor.publish({ + kind: 'error', + payload: { name: 'Error', message: 'Payment failed' }, + relatedSequence: log, + destination: 'sink', + origin + })!; + settle(); + return { log, error }; + } + + it('should keep an open row, and the focus in it, as events arrive', () => { + publish(1); + const [{ event }] = store.filtered(); + open(event.sequence); + const button = buttonIn(event.sequence, 'Event name: needle')!; + button.focus(); + + publish(1); + + expect(button.isConnected).toBe(true); + expect(document.activeElement).toBe(button); + }); + + it('should filter by one of its values, once', () => { + publish(1); + const [{ event }] = store.filtered(); + open(event.sequence); + + buttonIn(event.sequence, 'Event name: needle')?.click(); + fixture.detectChanges(); + buttonIn(event.sequence, 'Event name: needle')?.click(); + + expect(store.filters().fields).toEqual([ + expect.objectContaining({ + path: 'eventName', + operator: 'equals', + value: 'needle' + }) + ]); + }); + + it('should take an error to its log event, open on its page, and back', () => { + const { log, error } = mirror(); + open(error); + + buttonIn(error, `log event #${log}`)?.click(); + fixture.detectChanges(); + TestBed.tick(); + + expect(ui().first()).toBe(PAGE); + expect(detail(log)).not.toBeNull(); + expect(document.activeElement?.getAttribute('aria-label')).toBe( + `Payload of event #${log}` + ); + + buttonIn(log, `event #${error}`)?.click(); + fixture.detectChanges(); + TestBed.tick(); + expect(ui().first()).toBe(0); + expect(document.activeElement?.getAttribute('aria-label')).toBe( + `Payload of event #${error}` + ); + }); + + it('should clear the filters hiding the log event, and say so', () => { + const { log, error } = mirror(); + store.addFilter({ path: 'kind', operator: 'equals', value: 'error' }); + fixture.detectChanges(); + open(error); + + buttonIn(error, `log event #${log}`)?.click(); + fixture.detectChanges(); + + expect(store.filters().fields).toHaveLength(0); + expect(detail(log)).not.toBeNull(); + expect(announce).toHaveBeenCalledWith( + `Filters cleared to show event #${log}.`, + 'polite' + ); + }); + + it('should name a log event it no longer has, without a link', () => { + const error = monitor.publish({ + kind: 'error', + payload: { name: 'Error', message: 'Payment failed' }, + relatedSequence: 999, + destination: 'sink', + origin + })!; + settle(); + open(error); + + expect(detail(error)?.textContent).toContain('log event #999'); + expect(buttonIn(error, 'log event #999')).toBeUndefined(); + }); + }); +}); diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-timeline/uwt-inspector-timeline.component.ts b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-timeline/uwt-inspector-timeline.component.ts new file mode 100644 index 0000000..46e775e --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-timeline/uwt-inspector-timeline.component.ts @@ -0,0 +1,309 @@ +import { + afterNextRender, + ChangeDetectionStrategy, + Component, + computed, + ElementRef, + inject, + Injector, + input, + linkedSignal, + signal, + viewChild +} from '@angular/core'; +import { + CPS_LIVE_ANNOUNCER_SERVICE, + CpsButtonComponent, + CpsButtonToggleComponent, + CpsButtonToggleOption, + CpsMenuComponent, + CpsMenuItem, + CpsTableComponent +} from 'cps-ui-kit'; +import { + UwtInspectorEntry, + UwtInspectorKindFilter, + UwtInspectorSectionId +} from '../uwt-inspector.models/uwt-inspector.models'; +import { + UWT_DEFAULT_INSPECTOR_CONFIG, + UWT_INSPECTOR_SECTIONS, + UwtInspectorStore +} from '../uwt-inspector-store/uwt-inspector-store'; +import { UwtInspectorJsonComponent } from '../uwt-inspector-json/uwt-inspector-json.component'; +import { UwtInspectorFilterBarComponent } from '../uwt-inspector-filter-bar/uwt-inspector-filter-bar.component'; +import { + uwtInspectorEventCount, + uwtInspectorEventsNoun, + UwtInspectorExporter +} from '../uwt-inspector-export/uwt-inspector-exporter'; +import { + UwtInspectorFilterBy, + UwtInspectorRow, + uwtInspectorRow +} from './uwt-inspector-rows'; + +/** The kind toggle's labels. */ +const KIND_LABEL: Record = { + bi: 'BI', + scenario: 'Scenarios', + logging: 'Logs' +}; + +/** + * Every captured event in one table, newest first: a kind toggle with + * counts, one search and set of filters for every kind, an Export menu for + * the kind shown, and a paginated table whose rows open on the payload, + * with ways to filter by its values and to reach the event it mirrors. + */ +@Component({ + selector: 'uwt-inspector-timeline', + imports: [ + CpsButtonComponent, + CpsButtonToggleComponent, + CpsMenuComponent, + CpsTableComponent, + UwtInspectorJsonComponent, + UwtInspectorFilterBarComponent + ], + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: './uwt-inspector-timeline.component.html', + styleUrl: './uwt-inspector-timeline.component.scss' +}) +export class UwtInspectorTimelineComponent { + protected readonly store = inject(UwtInspectorStore); + private readonly exporter = inject(UwtInspectorExporter); + private readonly host: ElementRef = inject(ElementRef); + private readonly injector = inject(Injector); + private readonly announcer = inject(CPS_LIVE_ANNOUNCER_SERVICE, { + optional: true + }); + + private readonly table = viewChild.required(CpsTableComponent); + + /** JSON length, in characters, above which a payload is shown truncated. */ + readonly maxPayloadChars = input( + UWT_DEFAULT_INSPECTOR_CONFIG.maxPayloadCharsInView + ); + + /** Each kind with how many of its events match the filters. */ + protected readonly kindOptions = computed(() => { + const counts = this.store.matchCounts(); + const all = UWT_INSPECTOR_SECTIONS.reduce((n, id) => n + counts[id], 0); + return [ + { value: 'all', label: `All ${all}` }, + ...UWT_INSPECTOR_SECTIONS.map((id) => ({ + value: id, + label: `${KIND_LABEL[id]} ${counts[id]}` + })) + ]; + }); + + /** + * Each entry's row, made once: the table keeps a row's elements only while + * it gets the same object, so a new one would redraw it as events arrive, + * taking the focus and an open payload with it. + */ + private readonly rowOf = new WeakMap(); + + protected readonly rows = computed(() => + this.store.filtered().map((entry) => { + let row = this.rowOf.get(entry); + if (!row) { + row = uwtInspectorRow(entry); + this.rowOf.set(entry, row); + } + return row; + }) + ); + + /** + * The paginator's offset. Back to the first page, where the newest events + * are, when the kind or filters change, or when the rows shrink past it, + * as after a clear; otherwise kept while events arrive. The table won't + * do this itself: PrimeNG steps back one page at most, so page four + * filtered down to one page would show an empty table. + */ + protected readonly first = linkedSignal({ + source: () => ({ + rows: this.rows().length, + kind: this.store.kind(), + filters: this.store.filters() + }), + computation: (source, previous): number => + previous && + previous.source.kind === source.kind && + previous.source.filters === source.filters && + previous.value < source.rows + ? previous.value + : 0 + }); + + /** Rows per page, as the reader last chose. */ + private readonly pageSize = signal(25); + + /** The log events captured, to link an error back to its own. */ + protected readonly logSequences = computed( + () => new Set(this.store.entries().logging.map((e) => e.event.sequence)) + ); + + /** Each log event's sequence, to the error that mirrored it to RUM. */ + protected readonly mirroredTo = computed(() => { + const mirrors = new Map(); + for (const { event } of this.store.entries().logging) { + if (event.kind === 'error' && event.relatedSequence) { + mirrors.set(event.relatedSequence, event.sequence); + } + } + return mirrors; + }); + + protected readonly emptyMessage = computed(() => { + const kind = this.store.kind(); + const noun = uwtInspectorEventsNoun(kind); + const captured = + kind === 'all' ? this.store.total() : this.store.entries()[kind].length; + return captured > 0 ? `No ${noun} match the filters.` : `No ${noun} yet.`; + }); + + /** The newest event shown when the reader was last on the first page. */ + private readonly seenUpTo = linkedSignal< + { first: number; newest: number }, + number + >({ + source: () => ({ + first: this.first(), + newest: this.rows()[0]?.sequence ?? 0 + }), + computation: (now, before) => + now.first === 0 ? now.newest : (before?.value ?? now.newest) + }); + + /** Events above the page being read that arrived since it was left. */ + protected readonly unseenNew = computed( + () => + this.rows() + .slice(0, this.first()) + .filter((r) => r.sequence > this.seenUpTo()).length + ); + + protected readonly copyState = signal<'idle' | 'copied' | 'failed'>('idle'); + protected readonly exportOpen = signal(false); + /** What the Export menu says it holds; set as it opens. */ + protected readonly exportHeader = signal(''); + + // No descriptions: cps-menu draws them below text contrast. + protected readonly exportItems: CpsMenuItem[] = [ + { title: 'Copy JSON', icon: 'copy', action: () => this.copy() }, + { title: 'Download JSON', icon: 'download', action: () => this.download() } + ]; + + protected setKind(kind: UwtInspectorKindFilter): void { + this.store.setKind(kind); + } + + protected onPage(state: { first?: number; rows?: number }): void { + this.first.set(state?.first ?? 0); + this.pageSize.set(state?.rows ?? this.pageSize()); + } + + /** Adds a filter on a value from an open row, unless it's there already. */ + protected filterBy(field: UwtInspectorFilterBy): void { + const exists = this.store + .filters() + .fields.some( + (f) => + f.path === field.path && + f.operator === 'equals' && + f.value === field.value + ); + if (!exists) { + this.store.addFilter({ + path: field.path, + operator: 'equals', + value: field.value + }); + } + } + + /** + * Shows an event's row, open: clears the search and filters if they hide + * it, turns to its page, opens it, and moves focus to its payload. + */ + protected reveal(sequence: number): void { + if (!this.rows().some((r) => r.sequence === sequence)) { + this.store.clearFilters(); + this.announce(`Filters cleared to show event #${sequence}.`); + } + const rows = this.rows(); + const index = rows.findIndex((r) => r.sequence === sequence); + if (index < 0) { + return; + } + const size = this.pageSize(); + this.first(); // settles the page reset that clearing the filters makes + this.first.set(Math.floor(index / size) * size); + const table = this.table().primengTable; + if (!table.isRowExpanded(rows[index])) { + table.toggleRow(rows[index]); + } + afterNextRender( + () => { + const content = this.host.nativeElement.querySelector( + `[data-testid="cps-table-row-expanded-content-${sequence}"]` + ); + // Optional: some environments, such as jsdom, have no scrolling. + content?.previousElementSibling?.scrollIntoView?.({ block: 'start' }); + content + ?.querySelector('pre') + ?.focus({ preventScroll: true }); + }, + { injector: this.injector } + ); + } + + /** To the first page, whose notice then goes: focus to the newest event. */ + protected goToNewest(): void { + this.first.set(0); + afterNextRender( + () => + this.host.nativeElement + .querySelector( + '[data-testid^="cps-table-row-expand-btn-"]' + ) + ?.focus(), + { injector: this.injector } + ); + } + + protected toggleExport(menu: CpsMenuComponent, event: Event): void { + const kind = this.store.kind(); + this.exportHeader.set( + `${uwtInspectorEventCount(this.store.count(kind).captured, kind)}, with session and user ids` + ); + menu.toggle(event); + } + + protected isFresh(sequence: number): boolean { + return this.store.fresh().has(sequence); + } + + private announce(message: string): void { + try { + this.announcer?.announce(message, 'polite'); + } catch { + // Announcements are a courtesy; never let one break the inspector. + } + } + + private download(): void { + this.exporter.download(this.store.kind()); + } + + private async copy(): Promise { + this.copyState.set( + (await this.exporter.copy(this.store.kind())) ? 'copied' : 'failed' + ); + setTimeout(() => this.copyState.set('idle'), 2000); + } +} diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-window/uwt-inspector-window.spec.ts b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-window/uwt-inspector-window.spec.ts new file mode 100644 index 0000000..41769b8 --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-window/uwt-inspector-window.spec.ts @@ -0,0 +1,303 @@ +import { ApplicationRef, EnvironmentInjector } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; +import { provideNoopAnimations } from '@angular/platform-browser/animations'; +import { UWT_TELEMETRY_IDENTITY } from '@absaoss-cps/ngx-ui-watchtower'; +import { UwtInspectorSession } from '../uwt-inspector-session/uwt-inspector-session'; +import { + UWT_INSPECTOR_WINDOW_NAME, + uwtOpenInspectorWindow, + UwtInspectorWindowOptions +} from './uwt-inspector-window'; + +/** A popup window: an iframe's real window, its geometry pinned. */ +function fakePopup() { + const frame = document.body.appendChild(document.createElement('iframe')); + const popup = frame.contentWindow as Window & typeof globalThis; + const doc = popup.document; + doc.head.innerHTML = ''; + const geometry = { + innerWidth: 900, + innerHeight: 700, + screenX: 5, + screenY: 6 + }; + for (const [name, value] of Object.entries(geometry)) { + Object.defineProperty(popup, name, { value, configurable: true }); + } + jest.spyOn(popup, 'focus').mockImplementation(() => undefined); + jest.spyOn(popup, 'close').mockImplementation(() => undefined); + // A spy that still removes, through the real window: the popup the code + // sees is a proxy, which a window's own methods would not accept as `this`. + const remove = popup.removeEventListener.bind(popup); + jest + .spyOn(popup, 'removeEventListener') + .mockImplementation((type, listener, options) => + remove(type, listener, options) + ); + // What a browser does to a window: a reload gives it a new document, and + // closing it makes it `closed`. Everything else is the iframe's window. + let current: Document = doc; + let closed = false; + const shown = new Proxy(popup, { + get(target, name) { + if (name === 'document') { + return current; + } + if (name === 'closed') { + return closed; + } + const value = Reflect.get(target, name, target); + // The spies are the test's to see; any other method is the window's. + return typeof value === 'function' && !jest.isMockFunction(value) + ? value.bind(target) + : value; + } + }); + return { + popup: shown, + doc, + fire: (type: string) => popup.dispatchEvent(new popup.Event(type)), + key: () => + current.body.dispatchEvent( + new popup.KeyboardEvent('keydown', { bubbles: true }) + ), + /** The page hides, and a new, empty document takes its place. */ + reload: () => { + popup.dispatchEvent(new popup.Event('pagehide')); + // A document with a window of its own, as a browser's is. + current = document.body.appendChild( + document.createElement('iframe') + ).contentDocument!; + return current; + }, + /** The page hides, and the window goes. */ + close: () => { + popup.dispatchEvent(new popup.Event('pagehide')); + closed = true; + } + }; +} + +describe('uwtOpenInspectorWindow', () => { + let options: UwtInspectorWindowOptions; + let appStyle: HTMLStyleElement; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + provideNoopAnimations(), + { + provide: UWT_TELEMETRY_IDENTITY, + useValue: { application: 'shop', environment: 'test', version: '1' } + } + ] + }); + appStyle = document.head.appendChild(document.createElement('style')); + appStyle.textContent = '.app {}'; + options = { + document, + environment: TestBed.inject(EnvironmentInjector), + appRef: TestBed.inject(ApplicationRef), + title: 'Event Inspector · shop', + onClosed: jest.fn(), + onReloaded: jest.fn(), + onKeydown: jest.fn(), + onGeometry: jest.fn() + }; + }); + + afterEach(() => { + appStyle.remove(); + document.querySelectorAll('iframe').forEach((frame) => frame.remove()); + TestBed.inject(UwtInspectorSession).end(); + jest.restoreAllMocks(); + }); + + it('should open the named window, titled, with the app styles, showing the inspector', () => { + const { popup, doc } = fakePopup(); + const open = jest.spyOn(window, 'open').mockReturnValue(popup); + + const host = uwtOpenInspectorWindow(options); + + expect(host?.dock).toBe('window'); + expect(open).toHaveBeenCalledWith( + '', + UWT_INSPECTOR_WINDOW_NAME, + expect.stringMatching(/^popup,width=\d+,height=\d+$/) + ); + expect(doc.title).toBe('Event Inspector · shop'); + const icon = doc.querySelector('link[rel="icon"]'); + expect(icon?.type).toBe('image/svg+xml'); + expect(decodeURIComponent(icon?.href ?? '')).toMatch( + /^data:image\/svg\+xml,$/ + ); + expect(doc.head.textContent).toContain('.app {}'); + expect(doc.head.textContent).not.toContain('.stale'); + expect(doc.querySelector('.uwt-inspector__title')?.textContent).toBe( + 'Event Inspector' + ); + expect( + doc.querySelector('[aria-pressed="true"]')?.getAttribute('aria-label') + ).toBe('Separate window'); + host?.destroy(); + }); + + it('should keep the window in step as the app adds and removes styles', async () => { + const { popup, doc } = fakePopup(); + jest.spyOn(window, 'open').mockReturnValue(popup); + const host = uwtOpenInspectorWindow(options); + + const later = document.head.appendChild(document.createElement('style')); + later.textContent = '.later {}'; + await Promise.resolve(); + expect(doc.head.textContent).toContain('.later {}'); + + later.remove(); + await Promise.resolve(); + expect(doc.head.textContent).not.toContain('.later {}'); + host?.destroy(); + }); + + it('should open where it was last', () => { + const { popup } = fakePopup(); + const open = jest.spyOn(window, 'open').mockReturnValue(popup); + + uwtOpenInspectorWindow({ + ...options, + geometry: { width: 800, height: 600, left: 10, top: 20 } + })?.destroy(); + + expect(open.mock.calls[0][2]).toMatch(/,left=10,top=20$/); + }); + + it('should pass on key presses, and report its closing with its geometry', () => { + jest.useFakeTimers(); + const { popup, close, key } = fakePopup(); + jest.spyOn(window, 'open').mockReturnValue(popup); + const host = uwtOpenInspectorWindow(options); + + key(); + expect(options.onKeydown).toHaveBeenCalledTimes(1); + + close(); + expect(options.onGeometry).toHaveBeenCalledWith({ + width: 900, + height: 700, + left: 5, + top: 6 + }); + // A reload hides the page too, so the window is looked at once it settles. + jest.advanceTimersByTime(100); + expect(options.onClosed).toHaveBeenCalledTimes(1); + expect(options.onReloaded).not.toHaveBeenCalled(); + + host?.destroy(); + expect(options.onGeometry).toHaveBeenCalledTimes(1); + jest.useRealTimers(); + }); + + it('should stay open, showing the inspector again, when the window is reloaded', () => { + jest.useFakeTimers(); + const { popup, reload, key } = fakePopup(); + jest.spyOn(window, 'open').mockReturnValue(popup); + const host = uwtOpenInspectorWindow(options); + + const doc = reload(); + // Reported before the inspector is drawn again, for a fresh one. + let drawnWhenReported: boolean | undefined; + jest.mocked(options.onReloaded).mockImplementation(() => { + drawnWhenReported = !!doc.querySelector('.uwt-inspector__title'); + }); + jest.advanceTimersByTime(100); + + expect(options.onClosed).not.toHaveBeenCalled(); + expect(options.onReloaded).toHaveBeenCalledTimes(1); + expect(drawnWhenReported).toBe(false); + expect(doc.title).toBe('Event Inspector · shop'); + expect(doc.head.textContent).toContain('.app {}'); + expect(doc.querySelector('.uwt-inspector__title')?.textContent).toBe( + 'Event Inspector' + ); + expect(doc.querySelector('link[rel="icon"]')).not.toBeNull(); + key(); + expect(options.onKeydown).toHaveBeenCalledTimes(1); + host?.destroy(); + jest.useRealTimers(); + }); + + it('should keep the geometry of the window it shows again, for when it closes', () => { + jest.useFakeTimers(); + const { popup, reload } = fakePopup(); + jest.spyOn(window, 'open').mockReturnValue(popup); + const host = uwtOpenInspectorWindow(options); + + reload(); + jest.advanceTimersByTime(100); + expect(options.onGeometry).toHaveBeenCalledTimes(1); + + host?.destroy(); + expect(options.onGeometry).toHaveBeenCalledTimes(2); + jest.useRealTimers(); + }); + + it('should report its closing when the window never comes back', () => { + jest.useFakeTimers(); + const { popup, fire } = fakePopup(); + jest.spyOn(window, 'open').mockReturnValue(popup); + const host = uwtOpenInspectorWindow(options); + + fire('pagehide'); + // The inspector has timers of its own: the wait is advanced, not run out. + jest.advanceTimersByTime(5000); + + expect(options.onClosed).toHaveBeenCalledTimes(1); + host?.destroy(); + jest.useRealTimers(); + }); + + it('should close the window again when it fails to show the inspector', () => { + const { popup } = fakePopup(); + jest.spyOn(window, 'open').mockReturnValue(popup); + jest.spyOn(options.appRef, 'attachView').mockImplementationOnce(() => { + throw new Error('no kit'); + }); + + expect(() => uwtOpenInspectorWindow(options)).toThrow('no kit'); + expect(popup.close).toHaveBeenCalled(); + }); + + it('should close the window quietly when the app is destroyed first', () => { + const { popup } = fakePopup(); + jest.spyOn(window, 'open').mockReturnValue(popup); + const host = uwtOpenInspectorWindow(options); + const warn = jest.spyOn(console, 'warn'); + + TestBed.resetTestingModule(); + host?.destroy(); + + expect(popup.close).toHaveBeenCalled(); + expect(warn).not.toHaveBeenCalled(); + }); + + it('should close the window, and stop listening, when destroyed', () => { + const { popup, key } = fakePopup(); + jest.spyOn(window, 'open').mockReturnValue(popup); + + uwtOpenInspectorWindow(options)?.destroy(); + + expect(popup.close).toHaveBeenCalled(); + expect(popup.removeEventListener).toHaveBeenCalledWith( + 'pagehide', + expect.any(Function) + ); + expect(options.onGeometry).toHaveBeenCalledTimes(1); + key(); + expect(options.onKeydown).not.toHaveBeenCalled(); + }); + + it('should give up when the browser blocks the window', () => { + jest.spyOn(window, 'open').mockReturnValue(null); + + expect(uwtOpenInspectorWindow(options)).toBeUndefined(); + }); +}); diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-window/uwt-inspector-window.ts b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-window/uwt-inspector-window.ts new file mode 100644 index 0000000..3e580cc --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector-window/uwt-inspector-window.ts @@ -0,0 +1,286 @@ +import { DOCUMENT } from '@angular/common'; +import { + ApplicationRef, + ComponentRef, + createComponent, + createEnvironmentInjector, + EnvironmentInjector +} from '@angular/core'; +import { + CPS_LIVE_ANNOUNCER_SERVICE, + CpsLiveAnnouncerService, + CpsNotificationService +} from 'cps-ui-kit'; +import { UwtInspectorComponent } from '../uwt-inspector/uwt-inspector.component'; +import { UwtInspectorHost } from '../uwt-inspector-session/uwt-inspector-session'; +import { UwtInspectorWindowGeometry } from '../uwt-inspector-preferences/uwt-inspector-preferences'; +import { + uwtSafe, + uwtSafeVoid +} from '../uwt-inspector-internal.util/uwt-inspector-internal.util'; + +/** The window's name, so a second open reuses it rather than adding one. */ +export const UWT_INSPECTOR_WINDOW_NAME = 'ngx-ui-watchtower-event-inspector'; + +/** + * The window's name for an app: its own, so apps sharing a page — a shell + * and its fragments — don't take over each other's window. + */ +export function uwtInspectorWindowName(application?: string): string { + return application + ? `${UWT_INSPECTOR_WINDOW_NAME}:${application}` + : UWT_INSPECTOR_WINDOW_NAME; +} + +const STYLES = 'style, link[rel~="stylesheet"], base'; + +/** + * A window's page hides when the window closes, and when it reloads. Which it + * was is seen a moment later: a window that is still there, with a new + * document, was reloaded. The wait is bounded; a window that has neither + * closed nor come back in that time is treated as closed. + */ +const SETTLE_EVERY_MS = 50; +const SETTLE_TRIES = 20; + +export interface UwtInspectorWindowOptions { + /** The app's document. */ + document: Document; + environment: EnvironmentInjector; + appRef: ApplicationRef; + title: string; + /** The window's name; {@link UWT_INSPECTOR_WINDOW_NAME} by default. */ + name?: string; + /** Where the window was last, if anywhere. */ + geometry?: UwtInspectorWindowGeometry; + /** The user closed the window. */ + onClosed: () => void; + /** + * The user reloaded the window: as closing it and opening it again, so + * the inspector drawn in its new document starts afresh. + */ + onReloaded: () => void; + /** Every key pressed in the window, for the shortcut. */ + onKeydown: (event: KeyboardEvent) => void; + /** The window's size and position, as it closes. */ + onGeometry: (geometry: UwtInspectorWindowGeometry) => void; +} + +/** + * Shows the inspector in its own browser window, rendered by this app into + * the window's document. The window gets the app's styles, kept in step as + * Angular adds more, and its own `DOCUMENT`, notifications and screen + * reader announcer, so cps-ui-kit's tooltips, menus and messages appear in + * it rather than in the app. + * + * Must run during a click or key press, or the browser blocks the window. + * + * @returns the host, or `undefined` when the browser blocked the window + */ +export function uwtOpenInspectorWindow( + options: UwtInspectorWindowOptions +): UwtInspectorHost | undefined { + const view = options.document.defaultView; + const popup = view?.open( + '', + options.name ?? UWT_INSPECTOR_WINDOW_NAME, + features(view, options.geometry) + ); + if (!view || !popup) { + return undefined; + } + + /** The inspector, drawn into one document of the window: a reload gives it another. */ + interface Mounted { + doc: Document; + ref: ComponentRef; + environment: EnvironmentInjector; + stopMirroring: () => void; + } + + const mount = (doc: Document): Mounted => { + // A window left over from before the app reloaded holds stale content. + doc.head.replaceChildren(); + doc.body.replaceChildren(); + doc.title = options.title; + doc.body.style.margin = '0'; + const stopMirroring = mirrorStyles(options.document, doc); + + const element = doc.body.appendChild(doc.createElement('div')); + element.style.height = '100vh'; + const environment = createEnvironmentInjector( + [ + { provide: DOCUMENT, useValue: doc }, + CpsNotificationService, + { + provide: CPS_LIVE_ANNOUNCER_SERVICE, + useClass: CpsLiveAnnouncerService + } + ], + options.environment + ); + let created: ComponentRef | undefined; + try { + created = createComponent(UwtInspectorComponent, { + environmentInjector: environment, + hostElement: element + }); + created.setInput('dock', 'window'); + options.appRef.attachView(created.hostView); + created.changeDetectorRef.detectChanges(); + setIcon(doc); + } catch (error) { + created?.destroy(); + environment.destroy(); + stopMirroring(); + throw error; + } + doc.addEventListener('keydown', options.onKeydown, true); + return { doc, ref: created, environment, stopMirroring }; + }; + + const unmount = (mounted: Mounted) => { + mounted.doc.removeEventListener('keydown', options.onKeydown, true); + mounted.stopMirroring(); + // Destroying the view detaches it from the app, unless the app, being + // destroyed, already has: detaching it here would warn (NG0406). + mounted.ref.destroy(); + mounted.environment.destroy(); + }; + + let current: Mounted | undefined; + try { + current = mount(popup.document); + } catch (error) { + // Half open: close it again, for the caller to open a dialog instead. + popup.close(); + throw error; + } + + let saved = false; + const saveGeometry = () => { + if (!saved) { + saved = true; + options.onGeometry({ + width: popup.innerWidth, + height: popup.innerHeight, + left: popup.screenX, + top: popup.screenY + }); + } + }; + + let settling: ReturnType | undefined; + /** Closed, or reloaded: a reload gets the inspector drawn afresh in its new document. */ + const settle = (triesLeft: number) => { + settling = undefined; + if (popup.closed) { + options.onClosed(); + return; + } + const next = uwtSafe('window.document', () => popup.document, undefined); + if (next?.body && next !== current?.doc) { + if (current) { + unmount(current); + current = undefined; + } + try { + // Between the old view and the new: neither holds the history then. + options.onReloaded(); + current = mount(next); + // Its size is saved again when it next closes. + saved = false; + popup.addEventListener('pagehide', onPageHide); + } catch { + options.onClosed(); + } + } else if (triesLeft > 0) { + settling = setTimeout(() => settle(triesLeft - 1), SETTLE_EVERY_MS); + } else { + options.onClosed(); + } + }; + const onPageHide = () => { + // The size is gone once the window is, so it is taken now. + saveGeometry(); + settling ??= setTimeout(() => settle(SETTLE_TRIES), SETTLE_EVERY_MS); + }; + const closeWithApp = () => popup.close(); + popup.addEventListener('pagehide', onPageHide); + view.addEventListener('pagehide', closeWithApp); + + return { + dock: 'window', + focus: () => { + popup.focus(); + current?.ref.instance.focusInspector(); + }, + containsFocus: () => current?.doc.hasFocus() ?? false, + destroy: () => { + clearTimeout(settling); + settling = undefined; + popup.removeEventListener('pagehide', onPageHide); + view.removeEventListener('pagehide', closeWithApp); + uwtSafeVoid('window.geometry', saveGeometry); + if (current) { + unmount(current); + current = undefined; + } + popup.close(); + } + }; +} + +/** + * Gives the window the inspector's own mark as its icon, taken from the logo + * it drew: an app's icon, if any, would say nothing about this window, and a + * library can't count on serving a file. + */ +function setIcon(doc: Document): void { + const logo = doc.querySelector('uwt-inspector-logo svg'); + if (logo) { + const link = doc.head.appendChild(doc.createElement('link')); + link.rel = 'icon'; + link.type = 'image/svg+xml'; + link.href = `data:image/svg+xml,${encodeURIComponent(new XMLSerializer().serializeToString(logo))}`; + } +} + +/** 1200×800 at most the screen the first time, then where it was last. */ +function features(view: Window, geometry?: UwtInspectorWindowGeometry): string { + const width = Math.min(geometry?.width ?? 1200, view.screen.availWidth); + const height = Math.min(geometry?.height ?? 800, view.screen.availHeight); + const position = geometry ? `,left=${geometry.left},top=${geometry.top}` : ''; + return `popup,width=${width},height=${height}${position}`; +} + +/** + * Copies the app's stylesheets into the window, then keeps adding and + * removing copies as the app's `` changes — Angular adds a + * component's styles only when it first renders. + */ +function mirrorStyles(from: Document, to: Document): () => void { + const copies = new Map(); + const add = (node: Node) => { + if ( + node.nodeType === Node.ELEMENT_NODE && + (node as Element).matches(STYLES) + ) { + const copy = to.importNode(node as Element, true); + copies.set(node, copy); + to.head.appendChild(copy); + } + }; + from.head.childNodes.forEach(add); + const observer = new MutationObserver((records) => { + for (const record of records) { + record.addedNodes.forEach(add); + record.removedNodes.forEach((node) => { + copies.get(node)?.remove(); + copies.delete(node); + }); + } + }); + observer.observe(from.head, { childList: true }); + return () => observer.disconnect(); +} diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector.models/uwt-inspector.models.ts b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector.models/uwt-inspector.models.ts new file mode 100644 index 0000000..1ca8c7b --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector.models/uwt-inspector.models.ts @@ -0,0 +1,198 @@ +import { InjectionToken } from '@angular/core'; +import { UwtTelemetryObservedEvent } from '@absaoss-cps/ngx-ui-watchtower'; + +/** + * The kinds of event the Event Inspector tells apart: BI events (with any + * forwarded event of a type it doesn't recognise), scenarios and their + * steps, and logs (with errors mirrored to RUM). + * + * @group Types + */ +export type UwtInspectorSectionId = 'bi' | 'scenario' | 'logging'; + +/** + * What the Event Inspector's timeline shows, and exports: every kind, or + * one. + * + * @group Types + */ +export type UwtInspectorKindFilter = 'all' | UwtInspectorSectionId; + +/** + * Where the Event Inspector opens: a dialog over the app, its own browser + * window, or a pane beside or below the app. + * + * @group Types + */ +export type UwtInspectorDock = + 'dialog' | 'window' | 'left' | 'right' | 'bottom'; + +/** Every dock, in the order the switcher shows them. */ +export const UWT_INSPECTOR_DOCKS: readonly UwtInspectorDock[] = [ + 'dialog', + 'window', + 'left', + 'bottom', + 'right' +]; + +/** The docks that are panes beside or below the app. */ +export type UwtInspectorPaneSide = Extract< + UwtInspectorDock, + 'left' | 'right' | 'bottom' +>; + +/** + * A key combination that opens the Event Inspector. + * + * Matched on `KeyboardEvent.code` — the physical key, named for its place on + * a US QWERTY keyboard — so it isn't affected by what Option or Shift + * produce. A modifier left out must be *up*, so extra modifiers never match. + * Without Ctrl or ⌘, a key that types a character doesn't count in a text + * field. + * + * @group Interfaces + */ +export interface UwtInspectorShortcut { + /** `KeyboardEvent.code`, e.g. `'Digit8'` or `'KeyD'`. */ + code: string; + + /** + * A key pressed just before `code`, with the modifiers still held: + * `'KeyE'` in Shift+E+I. Without it, `code` alone is pressed. + */ + after?: string; + + ctrl?: boolean; + alt?: boolean; + shift?: boolean; + meta?: boolean; +} + +/** + * Configuration for {@link provideUwtEventInspector}. + * + * @group Interfaces + */ +export interface UwtEventInspectorConfig { + /** + * Whether the popup can be opened at all. A function is read once, at + * startup — use it to limit access to, say, support staff. + */ + enabled: boolean | (() => boolean); + + /** + * Shortcuts that open the popup. An empty array disables the keyboard, + * leaving {@link UwtEventInspectorService.open} as the only way in. + */ + shortcuts: readonly UwtInspectorShortcut[]; + + /** + * Events kept per kind (BI, scenarios, logs); the oldest of that kind are + * dropped beyond this, so a burst of one kind never pushes out another. + */ + maxEventsPerSection: number; + + /** + * Payloads whose JSON is longer than this many characters are shown + * truncated to this many, at most 65,536. The download always contains + * them in full. + * + * Characters (UTF-16 code units, JavaScript's `length`), not bytes: the + * limit protects rendering, which costs by the length of the text. + */ + maxPayloadCharsInView: number; + + /** + * Events of each kind kept from the app's start, so the popup shows what + * happened before it opened; at most `maxEventsPerSection`. `0`, the + * default, keeps none: the popup shows only what follows its opening. + * + * Above `0`, every event handed over is copied for it from startup, with + * the popup closed too — a cost the default avoids. Closing the popup + * keeps them coming; Clear empties them. + */ + captureBeforeOpen: number; +} + +/** @group Tokens */ +export const UWT_EVENT_INSPECTOR_CONFIG = + new InjectionToken('UWT_EVENT_INSPECTOR_CONFIG'); + +/** A flattened leaf of an event, used for filtering. */ +export interface UwtInspectorField { + /** Exact path, e.g. `steps[2].name`. */ + path: string; + + /** Path with array indexes removed, e.g. `steps[].name`. */ + pattern: string; + + /** The value as text. */ + value: string; +} + +/** One captured event, prepared once for display and filtering. */ +export interface UwtInspectorEntry { + event: UwtTelemetryObservedEvent; + section: UwtInspectorSectionId; + fields: readonly UwtInspectorField[]; + /** Length of the payload's JSON, in characters (UTF-16 code units). */ + sizeChars: number; + /** Set when the event could not be prepared for display. */ + renderError?: string; +} + +/** + * How a field filter compares. + * + * @group Types + */ +export type UwtInspectorFilterOperator = + 'contains' | 'equals' | 'not-contains' | 'exists' | 'missing'; + +/** @group Interfaces */ +export interface UwtInspectorFieldFilter { + id: string; + path: string; + operator: UwtInspectorFilterOperator; + value: string; +} + +/** @group Interfaces */ +export interface UwtInspectorFilterState { + /** Free text, matched against every value and path. */ + text: string; + fields: readonly UwtInspectorFieldFilter[]; +} + +/** + * An exported JSON file: the captured events of the kind shown, or of + * every kind (`section: 'all'`). + * + * `events` holds every event captured of that kind, oldest first, + * regardless of the search and filters on screen. `activeFilters` records + * them, for context; they were not applied to `events`. + * + * Version 2 exports the timeline; version 1 exported one of three sections + * and never had `'all'`. + * + * @group Interfaces + */ +export interface UwtInspectorExport { + format: 'ngx-ui-watchtower-event-inspector'; + formatVersion: 2; + section: UwtInspectorKindFilter; + exportedAt: string; + capture: { startedAt: string; endedAt: string; paused: boolean }; + app: { + application: string; + environment: string; + version: string; + eventNamespace: string; + }; + session: { sessionId?: string; userId?: string }; + userAgent: string; + activeFilters: UwtInspectorFilterState; + count: { captured: number; droppedOldest: number }; + events: UwtTelemetryObservedEvent[]; +} diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector.providers/uwt-inspector.providers.spec.ts b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector.providers/uwt-inspector.providers.spec.ts new file mode 100644 index 0000000..dc98197 --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector.providers/uwt-inspector.providers.spec.ts @@ -0,0 +1,49 @@ +import { UWT_DEFAULT_INSPECTOR_SHORTCUTS } from '../uwt-inspector-shortcut/uwt-inspector-shortcut'; +import { UWT_DEFAULT_INSPECTOR_CONFIG } from '../uwt-inspector-store/uwt-inspector-store'; +import { uwtInspectorConfig } from './uwt-inspector.providers'; + +describe('uwtInspectorConfig', () => { + it('should lay the overrides over the defaults', () => { + const shortcuts = [{ code: 'F9' }]; + + expect( + uwtInspectorConfig({ shortcuts, maxEventsPerSection: 50, enabled: false }) + ).toEqual({ + ...UWT_DEFAULT_INSPECTOR_CONFIG, + shortcuts, + maxEventsPerSection: 50, + enabled: false + }); + }); + + it('should keep the default for an option left undefined', () => { + expect( + uwtInspectorConfig({ + enabled: undefined, + shortcuts: undefined, + maxEventsPerSection: undefined, + captureBeforeOpen: undefined + }) + ).toEqual({ + ...UWT_DEFAULT_INSPECTOR_CONFIG, + shortcuts: UWT_DEFAULT_INSPECTOR_SHORTCUTS + }); + }); + + it('should take counts as whole numbers, keeping the default for one it cannot use', () => { + expect( + uwtInspectorConfig({ + maxEventsPerSection: 2.5, + captureBeforeOpen: -1, + maxPayloadCharsInView: Number.NaN + }) + ).toMatchObject({ + maxEventsPerSection: 2, + captureBeforeOpen: UWT_DEFAULT_INSPECTOR_CONFIG.captureBeforeOpen, + maxPayloadCharsInView: UWT_DEFAULT_INSPECTOR_CONFIG.maxPayloadCharsInView + }); + expect(uwtInspectorConfig({ maxEventsPerSection: 0 })).toMatchObject({ + maxEventsPerSection: UWT_DEFAULT_INSPECTOR_CONFIG.maxEventsPerSection + }); + }); +}); diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector.providers/uwt-inspector.providers.ts b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector.providers/uwt-inspector.providers.ts new file mode 100644 index 0000000..323594e --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector.providers/uwt-inspector.providers.ts @@ -0,0 +1,87 @@ +import { + EnvironmentProviders, + inject, + makeEnvironmentProviders, + provideAppInitializer +} from '@angular/core'; +import { + UWT_EVENT_INSPECTOR_CONFIG, + UwtEventInspectorConfig +} from '../uwt-inspector.models/uwt-inspector.models'; +import { UWT_DEFAULT_INSPECTOR_CONFIG } from '../uwt-inspector-store/uwt-inspector-store'; +import { UWT_DEFAULT_INSPECTOR_SHORTCUTS } from '../uwt-inspector-shortcut/uwt-inspector-shortcut'; +import { UwtEventInspectorService } from '../uwt-event-inspector.service/uwt-event-inspector.service'; + +/** A whole number of at least `min`, or `undefined` to keep the default. */ +function count(value: unknown, min: number): number | undefined { + return typeof value === 'number' && Number.isFinite(value) && value >= min + ? Math.floor(value) + : undefined; +} + +/** + * The config in effect: the app's overrides laid over the defaults, except + * one left `undefined`, or a count that isn't a usable number, which keeps + * its default rather than break the app. + */ +export function uwtInspectorConfig( + config: Partial +): UwtEventInspectorConfig { + const overrides: Partial = { + ...config, + shortcuts: Array.isArray(config.shortcuts) ? config.shortcuts : undefined, + maxEventsPerSection: count(config.maxEventsPerSection, 1), + maxPayloadCharsInView: count(config.maxPayloadCharsInView, 0), + captureBeforeOpen: count(config.captureBeforeOpen, 0) + }; + return { + ...UWT_DEFAULT_INSPECTOR_CONFIG, + shortcuts: UWT_DEFAULT_INSPECTOR_SHORTCUTS, + ...(Object.fromEntries( + Object.entries(overrides).filter(([, value]) => value !== undefined) + ) as Partial) + }; +} + +/** + * Adds the Event Inspector: an in-app window showing, live, + * every BI, scenario and log event this app hands to its destinations. + * + * Opens with Shift+E+I by default — hold Shift, press E, then I — in every + * environment. Needs cps-ui-kit set up in the app, as for any cps-ui-kit + * dialog. + * + * @example + * ```typescript + * import { provideUwtEventInspector } from '@absaoss-cps/ngx-ui-watchtower/event-inspector'; + * + * providers: [ + * provideUwtTelemetry({ application: 'my-app', environment, version }), + * provideUwtEventInspector() + * ] + * + * // Or a different key, or access limited to some users: + * provideUwtEventInspector({ + * shortcuts: [{ code: 'KeyD', ctrl: true, alt: true, shift: true }], + * enabled: () => inject(AuthService).isSupportStaff() + * }) + * ``` + * + * @param config overrides merged over the defaults + * @returns providers for the popup and its shortcut + * + * @group Utils + */ +export function provideUwtEventInspector( + config: Partial = {} +): EnvironmentProviders { + return makeEnvironmentProviders([ + { + provide: UWT_EVENT_INSPECTOR_CONFIG, + useValue: uwtInspectorConfig(config) + }, + provideAppInitializer(() => { + inject(UwtEventInspectorService).install(); + }) + ]); +} diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector/uwt-inspector.component.html b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector/uwt-inspector.component.html new file mode 100644 index 0000000..21a5c43 --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector/uwt-inspector.component.html @@ -0,0 +1,117 @@ +@if (framed()) { +
+
+ +

Event Inspector

+ + +
+ + +
+} @else { + + + + +} + +
+
+
+

+ + @if (pausedAt(); as at) { + Paused at {{ at }} · capturing since {{ since() }} · + } @else { + Capturing since {{ since() }} · + } + {{ store.total() }} event{{ store.total() === 1 ? '' : 's' }} + @if (dropped() > 0) { + · {{ dropped() }} oldest dropped + } +

+ + +
+
+ @if (dock() === 'dialog' && !maximized()) { + + + } + + + + +
+
+ + @if (store.paused() && store.newWhilePaused() > 0) { + +
+ {{ store.newWhilePaused() }} new event{{ + store.newWhilePaused() === 1 ? '' : 's' + }} + held back while paused. + + +
+ } + + @if (store.total() === 0 && !store.paused()) { +

+ Use the app to generate events; they appear here as they are sent. +

+ } + + + +
diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector/uwt-inspector.component.scss b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector/uwt-inspector.component.scss new file mode 100644 index 0000000..13d32eb --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector/uwt-inspector.component.scss @@ -0,0 +1,184 @@ +@use 'cps-ui-kit/styles/mixins' as cps; + +:host { + // One spacing scale for the whole inspector, so that what belongs together + // sits closer than what doesn't: `s` inside a cluster of controls, `m` + // between controls and between their rows, `l` between groups, `pad` from + // the edge of a pane. + --uwt-inspector-space-s: 0.5rem; + --uwt-inspector-space-m: 0.75rem; + --uwt-inspector-space-l: 1.5rem; + --uwt-inspector-pad: 1.25rem; + + display: block; + color: var(--cps-text-primary); +} + +// What the shortcut focuses (see focusInspector()), with cps-ui-kit's ring in +// place of the browser's outline. A dialog has padding for it to sit in; a +// pane or window has none, and would cut it off, so there it goes inside. +:host(:focus-visible) { + @include cps.focus-ring; + + // The ring spans the whole inspector: it mustn't take its clicks. + &::before, + &::after { + pointer-events: none; + } +} + +:host(.uwt-inspector--framed:focus-visible) { + @include cps.focus-ring(-0.125rem, -0.25rem, 0); +} + +// In a pane or window: a header like cps-dialog's, and a body that scrolls. +:host(.uwt-inspector--framed) { + display: flex; + flex-direction: column; + height: 100%; + background-color: #fff; + font-family: 'Source Sans Pro', sans-serif; +} + +.uwt-inspector__header { + display: flex; + flex-shrink: 0; + align-items: center; + justify-content: space-between; + gap: 0.5rem; + padding: 0.75rem 1rem; + border-left: 0.25rem solid var(--cps-color-surprise); + border-bottom: 0.0625rem solid var(--cps-color-line-mid); + color: var(--cps-color-text-dark); +} + +.uwt-inspector__brand { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.uwt-inspector__title { + margin: 0; + font-size: 1.25rem; + font-weight: 700; +} + +// cps-dialog spreads its header out, which centres the title between the +// drag handle and the buttons; the panes and the window keep it left. +::ng-deep .uwt-inspector-dialog .cps-dialog-header-left { + margin-inline: 0.5rem auto; +} + +// cps-dialog's focus ring spans the whole dialog, and as the kit draws it, it +// takes the clicks of everything inside: after a click on the header and a +// key press such as Esc, nothing in the dialog would respond. +// Temporary: remove once the cps-ui-kit fixes it, +// https://github.com/AbsaOSS/cps-shared-ui/issues/948 +::ng-deep .uwt-inspector-dialog:focus-visible::before, +::ng-deep .uwt-inspector-dialog:focus-visible::after { + pointer-events: none; +} + +.uwt-inspector__body { + display: flex; + flex-direction: column; + gap: var(--uwt-inspector-space-l); + overscroll-behavior: contain; +} + +// Wheeling over the inspector must not scroll the app under it: what the +// body can't scroll any further passes to the page. Parts with nothing to +// scroll are held by the component's wheel listener. +::ng-deep .uwt-inspector-dialog .cps-dialog-content { + overscroll-behavior: contain; +} + +:host(.uwt-inspector--framed) .uwt-inspector__body { + flex: 1; + min-height: 0; + overflow: auto; + padding: var(--uwt-inspector-pad); +} + +.uwt-inspector__toolbar { + display: flex; + flex-direction: column; + gap: var(--uwt-inspector-space-m); +} + +.uwt-inspector__status-row { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: var(--uwt-inspector-space-m) 1rem; +} + +.uwt-inspector__status { + display: flex; + align-items: center; + gap: 0.5rem; + margin: 0; + font-size: 0.875rem; + color: var(--cps-text-secondary); + font-variant-numeric: tabular-nums; +} + +.uwt-inspector__dot { + width: 0.5rem; + height: 0.5rem; + border-radius: var(--cps-radius-full, 50%); + background: var(--cps-state-success); + animation: uwt-inspector-pulse 1.6s ease-in-out infinite; + + &--paused { + background: var(--cps-state-warn); + animation: none; + } +} + +@keyframes uwt-inspector-pulse { + 50% { + opacity: 0.35; + } +} + +@media (prefers-reduced-motion: reduce) { + .uwt-inspector__dot { + animation: none; + } +} + +.uwt-inspector__actions { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: var(--uwt-inspector-space-m) 1rem; +} + +.uwt-inspector__paused { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.375rem 0.75rem; + border-radius: var(--cps-radius-sm); + background: var(--cps-state-warn-surface); + color: var(--cps-text-primary); + font-size: 0.8125rem; +} + +.uwt-inspector__hint { + margin: 0; + font-size: 0.875rem; + color: var(--cps-text-secondary); +} + +// Forced colours empty the Pause switch's knob, which says by its side +// whether updates are paused; a border draws it back. +@media (forced-colors: active) { + :host ::ng-deep .cps-switch-slider::before { + box-sizing: border-box; + border: 0.4375rem solid; + } +} diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector/uwt-inspector.component.spec.ts b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector/uwt-inspector.component.spec.ts new file mode 100644 index 0000000..bf13dcb --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector/uwt-inspector.component.spec.ts @@ -0,0 +1,899 @@ +import { signal } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { + UWT_TELEMETRY_IDENTITY, + UwtTelemetryMonitor, + UwtTelemetryPublishInput +} from '@absaoss-cps/ngx-ui-watchtower'; +import { Subject } from 'rxjs'; +import { + CPS_LIVE_ANNOUNCER_SERVICE, + CpsDialogConfig, + CpsDialogRef +} from 'cps-ui-kit'; +import * as exportUtil from '../uwt-inspector-export/uwt-inspector-export'; +import { + UWT_INSPECTOR_FLUSH_MS, + UwtInspectorStore +} from '../uwt-inspector-store/uwt-inspector-store'; +import { UWT_INSPECTOR_PREFERENCES_KEY } from '../uwt-inspector-preferences/uwt-inspector-preferences'; +import { UwtInspectorSession } from '../uwt-inspector-session/uwt-inspector-session'; +import { UwtInspectorComponent } from './uwt-inspector.component'; + +const bi = (name: string, theme = 'dark'): UwtTelemetryPublishInput => ({ + kind: 'bi', + eventType: 'com.uwt.bi', + payload: { + eventName: name, + eventTime: '2026-01-01T00:00:00.000Z', + application: 'app', + metadata: { theme } + }, + destination: 'sink', + origin: { forwarded: false } +}); + +const scenario: UwtTelemetryPublishInput = { + kind: 'scenario', + eventType: 'com.uwt.scenario', + payload: { + scenarioId: 'abc', + scenarioName: 'load-dashboard', + status: 'success', + startTime: '2026-01-01T00:00:00.000Z', + delta: 42, + elapsed: 1000, + stepCount: 1, + steps: [], + application: 'app' + }, + destination: 'sink', + origin: { forwarded: false } +}; + +const warn: UwtTelemetryPublishInput = { + kind: 'log', + payload: { + timestamp: '2026-01-01T00:00:00.000Z', + level: 'warn', + message: 'Careful now', + logger: 'checkout', + application: 'app', + environment: 'test', + version: '1' + }, + destination: 'log-provider', + origin: { forwarded: false } +}; + +describe('UwtInspectorComponent', () => { + let fixture: ComponentFixture; + let monitor: UwtTelemetryMonitor; + let announce: jest.Mock; + let dialogConfig: CpsDialogConfig; + let maximize: Subject; + + const el = () => fixture.nativeElement as HTMLElement; + /** The popup's own actions, as its buttons and switch call them. */ + const ui = () => + fixture.componentInstance as unknown as { + setPaused(paused: boolean): void; + clear(): void; + }; + /** A button found by the start of its accessible name. */ + const button = (name: string) => { + const found = el().querySelector( + `button[aria-label^="${name}"]` + ); + if (!found) { + throw new Error(`No button named "${name}…"`); + } + return found; + }; + /** A button found by its visible text. */ + const labelled = (label: string) => { + const found = Array.from(el().querySelectorAll('button')).find( + (b) => b.textContent?.trim() === label + ); + if (!found) { + throw new Error(`No button labelled "${label}"`); + } + return found; + }; + const text = () => el().textContent?.replace(/\s+/g, ' ') ?? ''; + const kindOptions = () => + Array.from( + el().querySelectorAll('cps-button-toggle input') + ); + const kinds = () => kindOptions().map((i) => i.getAttribute('aria-label')); + const bodyRows = () => + el().querySelectorAll('td.uwt-inspector-timeline__time').length; + /** Each shown row's Kind cell, newest first. */ + const rowKinds = () => + Array.from( + el().querySelectorAll('td.uwt-inspector-timeline__time + td') + ).map((td) => td.textContent?.trim()); + + /** Shows one kind, as choosing it in the kind toggle does. */ + function showKind(label: string): void { + kindOptions() + .find((i) => i.getAttribute('aria-label')?.startsWith(`${label} `)) + ?.click(); + fixture.detectChanges(); + } + + /** Opens the Export menu and chooses one of its items. */ + function exportAs(item: 'Copy JSON' | 'Download JSON'): void { + labelled('Export').click(); + fixture.detectChanges(); + Array.from(document.querySelectorAll('[role="menuitem"]')) + .find((i) => i.textContent?.includes(item)) + ?.click(); + fixture.detectChanges(); + } + + function settle(): void { + jest.advanceTimersByTime(UWT_INSPECTOR_FLUSH_MS); + fixture.detectChanges(); + } + + beforeEach(async () => { + jest.useFakeTimers(); + announce = jest.fn(); + dialogConfig = {}; + maximize = new Subject(); + await TestBed.configureTestingModule({ + imports: [UwtInspectorComponent, NoopAnimationsModule], + providers: [ + { + provide: UWT_TELEMETRY_IDENTITY, + useValue: { + application: 'app', + environment: 'test', + version: '1.0.0' + } + }, + { + provide: CPS_LIVE_ANNOUNCER_SERVICE, + useValue: { announce, clear: jest.fn() } + }, + { provide: CpsDialogConfig, useValue: dialogConfig }, + { + provide: CpsDialogRef, + useValue: { onMaximize: maximize.asObservable() } + } + ] + }).compileComponents(); + monitor = TestBed.inject(UwtTelemetryMonitor); + fixture = TestBed.createComponent(UwtInspectorComponent); + fixture.detectChanges(); + }); + + afterEach(() => { + fixture.destroy(); + jest.useRealTimers(); + jest.restoreAllMocks(); + TestBed.inject(UwtInspectorSession).end(); + localStorage.removeItem(UWT_INSPECTOR_PREFERENCES_KEY); + }); + + it('should open empty, with every kind at nought and an explanation', () => { + expect(kinds()).toEqual(['All 0', 'BI 0', 'Scenarios 0', 'Logs 0']); + expect(text()).toContain('Use the app to generate events'); + expect(text()).toContain('No events yet.'); + }); + + it('should show every kind live in one timeline, newest first', () => { + monitor.publish(bi('theme_changed')); + monitor.publish(scenario); + monitor.publish(warn); + settle(); + + expect(rowKinds()).toEqual(['Log', 'Scenario', 'BI']); + expect(kinds()).toEqual(['All 3', 'BI 1', 'Scenarios 1', 'Logs 1']); + expect(text()).toContain('theme_changed'); + expect(text()).toContain('theme=dark'); + expect(text()).toContain('load-dashboard'); + expect(text()).toContain('success · 42 ms · 1 step'); + expect(text()).toContain('warn · Careful now'); + expect(text()).toContain('3 events'); + }); + + it('should colour a status or level, with the word still written', () => { + monitor.publish(scenario); + monitor.publish(warn); + settle(); + + expect( + el().querySelector('.uwt-inspector-timeline__tone--success')?.textContent + ).toContain('success'); + expect( + el().querySelector('.uwt-inspector-timeline__tone--warn')?.textContent + ).toContain('warn'); + }); + + it('should mark the newest rows as new, for eyes and for screen readers', () => { + monitor.publish(bi('a')); + settle(); + const fresh = el().querySelector('td.uwt-inspector-timeline__fresh'); + expect(fresh?.querySelector('.cps-sr-only')?.textContent).toBe('New: '); + + jest.advanceTimersByTime(3000); + fixture.detectChanges(); + expect(el().querySelector('td.uwt-inspector-timeline__fresh')).toBeNull(); + }); + + it('should count, list and say what is shown when only one kind is', () => { + monitor.publish(bi('theme_changed')); + monitor.publish(warn); + settle(); + + showKind('Logs'); + expect(rowKinds()).toEqual(['Log']); + + showKind('Scenarios'); + expect(bodyRows()).toBe(0); + expect(text()).toContain('No scenario events yet.'); + }); + + it('should show the full payload when a row is expanded', () => { + monitor.publish(bi('theme_changed')); + settle(); + + ( + el().querySelector('button[aria-label="Expand row"]') as HTMLElement + ).click(); + fixture.detectChanges(); + + const pre = el().querySelector('pre'); + expect(pre?.getAttribute('aria-label')).toMatch(/^Payload of event #\d+$/); + expect(JSON.parse(pre?.textContent ?? '')).toMatchObject({ + eventName: 'theme_changed', + metadata: { theme: 'dark' } + }); + expect(text()).toContain('telemetry sink'); + }); + + it('should search every kind with one field, counting matches per kind', () => { + monitor.publish(bi('theme_changed', 'dark')); + monitor.publish(bi('sidebar_toggled', 'light')); + monitor.publish(warn); + settle(); + + const searches = el().querySelectorAll( + '.uwt-inspector-search input' + ); + expect(searches).toHaveLength(1); + expect(searches[0].getAttribute('aria-label')).toBe('Search events'); + searches[0].value = 'dark'; + searches[0].dispatchEvent(new Event('input')); + jest.advanceTimersByTime(200); + fixture.detectChanges(); + + expect(bodyRows()).toBe(1); + expect(kinds()).toEqual(['All 1', 'BI 1', 'Scenarios 0', 'Logs 0']); + + showKind('Logs'); + expect(text()).toContain('No log events match the filters.'); + }); + + it('should count the oldest events dropped to stay within the limit', () => { + fixture.debugElement.injector + .get(UwtInspectorStore) + .dropped.set({ bi: 2, scenario: 0, logging: 1 }); + fixture.detectChanges(); + + expect(text()).toContain('0 events · 3 oldest dropped'); + }); + + it('should clear history and keep capturing', () => { + monitor.publish(bi('a')); + settle(); + + Array.from(el().querySelectorAll('button')) + .find((b) => b.textContent?.trim() === 'Clear') + ?.click(); + fixture.detectChanges(); + expect(bodyRows()).toBe(0); + expect(announce).toHaveBeenCalledWith( + 'History cleared. Capturing continues.', + 'polite' + ); + + monitor.publish(bi('b')); + settle(); + expect(bodyRows()).toBe(1); + }); + + it('should hold events back while paused, then show them on resume', () => { + monitor.publish(bi('a')); + settle(); + + ui().setPaused(true); + monitor.publish(bi('b')); + settle(); + expect(bodyRows()).toBe(1); + expect(text()).toContain('1 new event held back while paused.'); + // Not a live region: its count changes with every event, and resuming + // announces what arrived meanwhile. + expect( + el().querySelector('.uwt-inspector__paused')?.getAttribute('role') + ).toBeNull(); + + const resume = Array.from(el().querySelectorAll('button')).find( + (b) => b.textContent?.trim() === 'Resume' + )!; + resume.focus(); + resume.click(); + fixture.detectChanges(); + TestBed.tick(); + expect(bodyRows()).toBe(2); + // The notice gone, focus is on the Pause switch, not the page. + expect(document.activeElement).toBe(el().querySelector('cps-switch input')); + }); + + it('should say when it was paused, as well as when capturing began', () => { + jest.setSystemTime(new Date(2026, 0, 1, 10, 0, 0)); + ui().clear(); + jest.setSystemTime(new Date(2026, 0, 1, 10, 5, 30)); + ui().setPaused(true); + fixture.detectChanges(); + expect(text()).toContain('Paused at 10:05:30 · capturing since 10:00:00 ·'); + + ui().setPaused(false); + fixture.detectChanges(); + expect(text()).toContain('Capturing since 10:00:00 ·'); + expect(text()).not.toContain('Paused at'); + }); + + it('should export every event of the kind shown, whatever the filters', () => { + const download = jest + .spyOn(exportUtil, 'uwtDownloadJson') + .mockImplementation(() => undefined); + monitor.publish(bi('a')); + monitor.publish(bi('b')); + monitor.publish(warn); + settle(); + // A search hiding every event must not remove any from the export. + fixture.debugElement.injector + .get(UwtInspectorStore) + .setText('nothing matches this'); + fixture.detectChanges(); + + exportAs('Download JSON'); + + expect(download).toHaveBeenCalledTimes(1); + const [, filename, json] = download.mock.calls[0]; + expect(filename).toMatch( + /^ngx-ui-watchtower-events-app-all-\d{8}-\d{6}\.json$/ + ); + const doc = JSON.parse(json); + expect(doc).toMatchObject({ + format: 'ngx-ui-watchtower-event-inspector', + formatVersion: 2, + section: 'all', + app: { application: 'app' }, + count: { captured: 3, droppedOldest: 0 }, + activeFilters: { text: 'nothing matches this' } + }); + expect(doc.events.map((e: { kind: string }) => e.kind)).toEqual([ + 'bi', + 'bi', + 'log' + ]); + + showKind('Logs'); + exportAs('Download JSON'); + const logging = JSON.parse(download.mock.calls[1][2]); + expect(download.mock.calls[1][1]).toContain('-logging-'); + expect(logging.section).toBe('logging'); + expect(logging.events.map((e: { kind: string }) => e.kind)).toEqual([ + 'log' + ]); + }); + + it('should say in the Export menu what it holds', async () => { + monitor.publish(bi('a')); + monitor.publish(warn); + settle(); + showKind('BI'); + + labelled('Export').click(); + fixture.detectChanges(); + // The menu reports itself shown once its animation starts. + await jest.advanceTimersByTimeAsync(0); + fixture.detectChanges(); + + expect( + document.querySelector('[role="menu"]')?.getAttribute('aria-label') + ).toBe('1 BI event, with session and user ids'); + expect(labelled('Export').getAttribute('aria-expanded')).toBe('true'); + }); + + it('should copy the kind shown as JSON, and say so on the button', async () => { + const writeText = jest.fn((_text: string) => Promise.resolve()); + Object.assign(navigator, { clipboard: { writeText } }); + monitor.publish(bi('a')); + monitor.publish(warn); + settle(); + + showKind('Logs'); + exportAs('Copy JSON'); + await jest.advanceTimersByTimeAsync(0); + fixture.detectChanges(); + + const doc = JSON.parse(writeText.mock.calls[0][0]); + expect(doc.section).toBe('logging'); + expect(doc.events).toHaveLength(1); + expect(labelled('Copied')).toBeTruthy(); + expect(announce).toHaveBeenCalledWith( + '1 log event copied as JSON.', + 'polite' + ); + }); + + it('should announce new arrivals politely, at most every ten seconds', () => { + monitor.publish(bi('a')); + monitor.publish(bi('b')); + monitor.publish(warn); + settle(); + expect(announce).not.toHaveBeenCalled(); + + jest.advanceTimersByTime(10_000); + expect(announce).toHaveBeenCalledWith( + '3 new events: 2 BI, 1 log.', + 'polite' + ); + + announce.mockClear(); + jest.advanceTimersByTime(10_000); + expect(announce).not.toHaveBeenCalled(); // nothing new + }); + + it('should announce no history after moving to another dock', () => { + monitor.publish(bi('a')); + monitor.publish(bi('b')); + settle(); + // Another dock: a new view of the same session. + fixture.destroy(); + const moved = TestBed.createComponent(UwtInspectorComponent); + moved.detectChanges(); + + jest.advanceTimersByTime(10_000); + expect(announce).not.toHaveBeenCalled(); + moved.destroy(); + }); + + it('should produce no telemetry of its own while being used', () => { + const seen: unknown[] = []; + // Subscribe as a second observer, alongside the popup's own store. + monitor.events$.subscribe((e) => seen.push(e)); + + ui().clear(); + ui().setPaused(true); + ui().setPaused(false); + jest + .spyOn(exportUtil, 'uwtDownloadJson') + .mockImplementation(() => undefined); + exportAs('Download JSON'); + showKind('Logs'); + exportAs('Download JSON'); + settle(); + + expect(seen).toHaveLength(0); + }); + + it('should make the whole dialog see-through, and remember it', () => { + expect(dialogConfig.style).toEqual({ opacity: '1' }); + + const slider = el().querySelector('cps-slider input') as HTMLInputElement; + slider.value = '30'; + slider.dispatchEvent(new Event('input')); + + expect(dialogConfig.style).toEqual({ opacity: '0.7' }); + expect( + JSON.parse(localStorage.getItem(UWT_INSPECTOR_PREFERENCES_KEY) ?? '{}') + .transparency + ).toBe(30); + }); + + it('should reopen at the remembered transparency', () => { + localStorage.setItem( + UWT_INSPECTOR_PREFERENCES_KEY, + JSON.stringify({ transparency: 20 }) + ); + const reopened = TestBed.createComponent(UwtInspectorComponent); + reopened.detectChanges(); + + expect(dialogConfig.style).toEqual({ opacity: '0.8' }); + expect( + (reopened.nativeElement as HTMLElement).querySelector( + 'cps-slider input' + )?.value + ).toBe('20'); + reopened.destroy(); + }); + + it('should drop the transparency while maximized, and bring it back after', () => { + const slider = el().querySelector('cps-slider input') as HTMLInputElement; + slider.value = '30'; + slider.dispatchEvent(new Event('input')); + + maximize.next(true); + fixture.detectChanges(); + expect(dialogConfig.style).toEqual({ opacity: '1' }); + expect(el().querySelector('cps-slider')).toBeNull(); + + maximize.next(false); + fixture.detectChanges(); + expect(dialogConfig.style).toEqual({ opacity: '0.7' }); + expect(el().querySelector('cps-slider')).not.toBeNull(); + }); + + it('should have the slider only as a dialog, and its own header elsewhere', () => { + expect(el().querySelector('cps-slider')).not.toBeNull(); + expect(el().querySelector('.uwt-inspector__header')).toBeNull(); + + fixture.componentRef.setInput('dock', 'right'); + fixture.detectChanges(); + + expect(el().querySelector('cps-slider')).toBeNull(); + expect(el().querySelector('.uwt-inspector__title')?.textContent).toBe( + 'Event Inspector' + ); + expect(button('Close Event Inspector')).toBeTruthy(); + expect( + el().querySelector('.uwt-inspector__brand uwt-inspector-logo') + ).not.toBeNull(); + }); + + it("should put the mark in cps-dialog's header icon slot", () => { + // Outside a cps-dialog, as in every other test, there is nowhere to put it. + expect(el().querySelector('uwt-inspector-logo')).toBeNull(); + + const dialog = document.body.appendChild(document.createElement('div')); + dialog.className = 'cps-dialog'; + const slot = dialog.appendChild(document.createElement('div')); + slot.className = 'cps-dialog-header-icon'; + const inDialog = TestBed.createComponent(UwtInspectorComponent); + dialog.appendChild(inDialog.nativeElement); + inDialog.detectChanges(); + TestBed.tick(); + + expect(slot.querySelector('uwt-inspector-logo')).not.toBeNull(); + inDialog.destroy(); + dialog.remove(); + }); + + it('should ask to move to another dock, or to close', () => { + const controls = { + setDock: jest.fn(), + close: jest.fn(), + panesFit: signal(true) + }; + TestBed.inject(UwtInspectorSession).controls = controls; + const dock = (name: string) => + el().querySelector(`button[data-dock="${name}"]`)!; + + expect(dock('dialog').getAttribute('aria-pressed')).toBe('true'); + dock('dialog').click(); + dock('right').click(); + expect(controls.setDock.mock.calls).toEqual([['right']]); + + fixture.componentRef.setInput('dock', 'window'); + fixture.detectChanges(); + button('Close Event Inspector').click(); + expect(controls.close).toHaveBeenCalledTimes(1); + }); + + it('should take focus itself, not to a field, where the shortcut types', () => { + fixture.componentInstance.focusInspector(); + + expect(document.activeElement).toBe(el()); + expect(el().tabIndex).toBe(-1); + }); + + it('should close the dialog on Escape from inside it, not while a list in it is open', () => { + const controls = { + setDock: jest.fn(), + close: jest.fn(), + panesFit: signal(true) + }; + TestBed.inject(UwtInspectorSession).controls = controls; + const search = el().querySelector('.uwt-inspector-search input')!; + const escape = () => + search.dispatchEvent( + new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }) + ); + + search.setAttribute('aria-expanded', 'true'); + escape(); + expect(controls.close).not.toHaveBeenCalled(); + + search.removeAttribute('aria-expanded'); + escape(); + expect(controls.close).toHaveBeenCalledTimes(1); + + fixture.componentRef.setInput('dock', 'right'); + fixture.detectChanges(); + escape(); + expect(controls.close).toHaveBeenCalledTimes(1); + }); + + it('should mark an Escape it closes the dialog on as handled, and leave any other alone', () => { + TestBed.inject(UwtInspectorSession).controls = { + setDock: jest.fn(), + close: jest.fn(), + panesFit: signal(true) + }; + const search = el().querySelector('.uwt-inspector-search input')!; + const escape = () => { + const event = new KeyboardEvent('keydown', { + key: 'Escape', + bubbles: true, + cancelable: true + }); + search.dispatchEvent(event); + return event; + }; + + // The kit's notifications close on an Escape nobody handled. + expect(escape().defaultPrevented).toBe(true); + + search.setAttribute('aria-expanded', 'true'); + expect(escape().defaultPrevented).toBe(false); + + search.removeAttribute('aria-expanded'); + fixture.componentRef.setInput('dock', 'right'); + fixture.detectChanges(); + expect(escape().defaultPrevented).toBe(false); + }); + + it('should offer no pane when the app has no room for one', () => { + TestBed.inject(UwtInspectorSession).controls = { + setDock: jest.fn(), + close: jest.fn(), + panesFit: signal(false) + }; + const narrow = TestBed.createComponent(UwtInspectorComponent); + narrow.detectChanges(); + + expect( + Array.from( + (narrow.nativeElement as HTMLElement).querySelectorAll('[data-dock]') + ).map((b) => b.getAttribute('data-dock')) + ).toEqual(['dialog', 'window']); + narrow.destroy(); + }); + + it('should keep its history in the session, for the next dock', () => { + monitor.publish(bi('theme_changed')); + settle(); + fixture.destroy(); + expect(monitor.observed).toBe(true); + + fixture = TestBed.createComponent(UwtInspectorComponent); + fixture.detectChanges(); + expect(text()).toContain('1 event'); + + TestBed.inject(UwtInspectorSession).end(); + expect(monitor.observed).toBe(false); + }); + + describe('page scroll', () => { + /** What the kit's menus listen to: the body's own `scroll`. */ + const heard = () => { + const listener = jest.fn(); + el() + .querySelector('.uwt-inspector__body')! + .addEventListener('scroll', listener); + return listener; + }; + + it("should tell the page's scroll to its body, which the kit's open lists listen to", () => { + const listener = heard(); + + document.dispatchEvent(new Event('scroll')); + + expect(listener).toHaveBeenCalledTimes(1); + }); + + it("should tell it a scrolling element's too, not only the window's", () => { + const listener = heard(); + const scroller = document.body.appendChild(document.createElement('div')); + + scroller.dispatchEvent(new Event('scroll')); + + expect(listener).toHaveBeenCalledTimes(1); + scroller.remove(); + }); + + it('should leave scrolling inside the inspector to the kit, and not answer itself', () => { + const listener = heard(); + + el() + .querySelector('.uwt-inspector__body')! + .dispatchEvent(new Event('scroll')); + + expect(listener).toHaveBeenCalledTimes(1); + }); + + it("should leave an open list's own scrolling alone", () => { + const listener = heard(); + const list = document.body.appendChild(document.createElement('div')); + list.className = 'cps-menu-container'; + + list.dispatchEvent(new Event('scroll')); + + expect(listener).not.toHaveBeenCalled(); + list.remove(); + }); + + it('should stop once destroyed', () => { + const listener = heard(); + fixture.destroy(); + + document.dispatchEvent(new Event('scroll')); + + expect(listener).not.toHaveBeenCalled(); + }); + }); + + describe('wheel', () => { + const wheel = ( + target: Element, + init: ConstructorParameters[1] = { deltaY: 100 } + ) => { + const event = new WheelEvent('wheel', { + bubbles: true, + cancelable: true, + ...init + }); + target.dispatchEvent(event); + return event; + }; + /** A part with `room` to scroll: `top` scrolled of `extent` in a `size` window. */ + const scrolling = (el: Element, top: number, extent = 400, size = 200) => { + (el as HTMLElement).style.overflowY = 'auto'; + Object.defineProperties(el, { + scrollTop: { value: top, configurable: true }, + clientHeight: { value: size, configurable: true }, + scrollHeight: { value: extent, configurable: true } + }); + }; + const body = () => el().querySelector('.uwt-inspector__body')!; + + it('should keep the wheel from the page where nothing in the inspector scrolls', () => { + fixture.componentRef.setInput('dock', 'right'); + fixture.detectChanges(); + + expect(wheel(body()).defaultPrevented).toBe(true); + expect( + wheel(el().querySelector('.uwt-inspector__header')!).defaultPrevented + ).toBe(true); + }); + + it('should let a part scroll while it has room, either way', () => { + scrolling(body(), 100); + + expect(wheel(body(), { deltaY: 100 }).defaultPrevented).toBe(false); + expect(wheel(body(), { deltaY: -100 }).defaultPrevented).toBe(false); + }); + + it('should keep it from the page once the part is at its end', () => { + scrolling(body(), 200); + expect(wheel(body(), { deltaY: 100 }).defaultPrevented).toBe(true); + + scrolling(body(), 0); + expect(wheel(body(), { deltaY: -100 }).defaultPrevented).toBe(true); + }); + + it('should let a scrolling parent have it when what is inside is at its end', () => { + scrolling(body(), 100); + const inner = body().appendChild(document.createElement('div')); + scrolling(inner, 200); + + expect(wheel(inner, { deltaY: 100 }).defaultPrevented).toBe(false); + }); + + it('should leave a pinch zoom, and a wheel already handled, alone', () => { + expect( + wheel(body(), { deltaY: 100, ctrlKey: true }).defaultPrevented + ).toBe(false); + body().addEventListener('wheel', (e) => e.preventDefault(), { + once: true + }); + const event = wheel(body()); + expect(event.defaultPrevented).toBe(true); + }); + + it('should stop once destroyed', () => { + const target = body(); + fixture.destroy(); + + expect(wheel(target).defaultPrevented).toBe(false); + }); + }); + + describe('dock menu', () => { + const original = window.ResizeObserver; + let resize: (width: number) => void; + const trigger = () => el().querySelector('.uwt-inspector-dock__trigger'); + const group = () => el().querySelector('.uwt-inspector-dock__group'); + + /** Opens the inspector in `dock`, with a ResizeObserver to drive. */ + const open = async (dock: 'right' | 'window') => { + fixture.destroy(); + // cps-ui-kit observes things too: only what watches the inspector drives. + type Callback = ConstructorParameters[0]; + window.ResizeObserver = class { + private readonly callback: Callback; + + constructor(callback: Callback) { + this.callback = callback; + } + + observe(target: Element): void { + if (target === fixture.nativeElement) { + resize = (width) => + this.callback( + [{ contentRect: { width } } as ResizeObserverEntry], + this as unknown as ResizeObserver + ); + } + } + + disconnect(): void {} + } as unknown as typeof ResizeObserver; + fixture = TestBed.createComponent(UwtInspectorComponent); + fixture.componentRef.setInput('dock', dock); + fixture.detectChanges(); + // Observing starts after the first render, a tick on. (whenStable + // would wait for the inspector's interval.) + await new Promise((resolve) => + jest.requireActual('timers').setImmediate(resolve) + ); + }; + + beforeEach(() => { + // The change is made on the next frame: here, at once. + jest + .spyOn(window, 'requestAnimationFrame') + .mockImplementation((frame) => { + frame(0); + return 0; + }); + }); + + afterEach(() => { + window.ResizeObserver = original; + jest.restoreAllMocks(); + }); + + it('should swap the dock buttons for a menu in a pane narrower than 34rem, and back', async () => { + await open('right'); + expect(group()).not.toBeNull(); + + resize(479); + fixture.detectChanges(); + expect(trigger()).not.toBeNull(); + expect(group()).toBeNull(); + + resize(576); + fixture.detectChanges(); + expect(trigger()).toBeNull(); + expect(group()).not.toBeNull(); + }); + + it('should keep the buttons in the separate window, however narrow', async () => { + await open('window'); + + resize(300); + fixture.detectChanges(); + + expect(trigger()).toBeNull(); + expect(group()).not.toBeNull(); + }); + }); +}); diff --git a/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector/uwt-inspector.component.ts b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector/uwt-inspector.component.ts new file mode 100644 index 0000000..2fdb518 --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/lib/uwt-inspector/uwt-inspector.component.ts @@ -0,0 +1,487 @@ +import { DOCUMENT } from '@angular/common'; +import { + afterNextRender, + ChangeDetectionStrategy, + Component, + computed, + DestroyRef, + ElementRef, + inject, + Injector, + input, + NgZone, + signal, + viewChild +} from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { + CPS_LIVE_ANNOUNCER_SERVICE, + CpsButtonComponent, + CpsDialogConfig, + CpsDialogRef, + CpsInfoCircleComponent, + CpsSwitchComponent +} from 'cps-ui-kit'; +import { CpsSliderComponent } from '../cps-slider/cps-slider.component'; +import { + UWT_EVENT_INSPECTOR_CONFIG, + UwtInspectorDock +} from '../uwt-inspector.models/uwt-inspector.models'; +import { + UWT_DEFAULT_INSPECTOR_CONFIG, + UWT_INSPECTOR_SECTIONS, + UwtInspectorStore +} from '../uwt-inspector-store/uwt-inspector-store'; +import { UwtInspectorTimelineComponent } from '../uwt-inspector-timeline/uwt-inspector-timeline.component'; +import { + UWT_INSPECTOR_SECTION_NOUN, + UwtInspectorExporter +} from '../uwt-inspector-export/uwt-inspector-exporter'; +import { uwtInspectorTime } from '../uwt-inspector-timeline/uwt-inspector-rows'; +import { UWT_DEFAULT_INSPECTOR_SHORTCUTS } from '../uwt-inspector-shortcut/uwt-inspector-shortcut'; +import { UwtInspectorDockSwitcherComponent } from '../uwt-inspector-dock-switcher/uwt-inspector-dock-switcher.component'; +import { UwtInspectorLogoComponent } from '../uwt-inspector-logo/uwt-inspector-logo.component'; +import { UwtInspectorSession } from '../uwt-inspector-session/uwt-inspector-session'; +import { + UWT_INSPECTOR_MAX_TRANSPARENCY, + UwtInspectorPreferenceStorage +} from '../uwt-inspector-preferences/uwt-inspector-preferences'; + +/** Screen readers hear about new events at most this often, in ms. */ +const ANNOUNCE_EVERY_MS = 10_000; + +/** + * Narrower than this, in rem, the dock buttons give way to a menu: beside the + * status line they no longer fit one row. A pane opens at 36rem, so it is + * only a pane dragged towards its 30rem minimum, or a dialog on a phone. + */ +const DOCK_MENU_BELOW_REM = 34; + +/** + * What the info icon in the header says, in every dock: what the inspector + * shows, and what it doesn't. + */ +const INFO = + "Shows what this app hands to its telemetry destinations, after redaction. A destination can still drop an event, for example when RUM samples a session out. RUM's own page views, errors and performance events aren't shown."; + +/** + * The Event Inspector's content, shown by {@link UwtEventInspectorService} + * in a non-modal cps-dialog, a pane or its own window. Capturing is + * controlled here — pause and clear apply to everything — while the + * timeline below shows, filters and exports the events. + * + * The captured history belongs to the {@link UwtInspectorSession}, so it + * survives moving between docks and is discarded on close. Uses no + * telemetry service itself, so opening or using it never produces events + * of its own. + */ +@Component({ + selector: 'uwt-inspector', + imports: [ + CpsButtonComponent, + CpsInfoCircleComponent, + CpsSliderComponent, + CpsSwitchComponent, + UwtInspectorDockSwitcherComponent, + UwtInspectorLogoComponent, + UwtInspectorTimelineComponent + ], + providers: [ + { + provide: UwtInspectorStore, + useFactory: () => inject(UwtInspectorSession).store + }, + UwtInspectorExporter + ], + changeDetection: ChangeDetectionStrategy.OnPush, + host: { + // Focusable, but not in the tab order: see focusInspector(). + tabindex: '-1', + '[class.uwt-inspector--framed]': 'framed()' + }, + templateUrl: './uwt-inspector.component.html', + styleUrl: './uwt-inspector.component.scss' +}) +export class UwtInspectorComponent { + /** Where it is shown; a cps-dialog creates it without inputs. */ + readonly dock = input('dialog'); + + protected readonly store = inject(UwtInspectorStore); + + private readonly document = inject(DOCUMENT); + private readonly host: ElementRef = inject(ElementRef); + private readonly session = inject(UwtInspectorSession); + private readonly preferences = inject(UwtInspectorPreferenceStorage); + private readonly announcer = inject(CPS_LIVE_ANNOUNCER_SERVICE, { + optional: true + }); + + /** The dialog's own config: cps-dialog binds its `style` to the window. */ + private readonly dialogConfig = inject(CpsDialogConfig, { optional: true }); + private readonly dialogRef = inject(CpsDialogRef, { optional: true }); + + private readonly config = { + ...UWT_DEFAULT_INSPECTOR_CONFIG, + shortcuts: UWT_DEFAULT_INSPECTOR_SHORTCUTS, + ...inject(UWT_EVENT_INSPECTOR_CONFIG, { optional: true }) + }; + + /** A pane or window brings no header of its own, so this draws one. */ + protected readonly framed = computed(() => this.dock() !== 'dialog'); + + /** Whether the app's window has room for a pane. */ + protected readonly panesFit = + this.session.controls?.panesFit ?? signal(true).asReadonly(); + + protected readonly maxPayloadChars = this.config.maxPayloadCharsInView; + + protected readonly since = computed(() => + uwtInspectorTime(this.store.startedAt()).slice(0, 8) + ); + + /** The oldest events dropped to stay within each kind's limit. */ + protected readonly dropped = computed(() => { + const dropped = this.store.dropped(); + return UWT_INSPECTOR_SECTIONS.reduce((n, id) => n + dropped[id], 0); + }); + + protected readonly pausedAt = computed(() => { + const at = this.store.pausedAt(); + return at && uwtInspectorTime(at).slice(0, 8); + }); + + protected readonly info = INFO; + + protected readonly maxTransparency = UWT_INSPECTOR_MAX_TRANSPARENCY; + protected readonly transparency = signal( + this.preferences.read().transparency + ); + + /** A maximized dialog covers the app, so it isn't see-through. */ + protected readonly maximized = signal(false); + + /** Too narrow for the dock buttons, which a menu replaces. */ + protected readonly compact = signal(false); + + private readonly dialogLogo = viewChild('dialogLogo', { read: ElementRef }); + + private readonly dialogInfo = viewChild('dialogInfo', { read: ElementRef }); + + private readonly pauseSwitch = viewChild('pauseSwitch', { read: ElementRef }); + + private readonly injector = inject(Injector); + + private readonly zone = inject(NgZone); + + /** From what the session already holds: a dock move announces no history. */ + private lastAnnounced = { ...this.store.received() }; + + constructor() { + const timer = setInterval(() => this.announceNew(), ANNOUNCE_EVERY_MS); + inject(DestroyRef).onDestroy(() => clearInterval(timer)); + this.applyTransparency(this.transparency()); + this.dialogRef?.onMaximize + .pipe(takeUntilDestroyed()) + .subscribe((maximized) => { + this.maximized.set(maximized); + this.applyTransparency(maximized ? 0 : this.transparency()); + }); + afterNextRender(() => this.placeInDialogHeader()); + this.keepScrollingInside(); + this.watchWidth(); + this.listen( + () => this.frame(), + 'keydown', + (event) => this.onEscape(event) + ); + } + + /** + * Whether the inspector is too narrow for the dock buttons: in a narrow + * pane they would take a row of their own. Not in the separate window, + * whose menu would open against the app's window. + */ + private watchWidth(): void { + const zone = inject(NgZone); + const destroyRef = inject(DestroyRef); + afterNextRender(() => { + const host = this.host.nativeElement; + const view = host.ownerDocument.defaultView; + if (!view?.ResizeObserver) { + return; + } + const rem = + parseFloat( + view.getComputedStyle(host.ownerDocument.documentElement).fontSize + ) || 16; + let width = -1; + const observer = new view.ResizeObserver(([entry]) => { + // Only width matters, and what changes the layout waits for the next + // frame: doing it here, as the row's height changes, would make the + // browser report a ResizeObserver loop. + if (entry.contentRect.width === width) { + return; + } + width = entry.contentRect.width; + view.requestAnimationFrame(() => { + const compact = + width < DOCK_MENU_BELOW_REM * rem && this.dock() !== 'window'; + if (compact !== this.compact()) { + zone.run(() => this.compact.set(compact)); + } + }); + }); + zone.runOutsideAngular(() => observer.observe(host)); + destroyRef.onDestroy(() => observer.disconnect()); + }); + } + + /** + * The inspector is fixed, so the page scrolls under it, and two things + * follow. + * + * cps-ui-kit puts an open list in the page, and closes it when a scrolling + * parent of its field scrolls, which the page is not for a fixed pane: the + * list would drift away from its field. Each dock has such a parent, the + * body or the dialog's content, so the page's scroll is passed on to it. + * Only the inspector's elements hear it. Scrolling inside the inspector or + * an open list is the kit's own. + * + * And a part that can't scroll, or has scrolled as far as it goes, passes + * the wheel on to the page. `overscroll-behavior` stops that in some + * browsers; the rest, and the header, are held here, unless a part under + * the pointer can scroll that way. A pinch zoom is left alone. + */ + private keepScrollingInside(): void { + const host = this.host.nativeElement; + let passing = false; + + this.listen(this.document, 'scroll', (event: Event) => { + const target = event.target; + if ( + passing || + (target instanceof Node && host.contains(target)) || + (target instanceof Element && target.closest('.cps-menu-container')) + ) { + return; + } + passing = true; + [ + host.querySelector('.uwt-inspector__body'), + host.closest('.cps-dialog-content') + ].forEach((el) => el?.dispatchEvent(new Event('scroll'))); + passing = false; + }); + + this.listen(host, 'wheel', (event: WheelEvent) => { + if (event.ctrlKey || event.defaultPrevented) { + return; + } + // In the dialog, the frame's scrolling content holds the inspector. + const limit = host.closest('.cps-dialog') ?? host; + const down = event.deltaY > 0; + const canScroll = (el: Element): boolean => + /auto|scroll/.test(getComputedStyle(el).overflowY) && + // A pixel's slack, for fractional positions. + (down + ? el.scrollTop + el.clientHeight < el.scrollHeight - 1 + : el.scrollTop > 0); + for ( + let el = event.target instanceof Element ? event.target : null; + el && limit.contains(el); + el = el.parentElement + ) { + if (canScroll(el)) { + return; + } + } + event.preventDefault(); + }); + } + + /** + * Listens once rendered, outside the zone, until destroyed. A target given + * as a function is looked up then: the dialog isn't around the host before. + */ + private listen( + target: EventTarget | (() => EventTarget), + type: string, + handler: (event: E) => void + ): void { + const zone = inject(NgZone); + const destroyRef = inject(DestroyRef); + // Scroll doesn't bubble: capture sees every element's. Wheel and keydown + // can be stopped, which a passive listener's preventDefault() would not. + const options = { + capture: type === 'scroll', + passive: type !== 'wheel' && type !== 'keydown' + }; + afterNextRender(() => { + const listener = handler as (event: Event) => void; + const element = typeof target === 'function' ? target() : target; + zone.runOutsideAngular(() => + element.addEventListener(type, listener, options) + ); + destroyRef.onDestroy(() => + element.removeEventListener(type, listener, options) + ); + }); + } + + /** Whether keyboard focus is anywhere in the inspector, header included. */ + containsFocus(): boolean { + return this.frame().contains(this.document.activeElement); + } + + /** + * Moves focus to the inspector itself, not to a field, where the shortcut + * types: so the shortcut closes it next. Not to cps-dialog's own frame + * either, whose focus ring covers the dialog and takes its clicks. + */ + focusInspector(): void { + this.host.nativeElement.focus(); + } + + protected setDock(dock: UwtInspectorDock): void { + this.session.controls?.setDock(dock); + } + + protected close(): void { + this.session.controls?.close(); + } + + /** + * Escape closes the dialog, but only from inside it — the header and frame + * included — as it isn't modal, and the app's own controls use Escape too; + * and not while one of its lists or menus is open, which Escape closes + * first. An Escape it closes on is marked handled, as cps-ui-kit's own + * dialog does, so the kit's notifications don't also close on it. + * + * Temporary: cps-dialog's `closeOnEscape` can't be limited to the focus + * inside it yet, so this stands in for it: + * https://github.com/AbsaOSS/cps-shared-ui/issues/951 + */ + protected onEscape(event: KeyboardEvent): void { + const target = event.target as Element | null; + if ( + event.key === 'Escape' && + this.dock() === 'dialog' && + !event.defaultPrevented && + !target?.closest?.('[aria-expanded="true"]') + ) { + event.preventDefault(); + // The listener runs outside the zone; closing the dialog needs it. + this.zone.run(() => this.close()); + } + } + + /** Resumes from the notice, which then goes: focus to the Pause switch. */ + protected resume(): void { + this.setPaused(false); + afterNextRender( + () => + (this.pauseSwitch()?.nativeElement as HTMLElement | undefined) + ?.querySelector('input') + ?.focus(), + { injector: this.injector } + ); + } + + protected setPaused(paused: boolean): void { + this.store.setPaused(paused); + this.announce( + paused + ? 'Live updates paused. Events are still captured.' + : 'Live updates resumed.' + ); + } + + protected clear(): void { + this.store.clear(); + this.lastAnnounced = { bi: 0, scenario: 0, logging: 0 }; + this.announce('History cleared. Capturing continues.'); + } + + /** Lets the app show through the dialog, and remembers the choice. */ + protected setTransparency(percent: number): void { + this.transparency.set(percent); + this.applyTransparency(percent); + this.preferences.update({ transparency: percent }); + } + + /** + * A new `style` object, rather than this component's own styles, so the + * dialog's header and frame turn see-through too. Only a dialog has one. + */ + private applyTransparency(percent: number): void { + if (this.dialogConfig) { + this.dialogConfig.style = { + ...this.dialogConfig.style, + opacity: String((100 - percent) / 100) + }; + } + } + + /** + * cps-dialog draws the dialog's header, and its slots take only an icon + * name and a tooltip's text, so the mark moves into its icon slot, before + * the title, and the info icon into its own, after it: the kit's own info + * icon can't be given a width, and this text is long. Without a slot, the + * mark and icon go. + */ + private placeInDialogHeader(): void { + const dialog = this.host.nativeElement.closest('.cps-dialog'); + const place = ( + moved: ElementRef | undefined, + slot: string + ): void => { + const el = moved?.nativeElement; + const target = dialog?.querySelector(slot); + if (target && el) { + target.append(el); + } else { + el?.remove(); + } + }; + place(this.dialogLogo(), '.cps-dialog-header-icon'); + place(this.dialogInfo(), '.cps-dialog-header-info-circle'); + } + + /** One polite summary of what arrived since the last one, if anything. */ + private announceNew(): void { + if (this.store.paused()) { + return; + } + const received = this.store.received(); + const parts = UWT_INSPECTOR_SECTIONS.map((id) => ({ + id, + n: received[id] - this.lastAnnounced[id] + })).filter((p) => p.n > 0); + this.lastAnnounced = { ...received }; + if (parts.length === 0) { + return; + } + const total = parts.reduce((sum, p) => sum + p.n, 0); + const detail = parts + .map((p) => `${p.n} ${UWT_INSPECTOR_SECTION_NOUN[p.id]}`) + .join(', '); + this.announce(`${total} new event${total === 1 ? '' : 's'}: ${detail}.`); + } + + private announce(message: string): void { + try { + this.announcer?.announce(message, 'polite'); + } catch { + // Announcements are a courtesy; never let one break the inspector. + } + } + + /** The dialog or pane around this content, or the content itself. */ + private frame(): HTMLElement { + return ( + this.host.nativeElement.closest( + '[role="dialog"], [role="region"]' + ) ?? this.host.nativeElement + ); + } +} diff --git a/projects/ngx-ui-watchtower/event-inspector/src/public-api.ts b/projects/ngx-ui-watchtower/event-inspector/src/public-api.ts new file mode 100644 index 0000000..027cf39 --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/src/public-api.ts @@ -0,0 +1,23 @@ +/* + * Public API Surface of ngx-ui-watchtower/event-inspector + * + * The Event Inspector: an in-app popup showing, live, every event this app hands to its + * telemetry destinations. Needs cps-ui-kit. + */ + +export { + UWT_EVENT_INSPECTOR_CONFIG, + UwtInspectorDock, + UwtInspectorExport, + UwtInspectorFieldFilter, + UwtInspectorFilterOperator, + UwtInspectorFilterState, + UwtInspectorKindFilter, + UwtInspectorSectionId, + UwtInspectorShortcut, + UwtEventInspectorConfig +} from './lib/uwt-inspector.models/uwt-inspector.models'; +export { UWT_DEFAULT_INSPECTOR_SHORTCUTS } from './lib/uwt-inspector-shortcut/uwt-inspector-shortcut'; +export { UWT_DEFAULT_INSPECTOR_CONFIG } from './lib/uwt-inspector-store/uwt-inspector-store'; +export { provideUwtEventInspector } from './lib/uwt-inspector.providers/uwt-inspector.providers'; +export { UwtEventInspectorService } from './lib/uwt-event-inspector.service/uwt-event-inspector.service'; diff --git a/projects/ngx-ui-watchtower/event-inspector/tsconfig.lib.json b/projects/ngx-ui-watchtower/event-inspector/tsconfig.lib.json new file mode 100644 index 0000000..d6fea07 --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/tsconfig.lib.json @@ -0,0 +1,25 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "../../../out-tsc/lib-event-inspector", + "rootDir": "../../..", + "lib": ["ES2022", "DOM"], + "moduleResolution": "bundler", + "declaration": true, + "declarationMap": true, + "inlineSources": true, + "skipLibCheck": true, + "types": [] + }, + "include": ["src/**/*.ts"], + "exclude": ["**/*.spec.ts"], + "angularCompilerOptions": { + "extendedDiagnostics": { + "checks": { + "nullishCoalescingNotNullable": "suppress", + "optionalChainNotNullable": "suppress" + } + } + } +} diff --git a/projects/ngx-ui-watchtower/event-inspector/tsconfig.spec.json b/projects/ngx-ui-watchtower/event-inspector/tsconfig.spec.json new file mode 100644 index 0000000..cd51ee4 --- /dev/null +++ b/projects/ngx-ui-watchtower/event-inspector/tsconfig.spec.json @@ -0,0 +1,11 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "../../../out-tsc/spec-event-inspector", + "rootDir": "../../..", + "types": ["jest"], + "resolveJsonModule": true + }, + "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] +} diff --git a/projects/ngx-ui-watchtower/package.json b/projects/ngx-ui-watchtower/package.json index 604c14b..b882aff 100644 --- a/projects/ngx-ui-watchtower/package.json +++ b/projects/ngx-ui-watchtower/package.json @@ -5,12 +5,24 @@ "peerDependencies": { "@angular/common": ">=21.0.0 <23.0.0", "@angular/core": ">=21.0.0 <23.0.0", + "@angular/animations": ">=21.0.0 <23.0.0", + "@angular/forms": ">=21.0.0 <23.0.0", "aws-rum-web": "^3.2.1", + "cps-ui-kit": ">=21.33.0 <23.0.0", "rxjs": "^7.8.2" }, "peerDependenciesMeta": { + "@angular/animations": { + "optional": true + }, + "@angular/forms": { + "optional": true + }, "aws-rum-web": { "optional": true + }, + "cps-ui-kit": { + "optional": true } }, "repository": { diff --git a/projects/ngx-ui-watchtower/rum/src/lib/uwt-rum-credentials/uwt-rum-credentials.ts b/projects/ngx-ui-watchtower/rum/src/lib/uwt-rum-credentials/uwt-rum-credentials.ts index 52d1630..69033e9 100644 --- a/projects/ngx-ui-watchtower/rum/src/lib/uwt-rum-credentials/uwt-rum-credentials.ts +++ b/projects/ngx-ui-watchtower/rum/src/lib/uwt-rum-credentials/uwt-rum-credentials.ts @@ -111,7 +111,9 @@ export interface UwtRumAppMonitorConfig { * Built-in telemetry categories to enable. Defaults to errors, performance * and same-origin HTTP. The SDK's own default also includes `'replay'` * (session replay via `rrweb`); this library does not enable it by - * default. + * default. `recordError` and `mirrorErrorsToRum` need `'errors'` in this + * list (or its plugin in `eventPluginsToLoad`); without it, errors aren't + * mirrored to RUM and the console warns once. * * Options the SDK matches with `RegExp.test` or calls as a function — * `urlsToInclude`, `urlsToExclude`, `addXRayTraceIdHeader` (also a diff --git a/projects/ngx-ui-watchtower/rum/src/lib/uwt-rum-telemetry.sink/uwt-rum-telemetry.sink.spec.ts b/projects/ngx-ui-watchtower/rum/src/lib/uwt-rum-telemetry.sink/uwt-rum-telemetry.sink.spec.ts index 2de1462..65137eb 100644 --- a/projects/ngx-ui-watchtower/rum/src/lib/uwt-rum-telemetry.sink/uwt-rum-telemetry.sink.spec.ts +++ b/projects/ngx-ui-watchtower/rum/src/lib/uwt-rum-telemetry.sink/uwt-rum-telemetry.sink.spec.ts @@ -288,8 +288,9 @@ describe('UwtRumTelemetrySink', () => { const config = AwsRumCtor.mock.calls[0][3] as { telemetries: unknown[] }; // Strict: an explicit `undefined` or `null` would replace the SDK's default. - expect(config.telemetries).toStrictEqual([ - ['http', { recordAllRequests: true }] + expect(config.telemetries[0]).toStrictEqual([ + 'http', + { recordAllRequests: true } ]); expect(config).not.toHaveProperty('pagesToInclude'); expect(config).not.toHaveProperty('pagesToExclude'); @@ -473,6 +474,15 @@ describe('UwtRumTelemetrySink', () => { }); }); + it("should leave the app's telemetries as they are, without adding RUM's error plugin", async () => { + configure({ loadImpl: async () => bootstrap({ telemetries: ['http'] }) }); + await sink.init(); + + expect(AwsRumCtor.mock.calls[0][3]).toMatchObject({ + telemetries: ['http'] + }); + }); + it('should omit every SDK-default-mirroring field entirely when unset, not send it as undefined', async () => { await sink.init(); @@ -999,6 +1009,42 @@ describe('UwtRumTelemetrySink', () => { }); }); + it("should warn once, in every environment, when RUM rejects an error because 'errors' isn't loaded", async () => { + const warn = jest.spyOn(console, 'warn').mockImplementation(() => {}); + awsRumInstance.recordError.mockImplementation(() => { + throw new Error('Invalid plugin ID: com.amazonaws.rum.js-error'); + }); + await sink.init(); + + sink.recordError({ name: 'TypeError', message: 'boom' }); + sink.recordError({ name: 'TypeError', message: 'again' }); + + const warned = warn.mock.calls.filter((c) => + String(c[0]).includes("'errors' telemetry isn't loaded") + ); + expect(warned).toHaveLength(1); + expect(String(warned[0][0])).toContain('mirrorErrorsToRum: false'); + warn.mockRestore(); + awsRumInstance.recordError.mockReset(); + }); + + it('should not take that warning for any other failure of recordError', async () => { + const warn = jest.spyOn(console, 'warn').mockImplementation(() => {}); + awsRumInstance.recordError.mockImplementation(() => { + throw new Error('something else'); + }); + await sink.init(); + + expect(() => + sink.recordError({ name: 'TypeError', message: 'boom' }) + ).not.toThrow(); + expect( + warn.mock.calls.some((c) => String(c[0]).includes("'errors' telemetry")) + ).toBe(false); + warn.mockRestore(); + awsRumInstance.recordError.mockReset(); + }); + it('should buffer an error recorded before init and replay it once ready', async () => { sink.recordError({ name: 'TypeError', message: 'boom' }); diff --git a/projects/ngx-ui-watchtower/rum/src/lib/uwt-rum-telemetry.sink/uwt-rum-telemetry.sink.ts b/projects/ngx-ui-watchtower/rum/src/lib/uwt-rum-telemetry.sink/uwt-rum-telemetry.sink.ts index 30645ac..98ff82c 100644 --- a/projects/ngx-ui-watchtower/rum/src/lib/uwt-rum-telemetry.sink/uwt-rum-telemetry.sink.ts +++ b/projects/ngx-ui-watchtower/rum/src/lib/uwt-rum-telemetry.sink/uwt-rum-telemetry.sink.ts @@ -291,6 +291,7 @@ export class UwtRumTelemetrySink extends UwtTelemetrySink implements OnDestroy { * or a refresh returned null; distinct from `!awsRum` alone. */ private disabled = false; + private errorPluginWarned = false; /** Broker requests that failed at startup so far. */ private initFailures = 0; /** When the first of them failed. */ @@ -504,7 +505,7 @@ export class UwtRumTelemetrySink extends UwtTelemetrySink implements OnDestroy { this.recordOrBuffer( 'rum.recordError', { kind: 'error', error, metadata }, - (awsRum) => awsRum.recordError(this.withOrigin(error, metadata)) + (awsRum) => this.recordErrorTo(awsRum, error, metadata) ); } @@ -926,6 +927,34 @@ export class UwtRumTelemetrySink extends UwtTelemetrySink implements OnDestroy { }); } + /** + * Hands an error to RUM's `recordError`, which RUM rejects when its + * `errors` telemetry isn't loaded. The sink never edits the app's RUM + * config, so that is reported once, in every environment, and the error is + * not mirrored. Any other failure propagates to the caller's fail-open guard. + */ + private recordErrorTo( + awsRum: AwsRum, + error: UwtTelemetryError, + metadata?: UwtTelemetryMetadata + ): void { + try { + awsRum.recordError(this.withOrigin(error, metadata)); + } catch (failure) { + if (!/invalid plugin id/i.test(String((failure as Error)?.message))) { + throw failure; + } + if (!this.errorPluginWarned) { + this.errorPluginWarned = true; + uwtSafeVoid('rum.reportMissingErrorPlugin', () => { + console.warn( + "[ngx-ui-watchtower] RUM's 'errors' telemetry isn't loaded, so errors aren't mirrored to RUM. Add it to the RUM telemetries, or set mirrorErrorsToRum: false." + ); + }); + } + } + } + private bufferItem(item: BufferedItem): void { this.buffer.push(item); if (this.buffer.length > PRE_INIT_BUFFER_LIMIT) { @@ -948,7 +977,9 @@ export class UwtRumTelemetrySink extends UwtTelemetrySink implements OnDestroy { } else if (item.kind === 'pageView') { this.awsRum?.recordPageView(item.pageId); } else { - this.awsRum?.recordError(this.withOrigin(item.error, item.metadata)); + if (this.awsRum) { + this.recordErrorTo(this.awsRum, item.error, item.metadata); + } } }); } diff --git a/projects/ngx-ui-watchtower/src/lib/models/uwt-telemetry-monitor.models/uwt-telemetry-monitor.models.ts b/projects/ngx-ui-watchtower/src/lib/models/uwt-telemetry-monitor.models/uwt-telemetry-monitor.models.ts new file mode 100644 index 0000000..952c5f5 --- /dev/null +++ b/projects/ngx-ui-watchtower/src/lib/models/uwt-telemetry-monitor.models/uwt-telemetry-monitor.models.ts @@ -0,0 +1,126 @@ +import { UwtBIEvent } from '../uwt-bi.models/uwt-bi.models'; +import { UwtLogRecord } from '../uwt-log.models/uwt-log.models'; +import { + UwtScenarioRecord, + UwtScenarioStepEvent +} from '../uwt-scenario.models/uwt-scenario.models'; +import { UwtTelemetryError } from '../uwt-telemetry-common.models/uwt-telemetry-common.models'; + +/** + * Any JSON value — used where a payload's shape is not guaranteed, such as + * an event forwarded from another realm. + * + * @group Types + */ +export type UwtJsonValue = + | string + | number + | boolean + | null + | UwtJsonValue[] + | { [key: string]: UwtJsonValue }; + +/** + * A JSON object — see {@link UwtJsonValue}. + * + * @group Types + */ +export type UwtJsonObject = { [key: string]: UwtJsonValue }; + +/** + * Where an observed event was handed: the telemetry sink, or the + * application's log API provider. + * + * @group Types + */ +export type UwtTelemetryDestination = 'sink' | 'log-provider'; + +/** + * Whether an observed event was produced in this realm, or forwarded from + * another realm through {@link UwtTelemetryBroadcastHost}. + * + * @group Types + */ +export type UwtTelemetryEventOrigin = + { forwarded: false } | { forwarded: true; application?: string }; + +/** + * What an observed event is. + * + * - `bi`, `scenario`, `scenario-step` — custom events sent to the sink. + * - `log` — a record sent to the log API provider. + * - `error` — an error sent to the sink, e.g. a log error mirrored to RUM. + * - `unknown` — a forwarded event whose type this realm does not recognise. + * + * @group Types + */ +export type UwtTelemetryEventKind = + 'bi' | 'scenario' | 'scenario-step' | 'log' | 'error' | 'unknown'; + +interface UwtTelemetryObservedBase { + /** Increases by one per observed event, within one page load. */ + sequence: number; + + /** When the event was handed over, as ISO-8601. */ + capturedAt: string; + + destination: UwtTelemetryDestination; + origin: UwtTelemetryEventOrigin; +} + +/** + * One event handed to a destination, as seen by + * {@link UwtTelemetryMonitor}. + * + * Local payloads keep their library types. A forwarded payload arrived from + * another realm and was only shape-checked there, so check + * `origin.forwarded` before relying on its fields. The type doesn't narrow + * with it: a payload is typed as either, and needs a cast. + * + * @group Types + */ +export type UwtTelemetryObservedEvent = + | (UwtTelemetryObservedBase & { + kind: 'bi'; + eventType: string; + payload: UwtBIEvent | UwtJsonObject; + }) + | (UwtTelemetryObservedBase & { + kind: 'scenario'; + eventType: string; + payload: UwtScenarioRecord | UwtJsonObject; + }) + | (UwtTelemetryObservedBase & { + kind: 'scenario-step'; + eventType: string; + payload: UwtScenarioStepEvent | UwtJsonObject; + }) + | (UwtTelemetryObservedBase & { + kind: 'log'; + payload: UwtLogRecord; + }) + | (UwtTelemetryObservedBase & { + kind: 'error'; + payload: UwtTelemetryError; + /** The log event this error was mirrored from, when there is one. */ + relatedSequence?: number; + }) + | (UwtTelemetryObservedBase & { + kind: 'unknown'; + eventType: string; + payload: UwtJsonObject; + }); + +/** Distributes `Omit` over a union, so each member keeps its own fields. */ +type UwtOmitEach = T extends unknown + ? Omit + : never; + +/** + * What a hand-off site passes to {@link UwtTelemetryMonitor.publish} — an + * observed event without the fields the monitor assigns itself. + */ +export type UwtTelemetryPublishInput = UwtOmitEach< + UwtTelemetryObservedEvent, + 'sequence' | 'capturedAt' +>; diff --git a/projects/ngx-ui-watchtower/src/lib/providers/uwt-log-api.provider/uwt-log-api.provider.ts b/projects/ngx-ui-watchtower/src/lib/providers/uwt-log-api.provider/uwt-log-api.provider.ts index a2a50cc..3d963e8 100644 --- a/projects/ngx-ui-watchtower/src/lib/providers/uwt-log-api.provider/uwt-log-api.provider.ts +++ b/projects/ngx-ui-watchtower/src/lib/providers/uwt-log-api.provider/uwt-log-api.provider.ts @@ -85,7 +85,9 @@ export interface UwtLogApiProvider { * * Called on `pagehide`, on `visibilitychange` going hidden, and on * teardown. Must be synchronous and non-blocking. `fetch` with `keepalive` - * or `navigator.sendBeacon` are the usual choices. + * or `navigator.sendBeacon` are the usual choices — every time, not only + * while `document.visibilityState` is `'hidden'`: Safari still says + * `visible` while a reloading page hides, and cancels a plain request then. */ flush?(): void; } diff --git a/projects/ngx-ui-watchtower/src/lib/scenario/uwt-scenario-operators/uwt-scenario-operators.spec.ts b/projects/ngx-ui-watchtower/src/lib/scenario/uwt-scenario-operators/uwt-scenario-operators.spec.ts index 582564a..8c525d8 100644 --- a/projects/ngx-ui-watchtower/src/lib/scenario/uwt-scenario-operators/uwt-scenario-operators.spec.ts +++ b/projects/ngx-ui-watchtower/src/lib/scenario/uwt-scenario-operators/uwt-scenario-operators.spec.ts @@ -14,6 +14,7 @@ import { } from 'rxjs'; import { UWT_DEFAULT_TELEMETRY_CONFIG } from '../../config/uwt-telemetry-common.config/uwt-telemetry-common.config'; import { UwtScenarioRecord } from '../../models/uwt-scenario.models/uwt-scenario.models'; +import { UwtTelemetryMonitor } from '../../services/uwt-telemetry-monitor.service/uwt-telemetry-monitor.service'; import { UwtNoopTelemetrySink } from '../../sinks/uwt-telemetry/uwt-noop-telemetry.sink/uwt-noop-telemetry.sink'; import { UWT_DEFAULT_REDACT_CONFIG } from '../../utils/uwt-telemetry-redact.util/uwt-telemetry-redact.util'; import { UwtScenario } from '../uwt-scenario/uwt-scenario'; @@ -331,6 +332,7 @@ describe('traceScenario operator', () => { }, redact: UWT_DEFAULT_REDACT_CONFIG, sink: new UwtNoopTelemetrySink(), + monitor: new UwtTelemetryMonitor(), onSettled: (_id, record) => settled.push(record) } ); diff --git a/projects/ngx-ui-watchtower/src/lib/scenario/uwt-scenario/uwt-scenario.spec.ts b/projects/ngx-ui-watchtower/src/lib/scenario/uwt-scenario/uwt-scenario.spec.ts index 5d7ac0b..082c949 100644 --- a/projects/ngx-ui-watchtower/src/lib/scenario/uwt-scenario/uwt-scenario.spec.ts +++ b/projects/ngx-ui-watchtower/src/lib/scenario/uwt-scenario/uwt-scenario.spec.ts @@ -5,6 +5,7 @@ import { UwtTelemetrySink } from '../../sinks/uwt-telemetry/uwt-telemetry-abstra import { UWT_DEFAULT_REDACT_CONFIG } from '../../utils/uwt-telemetry-redact.util/uwt-telemetry-redact.util'; import { uwtMarkSessionStart } from '../../utils/uwt-session-start.util/uwt-session-start.util'; import { UwtScenario, UwtScenarioDeps } from './uwt-scenario'; +import { UwtTelemetryMonitor } from '../../services/uwt-telemetry-monitor.service/uwt-telemetry-monitor.service'; /** Captures what the scenario emitted, so a test can assert on it. */ class RecordingSink extends UwtTelemetrySink { @@ -55,6 +56,7 @@ function createDeps(overrides: Partial = {}): { redact: UWT_DEFAULT_REDACT_CONFIG, sink, onSettled: (_id, record) => settled.push(record), + monitor: new UwtTelemetryMonitor(), ...overrides }; return { deps, sink, settled }; diff --git a/projects/ngx-ui-watchtower/src/lib/scenario/uwt-scenario/uwt-scenario.ts b/projects/ngx-ui-watchtower/src/lib/scenario/uwt-scenario/uwt-scenario.ts index 636547c..e608335 100644 --- a/projects/ngx-ui-watchtower/src/lib/scenario/uwt-scenario/uwt-scenario.ts +++ b/projects/ngx-ui-watchtower/src/lib/scenario/uwt-scenario/uwt-scenario.ts @@ -18,6 +18,7 @@ import { UwtTelemetryEventTypes, UwtTelemetryMetadata } from '../../models/uwt-telemetry-common.models/uwt-telemetry-common.models'; +import { UwtTelemetryMonitor } from '../../services/uwt-telemetry-monitor.service/uwt-telemetry-monitor.service'; import { UwtTelemetrySink } from '../../sinks/uwt-telemetry/uwt-telemetry-abstract.sink/uwt-telemetry-abstract.sink'; import { uwtDebugWrite, @@ -63,6 +64,8 @@ export interface UwtScenarioDeps { runOutsideAngular?: (callback: () => T) => T; /** Called once with the scenario's id when it reaches a terminal state. */ onSettled: (scenarioId: string, record: UwtScenarioRecord) => void; + /** Told about every event right after it is handed to the sink. */ + monitor: UwtTelemetryMonitor; /** Telemetry is switched off: the scenario records, times and sends nothing. */ inert?: boolean; } @@ -873,10 +876,11 @@ export class UwtScenario { /** * Sends a payload to the sink, first logging that exact same object if - * `debugScenario` is on. + * `debugScenario` is on, then telling the monitor it was handed over. * - * The only place either happens, so console output and sink output can - * never drift apart: one object, one call site. + * The only place any of the three happens, so console output, sink output + * and what an observer sees can never drift apart: one object, one call + * site. * * @param emission the payload and which of the two events it is * @param summary a short human-readable prefix for the console line @@ -891,6 +895,12 @@ export class UwtScenario { writeToConsole(summary, eventType, emission.payload) ); this.deps.sink.record(eventType, emission.payload); + this.deps.monitor.publish({ + ...emission, + eventType, + destination: 'sink', + origin: { forwarded: false } + }); } } diff --git a/projects/ngx-ui-watchtower/src/lib/services/uwt-bi-telemetry.service/uwt-bi-telemetry.service.ts b/projects/ngx-ui-watchtower/src/lib/services/uwt-bi-telemetry.service/uwt-bi-telemetry.service.ts index 0aa89ae..f0d81a4 100644 --- a/projects/ngx-ui-watchtower/src/lib/services/uwt-bi-telemetry.service/uwt-bi-telemetry.service.ts +++ b/projects/ngx-ui-watchtower/src/lib/services/uwt-bi-telemetry.service/uwt-bi-telemetry.service.ts @@ -14,6 +14,7 @@ import { uwtEventTypes, UwtTelemetryMetadata } from '../../models/uwt-telemetry-common.models/uwt-telemetry-common.models'; +import { UwtTelemetryMonitor } from '../uwt-telemetry-monitor.service/uwt-telemetry-monitor.service'; import { UwtTelemetrySink } from '../../sinks/uwt-telemetry/uwt-telemetry-abstract.sink/uwt-telemetry-abstract.sink'; import { uwtDebugWrite } from '../../utils/uwt-debug-flag.util/uwt-debug-flag.util'; import { @@ -70,6 +71,7 @@ export class UwtBITelemetryService { ); private readonly sink = inject(UwtTelemetrySink); + private readonly monitor = inject(UwtTelemetryMonitor); private readonly enabled = uwtTelemetryEnabled(); private readonly eventTypes = uwtEventTypes(this.identity.eventNamespace); @@ -135,6 +137,13 @@ export class UwtBITelemetryService { ); this.sink.record(this.eventTypes.bi, event); + this.monitor.publish({ + kind: 'bi', + eventType: this.eventTypes.bi, + payload: event, + destination: 'sink', + origin: { forwarded: false } + }); }); } diff --git a/projects/ngx-ui-watchtower/src/lib/services/uwt-logger.service/uwt-logger.service.ts b/projects/ngx-ui-watchtower/src/lib/services/uwt-logger.service/uwt-logger.service.ts index 62c9885..ae0a611 100644 --- a/projects/ngx-ui-watchtower/src/lib/services/uwt-logger.service/uwt-logger.service.ts +++ b/projects/ngx-ui-watchtower/src/lib/services/uwt-logger.service/uwt-logger.service.ts @@ -14,6 +14,7 @@ import { type UwtLogLevel, type UwtLogRecord } from '../../models/uwt-log.models/uwt-log.models'; +import { UwtTelemetryMonitor } from '../uwt-telemetry-monitor.service/uwt-telemetry-monitor.service'; import { UwtTelemetrySink } from '../../sinks/uwt-telemetry/uwt-telemetry-abstract.sink/uwt-telemetry-abstract.sink'; import { UWT_LOG_API_PROVIDER, @@ -89,6 +90,7 @@ export class UwtLoggerService implements OnDestroy { /** Enrichment only (`sessionId`/`userId`, optional RUM mirroring). */ private readonly sink = inject(UwtTelemetrySink, { optional: true }); private readonly apiProvider = inject(UWT_LOG_API_PROVIDER); + private readonly monitor = inject(UwtTelemetryMonitor); private readonly document = inject(DOCUMENT); private readonly enabled = uwtTelemetryEnabled(); @@ -265,7 +267,7 @@ export class UwtLoggerService implements OnDestroy { uwtDebugWrite('debugLogger', () => writeToConsole(record), record.logger); - this.deliver(record); + const sequence = this.deliver(record); if (level === 'error' && this.logsConfig.mirrorErrorsToRum && this.sink) { const mirrored = @@ -273,16 +275,39 @@ export class UwtLoggerService implements OnDestroy { uwtNormalizeError(new Error(record.message), this.redact); if (mirrored) { this.sink.recordError(mirrored); + this.monitor.publish({ + kind: 'error', + payload: mirrored, + relatedSequence: sequence, + destination: 'sink', + origin: { forwarded: false } + }); } } }); } - /** Guards against a throwing or secretly-async, rejecting provider. */ - private deliver(record: UwtLogRecord): void { - uwtSafeVoidMaybeAsync('logger.deliver', () => - this.apiProvider.send(record) - ); + /** + * Guards against a throwing or secretly-async, rejecting provider, and + * tells the monitor once the record has been handed over. + * + * @returns the monitor's sequence number for the record, when observed + */ + private deliver(record: UwtLogRecord): number | undefined { + let sequence: number | undefined; + uwtSafeVoidMaybeAsync('logger.deliver', () => { + const pending = this.apiProvider.send(record); + // After send, so a provider that throws synchronously is not reported + // as handed over. An asynchronous rejection can't be seen here. + sequence = this.monitor.publish({ + kind: 'log', + payload: record, + destination: 'log-provider', + origin: { forwarded: false } + }); + return pending; + }); + return sequence; } /** Gives the provider its chance to ship whatever it has queued itself. */ diff --git a/projects/ngx-ui-watchtower/src/lib/services/uwt-scenario-telemetry.service/uwt-scenario-telemetry.service.ts b/projects/ngx-ui-watchtower/src/lib/services/uwt-scenario-telemetry.service/uwt-scenario-telemetry.service.ts index 1fea5a7..6cc353f 100644 --- a/projects/ngx-ui-watchtower/src/lib/services/uwt-scenario-telemetry.service/uwt-scenario-telemetry.service.ts +++ b/projects/ngx-ui-watchtower/src/lib/services/uwt-scenario-telemetry.service/uwt-scenario-telemetry.service.ts @@ -21,6 +21,7 @@ import { } from '../../utils/uwt-telemetry-safe.util/uwt-telemetry-safe.util'; import { uwtRedactConfigFor } from '../../utils/uwt-telemetry-redact.util/uwt-telemetry-redact.util'; import { UwtScenario } from '../../scenario/uwt-scenario/uwt-scenario'; +import { UwtTelemetryMonitor } from '../uwt-telemetry-monitor.service/uwt-telemetry-monitor.service'; /** * How many scenarios may be in flight before a development-mode warning @@ -69,6 +70,7 @@ export class UwtScenarioTelemetryService implements OnDestroy { ); private readonly sink = inject(UwtTelemetrySink); + private readonly monitor = inject(UwtTelemetryMonitor); private readonly document = inject(DOCUMENT); private readonly enabled = uwtTelemetryEnabled(); @@ -128,6 +130,7 @@ export class UwtScenarioTelemetryService implements OnDestroy { scenarioTelemetryConfig: this.scenarioTelemetryConfig, redact: this.redact, sink: this.sink, + monitor: this.monitor, onSettled: () => undefined, inert: true }); @@ -137,6 +140,7 @@ export class UwtScenarioTelemetryService implements OnDestroy { scenarioTelemetryConfig: this.scenarioTelemetryConfig, redact: this.redact, sink: this.sink, + monitor: this.monitor, runOutsideAngular: (callback) => this.zone?.runOutsideAngular(callback) ?? callback(), onSettled: (scenarioId, record) => { diff --git a/projects/ngx-ui-watchtower/src/lib/services/uwt-telemetry-monitor.service/uwt-telemetry-monitor.integration.spec.ts b/projects/ngx-ui-watchtower/src/lib/services/uwt-telemetry-monitor.service/uwt-telemetry-monitor.integration.spec.ts new file mode 100644 index 0000000..5938741 --- /dev/null +++ b/projects/ngx-ui-watchtower/src/lib/services/uwt-telemetry-monitor.service/uwt-telemetry-monitor.integration.spec.ts @@ -0,0 +1,210 @@ +import { Injectable } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; +import { + provideUwtTelemetry, + withLogging, + withScenarios +} from '../../providers/uwt-telemetry-common.providers/uwt-telemetry-common.providers'; +import { + UWT_LOG_API_PROVIDER, + UwtLogApiProvider +} from '../../providers/uwt-log-api.provider/uwt-log-api.provider'; +import { UwtLogRecord } from '../../models/uwt-log.models/uwt-log.models'; +import { + UwtTelemetryError, + UwtTelemetryMetadata +} from '../../models/uwt-telemetry-common.models/uwt-telemetry-common.models'; +import { UwtTelemetryObservedEvent } from '../../models/uwt-telemetry-monitor.models/uwt-telemetry-monitor.models'; +import { UwtTelemetrySink } from '../../sinks/uwt-telemetry/uwt-telemetry-abstract.sink/uwt-telemetry-abstract.sink'; +import { UwtBITelemetryService } from '../uwt-bi-telemetry.service/uwt-bi-telemetry.service'; +import { UwtLoggerService } from '../uwt-logger.service/uwt-logger.service'; +import { UwtScenarioTelemetryService } from '../uwt-scenario-telemetry.service/uwt-scenario-telemetry.service'; +import { UwtTelemetryMonitor } from './uwt-telemetry-monitor.service'; + +/** Counts every hand-off, so a test can prove nothing is sent twice. */ +@Injectable() +class CountingSink extends UwtTelemetrySink { + readonly records: { eventType: string; payload: object }[] = []; + readonly errors: UwtTelemetryError[] = []; + throwOnRecord = false; + + record(eventType: string, payload: object, _m?: UwtTelemetryMetadata): void { + if (this.throwOnRecord) { + throw new Error('sink is down'); + } + this.records.push({ eventType, payload }); + } + + recordError(error: UwtTelemetryError): void { + this.errors.push(error); + } + + getSessionId(): string | undefined { + return 'session-1'; + } + + setUserId(): void {} + + getUserId(): string | undefined { + return undefined; + } + + flush(): void {} +} + +@Injectable() +class CountingLogApi implements UwtLogApiProvider { + readonly sent: UwtLogRecord[] = []; + throwOnSend = false; + + send(record: UwtLogRecord): void { + if (this.throwOnSend) { + throw new Error('log backend is down'); + } + this.sent.push(record); + } + + query(): Promise { + return Promise.resolve([]); + } +} + +describe('UwtTelemetryMonitor at the hand-off sites', () => { + let observed: UwtTelemetryObservedEvent[]; + let sink: CountingSink; + let logApi: CountingLogApi; + + beforeEach(() => { + TestBed.resetTestingModule(); + TestBed.configureTestingModule({ + providers: [ + provideUwtTelemetry( + { application: 'test-app', environment: 'test', version: '1.0.0' }, + withScenarios({ defaultTimeoutMs: 0, emitLifecycleEvents: true }), + withLogging({ mirrorErrorsToRum: true }) + ), + CountingSink, + { provide: UwtTelemetrySink, useExisting: CountingSink }, + CountingLogApi, + { provide: UWT_LOG_API_PROVIDER, useExisting: CountingLogApi } + ] + }); + sink = TestBed.inject(CountingSink); + logApi = TestBed.inject(CountingLogApi); + observed = []; + TestBed.inject(UwtTelemetryMonitor).events$.subscribe((e) => + observed.push(e) + ); + }); + + afterEach(() => jest.restoreAllMocks()); + + it('should observe a BI event once, and send it once', () => { + TestBed.inject(UwtBITelemetryService).track('export_clicked', { + format: 'csv' + }); + + expect(sink.records).toHaveLength(1); + expect(observed).toHaveLength(1); + expect(observed[0]).toMatchObject({ + kind: 'bi', + eventType: 'com.uwt.bi', + destination: 'sink', + origin: { forwarded: false }, + payload: { eventName: 'export_clicked', metadata: { format: 'csv' } } + }); + }); + + it('should observe each scenario step and the settled record, once each', () => { + TestBed.inject(UwtScenarioTelemetryService) + .start({ name: 'load' }) + .step('one') + .step('two') + .complete(); + + expect(observed.map((e) => e.kind)).toEqual([ + 'scenario-step', + 'scenario-step', + 'scenario' + ]); + expect(observed.map((e) => 'eventType' in e && e.eventType)).toEqual([ + 'com.uwt.scenario.step', + 'com.uwt.scenario.step', + 'com.uwt.scenario' + ]); + + expect(sink.records).toHaveLength(observed.length); + expect(observed[2].payload).toEqual(sink.records[2].payload); + }); + + it('should observe a log record as handed to the log provider', () => { + TestBed.inject(UwtLoggerService).getLogger('checkout').warn('careful'); + + expect(logApi.sent).toHaveLength(1); + expect(observed).toHaveLength(1); + expect(observed[0]).toMatchObject({ + kind: 'log', + destination: 'log-provider', + payload: { logger: 'checkout', level: 'warn', message: 'careful' } + }); + }); + + it('should observe a mirrored error, linked to the log line it came from', () => { + TestBed.inject(UwtLoggerService) + .getLogger('checkout') + .error('failed', { error: new Error('boom') }); + + expect(sink.errors).toHaveLength(1); + const [log, error] = observed; + expect(log.kind).toBe('log'); + expect(error).toMatchObject({ + kind: 'error', + destination: 'sink', + relatedSequence: log.sequence, + payload: { name: 'Error', message: 'boom' } + }); + }); + + it('should observe nothing for a hand-off that threw', () => { + sink.throwOnRecord = true; + logApi.throwOnSend = true; + + TestBed.inject(UwtBITelemetryService).track('export_clicked'); + TestBed.inject(UwtLoggerService).getLogger('checkout').log('lost'); + + expect(observed).toHaveLength(0); + }); + + it('should send exactly the same number of events whether observed or not', () => { + const run = () => { + TestBed.inject(UwtBITelemetryService).track('a'); + TestBed.inject(UwtScenarioTelemetryService) + .start({ name: 'load' }) + .step('one') + .complete(); + TestBed.inject(UwtLoggerService).getLogger('checkout').log('hi'); + }; + + run(); + const observedCounts = [sink.records.length, logApi.sent.length]; + + TestBed.resetTestingModule(); + TestBed.configureTestingModule({ + providers: [ + provideUwtTelemetry( + { application: 'test-app', environment: 'test', version: '1.0.0' }, + withScenarios({ defaultTimeoutMs: 0, emitLifecycleEvents: true }) + ), + CountingSink, + { provide: UwtTelemetrySink, useExisting: CountingSink }, + CountingLogApi, + { provide: UWT_LOG_API_PROVIDER, useExisting: CountingLogApi } + ] + }); + sink = TestBed.inject(CountingSink); + logApi = TestBed.inject(CountingLogApi); + run(); + + expect([sink.records.length, logApi.sent.length]).toEqual(observedCounts); + }); +}); diff --git a/projects/ngx-ui-watchtower/src/lib/services/uwt-telemetry-monitor.service/uwt-telemetry-monitor.service.spec.ts b/projects/ngx-ui-watchtower/src/lib/services/uwt-telemetry-monitor.service/uwt-telemetry-monitor.service.spec.ts new file mode 100644 index 0000000..78baff6 --- /dev/null +++ b/projects/ngx-ui-watchtower/src/lib/services/uwt-telemetry-monitor.service/uwt-telemetry-monitor.service.spec.ts @@ -0,0 +1,118 @@ +import { isDevMode } from '@angular/core'; +import { UwtBIEvent } from '../../models/uwt-bi.models/uwt-bi.models'; +import { UwtTelemetryObservedEvent } from '../../models/uwt-telemetry-monitor.models/uwt-telemetry-monitor.models'; +import * as safeUtil from '../../utils/uwt-telemetry-safe.util/uwt-telemetry-safe.util'; +import { UwtTelemetryMonitor } from './uwt-telemetry-monitor.service'; + +jest.mock('@angular/core', () => ({ + ...jest.requireActual('@angular/core'), + isDevMode: jest.fn(() => false) +})); + +function biEvent(): UwtBIEvent { + return { + eventName: 'export_clicked', + eventTime: '2026-01-01T00:00:00.000Z', + metadata: { format: 'csv' }, + application: 'test-app' + }; +} + +describe('UwtTelemetryMonitor', () => { + let monitor: UwtTelemetryMonitor; + let received: UwtTelemetryObservedEvent[]; + + beforeEach(() => { + monitor = new UwtTelemetryMonitor(); + received = []; + }); + + afterEach(() => { + monitor.ngOnDestroy(); + jest.restoreAllMocks(); + }); + + function publishBI(payload = biEvent()): number | undefined { + return monitor.publish({ + kind: 'bi', + eventType: 'com.uwt.bi', + payload, + destination: 'sink', + origin: { forwarded: false } + }); + } + + it('should do nothing, and copy nothing, while nobody is subscribed', () => { + const clone = jest.spyOn(safeUtil, 'uwtDeepClone'); + + expect(monitor.observed).toBe(false); + expect(publishBI()).toBeUndefined(); + expect(clone).not.toHaveBeenCalled(); + }); + + it('should deliver an event to a subscriber, with the envelope filled in', () => { + monitor.events$.subscribe((e) => received.push(e)); + + const sequence = publishBI(); + + expect(received).toHaveLength(1); + expect(received[0]).toMatchObject({ + kind: 'bi', + eventType: 'com.uwt.bi', + destination: 'sink', + origin: { forwarded: false }, + sequence + }); + expect(received[0].capturedAt).toMatch( + /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/ + ); + }); + + it('should hand a subscriber a copy, isolated from the object that was sent', () => { + monitor.events$.subscribe((e) => received.push(e)); + const sent = biEvent(); + + publishBI(sent); + const observed = received[0].payload as UwtBIEvent; + + expect(observed).toEqual(sent); + expect(observed).not.toBe(sent); + + observed.metadata!.format = 'changed-by-observer'; + sent.metadata!.format = 'changed-by-sender'; + expect(sent.metadata!.format).toBe('changed-by-sender'); + expect(observed.metadata!.format).toBe('changed-by-observer'); + }); + + it('should number events in increasing order', () => { + monitor.events$.subscribe((e) => received.push(e)); + + publishBI(); + publishBI(); + publishBI(); + + const [a, b, c] = received.map((e) => e.sequence); + expect(b).toBe(a + 1); + expect(c).toBe(b + 1); + }); + + it('should stop observing once the last subscriber leaves', () => { + const subscription = monitor.events$.subscribe((e) => received.push(e)); + subscription.unsubscribe(); + + expect(monitor.observed).toBe(false); + expect(publishBI()).toBeUndefined(); + expect(received).toHaveLength(0); + }); + + it('should never throw into the hand-off site, even when copying fails', () => { + (isDevMode as jest.Mock).mockReturnValue(false); + monitor.events$.subscribe((e) => received.push(e)); + jest.spyOn(safeUtil, 'uwtDeepClone').mockImplementation(() => { + throw new Error('clone failed'); + }); + + expect(() => publishBI()).not.toThrow(); + expect(received).toHaveLength(0); + }); +}); diff --git a/projects/ngx-ui-watchtower/src/lib/services/uwt-telemetry-monitor.service/uwt-telemetry-monitor.service.ts b/projects/ngx-ui-watchtower/src/lib/services/uwt-telemetry-monitor.service/uwt-telemetry-monitor.service.ts new file mode 100644 index 0000000..1eca315 --- /dev/null +++ b/projects/ngx-ui-watchtower/src/lib/services/uwt-telemetry-monitor.service/uwt-telemetry-monitor.service.ts @@ -0,0 +1,81 @@ +import { Injectable, OnDestroy } from '@angular/core'; +import { Observable, Subject } from 'rxjs'; +import { + UwtTelemetryObservedEvent, + UwtTelemetryPublishInput +} from '../../models/uwt-telemetry-monitor.models/uwt-telemetry-monitor.models'; +import { + uwtDeepClone, + uwtSafeVoid +} from '../../utils/uwt-telemetry-safe.util/uwt-telemetry-safe.util'; + +/** + * A read-only view of every event this library hands to a destination — + * the telemetry sink or the application's log API provider. + * + * Each hand-off site publishes here immediately *after* the hand-off, with + * the very object it just sent. Nothing is sent from here: this is an + * in-memory stream for observers such as the Event Inspector, never a + * second delivery path, so observing it cannot cause an event to be sent + * twice. + * + * Costs one check per event while nobody subscribes. A subscriber receives + * a deep copy of each payload, so it can neither change what was sent nor + * see a live object change after the fact. + * + * "Handed over" is not "delivered": a sink may still drop an event, for + * example when RUM samples a session out or reaches its event limit. + * + * @example + * ```typescript + * inject(UwtTelemetryMonitor) + * .events$.pipe(filter((e) => e.kind === 'scenario')) + * .subscribe((e) => console.table(e.payload)); + * ``` + * + * @group Services + */ +@Injectable({ providedIn: 'root' }) +export class UwtTelemetryMonitor implements OnDestroy { + private readonly subject = new Subject(); + private sequence = 0; + + /** Every observed event, from the moment of subscription onward. */ + readonly events$: Observable = + this.subject.asObservable(); + + /** Whether anything is currently subscribed to {@link events$}. */ + get observed(): boolean { + return this.subject.observed; + } + + /** + * Records one hand-off. Called by the library's own hand-off sites; an + * application has no reason to call it. + * + * @param input the event just handed over, and where it went + * @returns the sequence number assigned, or `undefined` when nobody is + * subscribed and the event was not recorded + */ + publish(input: UwtTelemetryPublishInput): number | undefined { + if (!this.subject.observed) { + return undefined; + } + + const sequence = ++this.sequence; + uwtSafeVoid('monitor.publish', () => { + this.subject.next({ + ...input, + payload: uwtDeepClone(input.payload), + sequence, + capturedAt: new Date().toISOString() + } as UwtTelemetryObservedEvent); + }); + return sequence; + } + + /** @inheritdoc */ + ngOnDestroy(): void { + this.subject.complete(); + } +} diff --git a/projects/ngx-ui-watchtower/src/lib/sinks/uwt-broadcast/uwt-broadcast-host.service.ts b/projects/ngx-ui-watchtower/src/lib/sinks/uwt-broadcast/uwt-broadcast-host.service.ts index 22a781f..122746e 100644 --- a/projects/ngx-ui-watchtower/src/lib/sinks/uwt-broadcast/uwt-broadcast-host.service.ts +++ b/projects/ngx-ui-watchtower/src/lib/sinks/uwt-broadcast/uwt-broadcast-host.service.ts @@ -11,15 +11,27 @@ import { uwtIsBroadcastMessage } from './uwt-broadcast.messages'; import { UwtTelemetrySink } from '../uwt-telemetry/uwt-telemetry-abstract.sink/uwt-telemetry-abstract.sink'; +import { + UwtTelemetrySinkEvent, + uwtClassifyTelemetryEvent +} from '../../utils/uwt-telemetry-event.util/uwt-telemetry-event.util'; import { uwtTelemetryEnabled } from '../../config/uwt-telemetry-common.config/uwt-telemetry-common.config'; import { UwtBroadcastTelemetrySink } from './uwt-broadcast-telemetry.sink'; import { UwtBroadcastLogApiProvider } from '../../providers/uwt-broadcast-log-api.provider/uwt-broadcast-log-api.provider'; +import { UwtTelemetryMonitor } from '../../services/uwt-telemetry-monitor.service/uwt-telemetry-monitor.service'; +import { + UwtJsonObject, + UwtTelemetryEventOrigin +} from '../../models/uwt-telemetry-monitor.models/uwt-telemetry-monitor.models'; import { UwtLogRecord } from '../../models/uwt-log.models/uwt-log.models'; +import { + UwtTelemetryError, + UwtTelemetryMetadata +} from '../../models/uwt-telemetry-common.models/uwt-telemetry-common.models'; import { UWT_LOG_API_PROVIDER, UwtLogQuery } from '../../providers/uwt-log-api.provider/uwt-log-api.provider'; -import { uwtClassifyTelemetryEvent } from '../../utils/uwt-telemetry-event.util/uwt-telemetry-event.util'; import { uwtSessionElapsed } from '../../utils/uwt-session-start.util/uwt-session-start.util'; /** The identity fields scenario, BI and log records carry. */ @@ -35,6 +47,16 @@ interface UwtIdentified { */ const hostsInThisRealm = new Map(); +/** + * A forwarded error-level log record, kept for exactly one message in case + * the next is its `mirrorErrorsToRum` copy. + */ +interface UwtForwardedErrorLog { + application: string; + expected: UwtTelemetryError; + sequence: number; +} + /** * Receives telemetry forwarded by follower realms and records it through this * realm's sink. @@ -75,6 +97,7 @@ const hostsInThisRealm = new Map(); @Injectable() export class UwtTelemetryBroadcastHost implements OnDestroy { private readonly sink = inject(UwtTelemetrySink); + private readonly monitor = inject(UwtTelemetryMonitor); private readonly logApi = inject(UWT_LOG_API_PROVIDER); private readonly connection: UwtBroadcastConnection = uwtConnectBroadcastChannel('broadcastHost'); @@ -96,6 +119,9 @@ export class UwtTelemetryBroadcastHost implements OnDestroy { private releaseLeadership: () => void = () => undefined; private readonly enabled = uwtTelemetryEnabled(); + /** The last message, when it was an error-level log record. */ + private lastErrorLog?: UwtForwardedErrorLog; + constructor() { if (!this.enabled) { // Telemetry is off: no listening, no leader election. @@ -145,17 +171,41 @@ export class UwtTelemetryBroadcastHost implements OnDestroy { this._received++; } + // A mirror arrives straight after its log record, so the candidate + // lives for one message only. + const errorLog = this.lastErrorLog; + this.lastErrorLog = undefined; + switch (data.kind) { - case 'event': - this.sink.record( + case 'event': { + const { kind } = uwtClassifyTelemetryEvent( data.eventType, - this.stampEvent(data.eventType, data.payload), - data.metadata + data.payload ); + const payload = this.stampEvent(kind, data.payload); + this.sink.record(data.eventType, payload, data.metadata); + this.monitor.publish({ + kind, + eventType: data.eventType, + payload: payload as UwtJsonObject, + destination: 'sink', + origin: forwardedFrom(data.metadata?.application) + }); this.reannounceIfIdentityChanged(); break; + } case 'error': this.sink.recordError(data.error, data.metadata); + this.monitor.publish({ + kind: 'error', + payload: data.error, + ...(errorLog && + isMirrorOf(errorLog, data.error, data.metadata) && { + relatedSequence: errorLog.sequence + }), + destination: 'sink', + origin: forwardedFrom(data.metadata?.application) + }); this.reannounceIfIdentityChanged(); break; case 'user': @@ -197,8 +247,10 @@ export class UwtTelemetryBroadcastHost implements OnDestroy { * A forwarded scenario or BI record with this realm's identity; any other * payload as it is. */ - private stampEvent(eventType: string, payload: object): object { - const { kind } = uwtClassifyTelemetryEvent(eventType, payload); + private stampEvent( + kind: UwtTelemetrySinkEvent['kind'], + payload: object + ): object { return kind === 'unknown' ? payload : this.withIdentity(payload as UwtIdentified, kind === 'scenario'); @@ -235,9 +287,29 @@ export class UwtTelemetryBroadcastHost implements OnDestroy { return stamped; } - /** Hands a follower's record to this realm's log API provider. */ + /** + * Hands a follower's record to this realm's log API provider, and tells + * the monitor once it has — as {@link UwtLoggerService} does for its own. + */ private deliverLog(record: UwtLogRecord): void { - uwtSafeVoidMaybeAsync('broadcastHost.log', () => this.logApi.send(record)); + uwtSafeVoidMaybeAsync('broadcastHost.log', () => { + const pending = this.logApi.send(record); + const sequence = this.monitor.publish({ + kind: 'log', + payload: record, + destination: 'log-provider', + origin: forwardedFrom(record.application) + }); + if (record.level === 'error' && sequence !== undefined) { + this.lastErrorLog = { + application: record.application, + // What UwtLoggerService mirrors to RUM for this record. + expected: record.error ?? { name: 'Error', message: record.message }, + sequence + }; + } + return pending; + }); } /** @@ -357,3 +429,32 @@ export class UwtTelemetryBroadcastHost implements OnDestroy { ); } } + +/** + * Whether a forwarded error is the `mirrorErrorsToRum` copy of the error-level + * log record just before it: same realm, same name and message. A mirror + * the logger had to rewrite simply goes unlinked. + */ +function isMirrorOf( + errorLog: UwtForwardedErrorLog, + error: UwtTelemetryError, + metadata: UwtTelemetryMetadata | undefined +): boolean { + return ( + metadata?.application === errorLog.application && + error.name === errorLog.expected.name && + error.message === errorLog.expected.message + ); +} + +/** + * The forwarding realm: the `application` a follower sink stamps onto + * metadata, or a log record's own. + */ +function forwardedFrom( + application: UwtTelemetryMetadata[string] | undefined +): UwtTelemetryEventOrigin { + return typeof application === 'string' + ? { forwarded: true, application } + : { forwarded: true }; +} diff --git a/projects/ngx-ui-watchtower/src/lib/sinks/uwt-broadcast/uwt-broadcast.spec.ts b/projects/ngx-ui-watchtower/src/lib/sinks/uwt-broadcast/uwt-broadcast.spec.ts index bd4adfb..9028251 100644 --- a/projects/ngx-ui-watchtower/src/lib/sinks/uwt-broadcast/uwt-broadcast.spec.ts +++ b/projects/ngx-ui-watchtower/src/lib/sinks/uwt-broadcast/uwt-broadcast.spec.ts @@ -36,6 +36,8 @@ import { uwtIsBroadcastMessage } from './uwt-broadcast.messages'; import { UwtTelemetrySink } from '../uwt-telemetry/uwt-telemetry-abstract.sink/uwt-telemetry-abstract.sink'; +import { UwtTelemetryMonitor } from '../../services/uwt-telemetry-monitor.service/uwt-telemetry-monitor.service'; +import { UwtTelemetryObservedEvent } from '../../models/uwt-telemetry-monitor.models/uwt-telemetry-monitor.models'; import { uwtMarkSessionStart } from '../../utils/uwt-session-start.util/uwt-session-start.util'; /** @@ -45,6 +47,7 @@ import { uwtMarkSessionStart } from '../../utils/uwt-session-start.util/uwt-sess function createRealm(providers: unknown[]): Injector { return Injector.create({ providers: [ + UwtTelemetryMonitor, { provide: PLATFORM_ID, useValue: 'browser' }, { provide: DOCUMENT, useValue: document }, { @@ -378,6 +381,69 @@ describe('broadcast telemetry across realms', () => { expect(shellSink.events[0].metadata).toMatchObject({ feature: 'cart' }); }); + it('should show a forwarded event to the shell monitor, marked with its realm', async () => { + const observed: UwtTelemetryObservedEvent[] = []; + shell.get(UwtTelemetryMonitor).events$.subscribe((e) => observed.push(e)); + const fragment = createFragment(); + + fragment + .get(UwtTelemetrySink) + .record('com.uwt.scenario.step', { scenarioName: 'load' }); + fragment.get(UwtTelemetrySink).record('com.uwt.bi', { eventName: 'x' }); + fragment.get(UwtTelemetrySink).record('custom.type', { a: 1 }); + fragment + .get(UwtTelemetrySink) + .recordError({ name: 'TypeError', message: 'boom' }); + await UwtBroadcastChannelStub.settle(); + + expect(observed.map((e) => e.kind)).toEqual([ + 'scenario-step', + 'bi', + 'unknown', + 'error' + ]); + for (const e of observed) { + expect(e.origin).toEqual({ forwarded: true, application: 'realm' }); + expect(e.destination).toBe('sink'); + } + expect(shellSink.events).toHaveLength(3); + expect(shellSink.errors).toHaveLength(1); + }); + + it('should show a fragment scenario once per monitor — local in the fragment, forwarded in the shell', async () => { + const fragment = createFragment(); + const inShell: UwtTelemetryObservedEvent[] = []; + const inFragment: UwtTelemetryObservedEvent[] = []; + shell.get(UwtTelemetryMonitor).events$.subscribe((e) => inShell.push(e)); + fragment + .get(UwtTelemetryMonitor) + .events$.subscribe((e) => inFragment.push(e)); + + fragment + .get(UwtScenarioTelemetryService) + .start({ name: 'load' }) + .complete(); + await UwtBroadcastChannelStub.settle(); + + expect(fragment.get(UwtTelemetryMonitor)).not.toBe( + shell.get(UwtTelemetryMonitor) + ); + expect(inFragment).toHaveLength(1); + expect(inFragment[0]).toMatchObject({ + kind: 'scenario', + origin: { forwarded: false } + }); + expect(inShell).toHaveLength(1); + expect(inShell[0]).toMatchObject({ + kind: 'scenario', + origin: { forwarded: true, application: 'realm' } + }); + + expect( + shellSink.events.filter((e) => e.eventType === 'com.uwt.scenario') + ).toHaveLength(1); + }); + it('should forward handled errors', async () => { const fragment = createFragment(); fragment @@ -542,6 +608,23 @@ describe('broadcast telemetry across realms', () => { expect(event.payload.userId).toBeUndefined(); }); + it('should show the shell monitor the record as stamped and sent', async () => { + const observed: UwtTelemetryObservedEvent[] = []; + shell.get(UwtTelemetryMonitor).events$.subscribe((e) => observed.push(e)); + + createFragment() + .get(UwtTelemetrySink) + .record(UWT_TELEMETRY_EVENT_TYPE.bi, { + eventName: 'x', + sessionId: 'expired-session' + }); + await UwtBroadcastChannelStub.settle(); + + expect(observed.find((e) => e.kind === 'bi')?.payload).toMatchObject({ + sessionId: 'test-session' + }); + }); + it("should leave a custom event's payload as it is", async () => { createFragment() .get(UwtTelemetrySink) @@ -717,6 +800,35 @@ describe('broadcast telemetry across realms', () => { expect(fragment.get(RecordingLogApi).records).toHaveLength(0); }); + it('should show a forwarded record once per monitor — local in the fragment, forwarded in the shell', async () => { + const fragment = createFragment(); + const inShell: UwtTelemetryObservedEvent[] = []; + const inFragment: UwtTelemetryObservedEvent[] = []; + shell.get(UwtTelemetryMonitor).events$.subscribe((e) => inShell.push(e)); + fragment + .get(UwtTelemetryMonitor) + .events$.subscribe((e) => inFragment.push(e)); + + fragment.get(UwtLoggerService).getLogger('cart').log('Hello'); + await UwtBroadcastChannelStub.settle(); + + expect(inFragment).toEqual([ + expect.objectContaining({ + kind: 'log', + destination: 'log-provider', + origin: { forwarded: false } + }) + ]); + expect(inShell).toEqual([ + expect.objectContaining({ + kind: 'log', + destination: 'log-provider', + origin: { forwarded: true, application: 'realm' }, + payload: expect.objectContaining({ message: 'Hello' }) + }) + ]); + }); + it('should forward a flush to the shell log provider', async () => { const fragment = createFragment(); fragment.get(UWT_LOG_API_PROVIDER).flush?.(); @@ -807,6 +919,7 @@ describe('broadcast telemetry across realms', () => { it('should fail at injection when the shell binds no log provider', () => { const bareShell = Injector.create({ providers: [ + UwtTelemetryMonitor, { provide: PLATFORM_ID, useValue: 'browser' }, RecordingSink, { provide: UwtTelemetrySink, useExisting: RecordingSink }, @@ -818,6 +931,64 @@ describe('broadcast telemetry across realms', () => { /UWT_LOG_API_PROVIDER/ ); }); + + it('should link a mirrored error to its forwarded log record in the shell monitor', async () => { + const fragment = createRealm([ + UwtBroadcastTelemetrySink, + { provide: UwtTelemetrySink, useExisting: UwtBroadcastTelemetrySink }, + UwtBroadcastLogApiProvider, + { + provide: UWT_LOG_API_PROVIDER, + useExisting: UwtBroadcastLogApiProvider + }, + { + provide: UWT_LOG_CONFIG, + useValue: { + ...UWT_DEFAULT_TELEMETRY_CONFIG.logs, + mirrorErrorsToRum: true + } + } + ]); + const inShell: UwtTelemetryObservedEvent[] = []; + shell.get(UwtTelemetryMonitor).events$.subscribe((e) => inShell.push(e)); + + const logger = fragment.get(UwtLoggerService).getLogger('cart'); + logger.error('Payment failed'); + logger.error('Card declined', { error: new TypeError('declined') }); + await UwtBroadcastChannelStub.settle(); + + expect(inShell.map((e) => e.kind)).toEqual([ + 'log', + 'error', + 'log', + 'error' + ]); + const [firstLog, firstMirror, secondLog, secondMirror] = inShell; + expect(firstMirror).toMatchObject({ + payload: { name: 'Error', message: 'Payment failed' }, + relatedSequence: firstLog.sequence + }); + expect(secondMirror).toMatchObject({ + payload: { name: 'TypeError', message: 'declined' }, + relatedSequence: secondLog.sequence + }); + }); + + it('should not link an error that is not the mirror of the record before it', async () => { + const fragment = createFragment(); + const inShell: UwtTelemetryObservedEvent[] = []; + shell.get(UwtTelemetryMonitor).events$.subscribe((e) => inShell.push(e)); + + fragment.get(UwtLoggerService).getLogger('cart').error('Payment failed'); + fragment + .get(UwtTelemetrySink) + .recordError({ name: 'Error', message: 'Something else' }); + await UwtBroadcastChannelStub.settle(); + + const error = inShell.find((e) => e.kind === 'error'); + expect(error).toBeDefined(); + expect(error).not.toHaveProperty('relatedSequence'); + }); }); describe('robustness', () => { diff --git a/projects/ngx-ui-watchtower/src/public-api.ts b/projects/ngx-ui-watchtower/src/public-api.ts index 795967e..3ba7c87 100644 --- a/projects/ngx-ui-watchtower/src/public-api.ts +++ b/projects/ngx-ui-watchtower/src/public-api.ts @@ -85,11 +85,21 @@ export type { UwtTelemetryEventTypes, UwtTelemetryMetadata } from './lib/models/uwt-telemetry-common.models/uwt-telemetry-common.models'; +export { + UwtJsonObject, + UwtJsonValue, + UwtTelemetryDestination, + UwtTelemetryEventKind, + UwtTelemetryEventOrigin, + UwtTelemetryObservedEvent, + UwtTelemetryPublishInput +} from './lib/models/uwt-telemetry-monitor.models/uwt-telemetry-monitor.models'; // Services export { UwtBITelemetryService } from './lib/services/uwt-bi-telemetry.service/uwt-bi-telemetry.service'; export { UwtLoggerService } from './lib/services/uwt-logger.service/uwt-logger.service'; export { UwtScenario } from './lib/scenario/uwt-scenario/uwt-scenario'; +export { UwtTelemetryMonitor } from './lib/services/uwt-telemetry-monitor.service/uwt-telemetry-monitor.service'; export { UwtScenarioTelemetryService } from './lib/services/uwt-scenario-telemetry.service/uwt-scenario-telemetry.service'; // Sinks diff --git a/projects/watchtower-consumer-smoke/package.json b/projects/watchtower-consumer-smoke/package.json new file mode 100644 index 0000000..1dfd57c --- /dev/null +++ b/projects/watchtower-consumer-smoke/package.json @@ -0,0 +1,5 @@ +{ + "name": "watchtower-consumer-smoke", + "private": true, + "description": "A minimal app consuming @absaoss-cps/ngx-ui-watchtower as published: `npm run smoke` packs the library and sets this app up on its own, outside the workspace, with the tarball installed." +} diff --git a/projects/watchtower-consumer-smoke/smoke.mjs b/projects/watchtower-consumer-smoke/smoke.mjs new file mode 100644 index 0000000..a707a43 --- /dev/null +++ b/projects/watchtower-consumer-smoke/smoke.mjs @@ -0,0 +1,238 @@ +// `npm run smoke`: the library as a consumer gets it. Packs the built +// package, then sets this app up on its own, in a folder outside the +// workspace: its own package.json and angular.json, and an `npm install` +// with npm's peer checks, so nothing comes from the workspace. Builds it for +// the browser and the server, and checks the result. Prints a line per +// check; exits non-zero if any fails, keeping the folder to look into. +import { execFileSync, spawnSync } from 'node:child_process'; +import { + cpSync, + existsSync, + mkdtempSync, + readdirSync, + readFileSync, + rmSync, + writeFileSync +} from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const ROOT = fileURLToPath(new URL('../../', import.meta.url)); +const APP = join(ROOT, 'projects/watchtower-consumer-smoke'); +const PACKAGE = join(ROOT, 'dist/ngx-ui-watchtower'); +const shell = process.platform === 'win32'; + +/** What the app depends on, at the versions the workspace has. */ +const DEPENDENCIES = [ + '@angular/animations', + '@angular/common', + '@angular/compiler', + '@angular/core', + '@angular/forms', + '@angular/platform-browser', + '@angular/platform-server', + '@angular/router', + '@angular/ssr', + 'aws-rum-web', + 'cps-ui-kit', + 'rxjs', + 'tslib' +]; +const DEV_DEPENDENCIES = [ + '@angular/build', + '@angular/cli', + '@angular/compiler-cli', + 'typescript' +]; + +let failures = 0; +let consumer; +function check(label, ok, detail) { + console.log(`${ok ? 'PASS' : 'FAIL'} ${label}`); + if (!ok) { + failures++; + if (detail !== undefined) { + console.log(` ${JSON.stringify(detail)}`); + } + } +} + +/** Runs a command with its output shown; whether it succeeded. */ +function run(command, args, cwd = ROOT) { + console.log(`\n$ ${command} ${args.join(' ')}`); + return ( + spawnSync(command, args, { + cwd, + stdio: 'inherit', + shell, + env: { ...process.env, NG_CLI_ANALYTICS: 'false' } + }).status === 0 + ); +} + +/** Whether any script in a build's browser output contains `text`. */ +function bundleContains(outputPath, text) { + const browser = join(outputPath, 'browser'); + return readdirSync(browser) + .filter((file) => file.endsWith('.js')) + .some((file) => readFileSync(join(browser, file), 'utf8').includes(text)); +} + +/** A package's version as the workspace has it installed. */ +function version(name) { + return JSON.parse( + readFileSync(join(ROOT, 'node_modules', name, 'package.json'), 'utf8') + ).version; +} + +function finish() { + if (consumer && failures) { + console.log(`\nThe app is left in ${consumer}`); + } else if (consumer) { + rmSync(consumer, { recursive: true, force: true }); + } + console.log(failures ? `\n${failures} FAILED` : '\nALL PASSED'); + process.exit(failures ? 1 : 0); +} + +// 1. The package, as `npm publish` would send it. +if (!run('npm', ['run', 'build'])) { + check('the library builds', false); + finish(); +} +const packDir = mkdtempSync(join(tmpdir(), 'uwt-smoke-')); +const [packed] = JSON.parse( + execFileSync( + 'npm', + ['pack', PACKAGE, '--pack-destination', packDir, '--json'], + { cwd: ROOT, shell } + ) +); +const files = packed.files.map((f) => f.path); +const expected = [ + 'package.json', + 'README.md', + 'LICENSE', + ...['', '-rum', '-event-inspector'].flatMap((entry) => [ + `fesm2022/absaoss-cps-ngx-ui-watchtower${entry}.mjs`, + `types/absaoss-cps-ngx-ui-watchtower${entry}.d.ts` + ]) +]; +check( + `the tarball has the manifest, README, LICENSE and all three entry points (${files.length} files)`, + expected.every((file) => files.includes(file)), + expected.filter((file) => !files.includes(file)) +); +const isSource = (file) => + /(^|\/)src\/|\.spec\./.test(file) || + (file.endsWith('.ts') && !file.endsWith('.d.ts')); +check( + 'the tarball has no source or spec files', + !files.some(isSource), + files.filter(isSource) +); + +// 2. The app on its own, outside the workspace: its sources, and a manifest +// and build target of its own — the workspace's target, with its paths made +// the app's. +consumer = mkdtempSync(join(tmpdir(), 'uwt-consumer-')); +cpSync(join(APP, 'src'), join(consumer, 'src'), { recursive: true }); +cpSync(join(APP, 'tsconfig.app.json'), join(consumer, 'tsconfig.app.json')); +const pinned = (names) => + Object.fromEntries(names.map((name) => [name, version(name)])); +writeFileSync( + join(consumer, 'package.json'), + JSON.stringify( + { + name: 'watchtower-consumer', + private: true, + dependencies: { + ...pinned(DEPENDENCIES), + '@absaoss-cps/ngx-ui-watchtower': `file:${join(packDir, packed.filename)}` + }, + devDependencies: pinned(DEV_DEPENDENCIES) + }, + null, + 2 + ) +); +const target = JSON.parse(readFileSync(join(ROOT, 'angular.json'), 'utf8')) + .projects['watchtower-consumer-smoke']; +const own = JSON.parse( + JSON.stringify(target) + .replaceAll('projects/watchtower-consumer-smoke/', '') + .replaceAll('dist/watchtower-consumer-smoke', 'dist/app') +); +writeFileSync( + join(consumer, 'angular.json'), + JSON.stringify( + { + version: 1, + cli: { analytics: false }, + projects: { app: { ...own, root: '', sourceRoot: 'src' } } + }, + null, + 2 + ) +); + +// 3. Installed as a consumer installs it, peer checks and all: a peer the +// package leaves out or mis-ranges fails here, as it would for the app. +const installed = run( + 'npm', + ['install', '--no-package-lock', '--no-audit', '--no-fund'], + consumer +); +const library = join(consumer, 'node_modules/@absaoss-cps/ngx-ui-watchtower'); +check( + `the tarball installs, with its peers checked (${packed.id})`, + installed && + existsSync(join(library, 'package.json')) && + JSON.parse(readFileSync(join(library, 'package.json'), 'utf8')).version === + packed.version +); +rmSync(packDir, { recursive: true, force: true }); +check( + 'no second copy of Angular is installed beside it', + !existsSync(join(library, 'node_modules/@angular')) +); +if (failures) { + finish(); +} + +// 4. Built against the published typings and bundles, for the browser and, +// prerendering, the server. +check( + 'the app builds and prerenders without /rum', + run('npx', ['ng', 'build', 'app'], consumer) +); +check( + 'the app builds and prerenders with /rum', + run('npx', ['ng', 'build', 'app', '-c', 'rum'], consumer) +); +if (failures) { + finish(); +} + +// 5. The builds' output. The RUM SDK sends to `/appmonitors/`, a path in no +// part of this library, which minifying keeps. +const SDK = '/appmonitors/'; +check( + 'without /rum, no aws-rum-web in the bundle', + !bundleContains(join(consumer, 'dist/app'), SDK) +); +check( + 'with /rum, aws-rum-web is in the bundle — the check above can see it', + bundleContains(join(consumer, 'dist/app-rum'), SDK) +); +const prerendered = readFileSync( + join(consumer, 'dist/app/browser/index.html'), + 'utf8' +); +check( + 'the page was rendered on the server', + /]*>ngx-ui-watchtower consumer smoke<\/h1>/.test(prerendered) +); + +finish(); diff --git a/projects/watchtower-consumer-smoke/src/app.component.ts b/projects/watchtower-consumer-smoke/src/app.component.ts new file mode 100644 index 0000000..07f7b58 --- /dev/null +++ b/projects/watchtower-consumer-smoke/src/app.component.ts @@ -0,0 +1,37 @@ +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; +import { + UwtBITelemetryService, + UwtLoggerService, + UwtScenarioTelemetryService +} from '@absaoss-cps/ngx-ui-watchtower'; +import { UwtEventInspectorService } from '@absaoss-cps/ngx-ui-watchtower/event-inspector'; + +/** Uses the library the way an app would, through its published API. */ +@Component({ + selector: 'smoke-root', + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` +

ngx-ui-watchtower consumer smoke

+ + + ` +}) +export class AppComponent { + protected readonly inspector = inject(UwtEventInspectorService); + + private readonly bi = inject(UwtBITelemetryService); + private readonly logger = inject(UwtLoggerService).getLogger('smoke'); + + constructor() { + const scenario = inject(UwtScenarioTelemetryService).start({ + name: 'smoke-render' + }); + scenario.step('render'); + scenario.complete(); + this.logger.log('Rendered'); + } + + protected track(): void { + this.bi.track('smoke_clicked'); + } +} diff --git a/projects/watchtower-consumer-smoke/src/app.config.server.ts b/projects/watchtower-consumer-smoke/src/app.config.server.ts new file mode 100644 index 0000000..d7b98b8 --- /dev/null +++ b/projects/watchtower-consumer-smoke/src/app.config.server.ts @@ -0,0 +1,17 @@ +import { mergeApplicationConfig } from '@angular/core'; +import { + provideServerRendering, + RenderMode, + ServerRoute, + withRoutes +} from '@angular/ssr'; +import { appConfig } from './app.config'; + +/** Rendered on the server at build time: the build fails if that throws. */ +const serverRoutes: ServerRoute[] = [ + { path: '**', renderMode: RenderMode.Prerender } +]; + +export const serverConfig = mergeApplicationConfig(appConfig, { + providers: [provideServerRendering(withRoutes(serverRoutes))] +}); diff --git a/projects/watchtower-consumer-smoke/src/app.config.ts b/projects/watchtower-consumer-smoke/src/app.config.ts new file mode 100644 index 0000000..f262737 --- /dev/null +++ b/projects/watchtower-consumer-smoke/src/app.config.ts @@ -0,0 +1,36 @@ +import { + ApplicationConfig, + provideZonelessChangeDetection +} from '@angular/core'; +import { + provideClientHydration, + withEventReplay +} from '@angular/platform-browser'; +import { provideAnimations } from '@angular/platform-browser/animations'; +import { + UWT_LOG_API_PROVIDER, + UwtNoopLogApiProvider, + provideUwtTelemetry, + withBIEvents, + withLogging, + withScenarios +} from '@absaoss-cps/ngx-ui-watchtower'; +import { provideUwtEventInspector } from '@absaoss-cps/ngx-ui-watchtower/event-inspector'; +import { destination } from './destination'; + +export const appConfig: ApplicationConfig = { + providers: [ + provideZonelessChangeDetection(), + provideClientHydration(withEventReplay()), + provideAnimations(), + provideUwtTelemetry( + { application: 'consumer-smoke', environment: 'smoke', version: '1.0.0' }, + withLogging({ minLevel: 'log' }), + withScenarios(), + withBIEvents() + ), + destination, + { provide: UWT_LOG_API_PROVIDER, useClass: UwtNoopLogApiProvider }, + provideUwtEventInspector() + ] +}; diff --git a/projects/watchtower-consumer-smoke/src/destination.rum.ts b/projects/watchtower-consumer-smoke/src/destination.rum.ts new file mode 100644 index 0000000..4db82c8 --- /dev/null +++ b/projects/watchtower-consumer-smoke/src/destination.rum.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@angular/core'; +import { + UWT_RUM_CREDENTIALS_PROVIDER, + UwtRumBootstrap, + UwtRumCredentialsProvider, + provideUwtTelemetryRumSink +} from '@absaoss-cps/ngx-ui-watchtower/rum'; + +/** RUM, switched off by its broker: the smoke test sends nothing. */ +@Injectable({ providedIn: 'root' }) +class SmokeRumCredentials implements UwtRumCredentialsProvider { + async load(): Promise { + return null; + } +} + +/** + * The `rum` configuration's destination: the published `/rum` entry point, + * whose `aws-rum-web` the smoke test must then find in the build. + */ +export const destination = [ + provideUwtTelemetryRumSink(), + { provide: UWT_RUM_CREDENTIALS_PROVIDER, useExisting: SmokeRumCredentials } +]; diff --git a/projects/watchtower-consumer-smoke/src/destination.ts b/projects/watchtower-consumer-smoke/src/destination.ts new file mode 100644 index 0000000..88ff618 --- /dev/null +++ b/projects/watchtower-consumer-smoke/src/destination.ts @@ -0,0 +1,8 @@ +import { provideUwtTelemetrySink } from '@absaoss-cps/ngx-ui-watchtower'; + +/** + * Where the telemetry goes: nowhere, and without importing `/rum`. The + * smoke test checks this build has no `aws-rum-web` in it; the `rum` + * configuration swaps in `destination.rum.ts`. + */ +export const destination = [provideUwtTelemetrySink('noop')]; diff --git a/projects/watchtower-consumer-smoke/src/index.html b/projects/watchtower-consumer-smoke/src/index.html new file mode 100644 index 0000000..ffecf8b --- /dev/null +++ b/projects/watchtower-consumer-smoke/src/index.html @@ -0,0 +1,12 @@ + + + + + ngx-ui-watchtower consumer smoke + + + + + + + diff --git a/projects/watchtower-consumer-smoke/src/main.server.ts b/projects/watchtower-consumer-smoke/src/main.server.ts new file mode 100644 index 0000000..00232f2 --- /dev/null +++ b/projects/watchtower-consumer-smoke/src/main.server.ts @@ -0,0 +1,11 @@ +import { + BootstrapContext, + bootstrapApplication +} from '@angular/platform-browser'; +import { AppComponent } from './app.component'; +import { serverConfig } from './app.config.server'; + +const bootstrap = (context: BootstrapContext) => + bootstrapApplication(AppComponent, serverConfig, context); + +export default bootstrap; diff --git a/projects/watchtower-consumer-smoke/src/main.ts b/projects/watchtower-consumer-smoke/src/main.ts new file mode 100644 index 0000000..de87a2b --- /dev/null +++ b/projects/watchtower-consumer-smoke/src/main.ts @@ -0,0 +1,7 @@ +import { bootstrapApplication } from '@angular/platform-browser'; +import { AppComponent } from './app.component'; +import { appConfig } from './app.config'; + +bootstrapApplication(AppComponent, appConfig).catch((error: unknown) => + console.error(error) +); diff --git a/projects/watchtower-consumer-smoke/src/telemetry.schema.ts b/projects/watchtower-consumer-smoke/src/telemetry.schema.ts new file mode 100644 index 0000000..8d92e28 --- /dev/null +++ b/projects/watchtower-consumer-smoke/src/telemetry.schema.ts @@ -0,0 +1,18 @@ +// The app's names, declared on the published typings as the README tells +// applications to. +declare module '@absaoss-cps/ngx-ui-watchtower' { + interface UwtScenarioNames { + 'smoke-render': true; + } + interface UwtScenarioSteps { + render: true; + } + interface UwtBIEventNames { + smoke_clicked: true; + } + interface UwtLoggerNames { + smoke: true; + } +} + +export {}; diff --git a/projects/watchtower-consumer-smoke/tsconfig.app.json b/projects/watchtower-consumer-smoke/tsconfig.app.json new file mode 100644 index 0000000..5804ad9 --- /dev/null +++ b/projects/watchtower-consumer-smoke/tsconfig.app.json @@ -0,0 +1,31 @@ +/* + * The consumer smoke app, configured like any app: it doesn't extend the + * workspace's tsconfig, so there are no paths into the library's source — + * only the package installed in ./node_modules. + */ +{ + "compileOnSave": false, + "compilerOptions": { + "outDir": "../../out-tsc/watchtower-consumer-smoke", + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "isolatedModules": true, + "experimentalDecorators": true, + "importHelpers": true, + "target": "ES2022", + "module": "ES2022", + "moduleResolution": "bundler", + "types": [] + }, + "angularCompilerOptions": { + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + }, + "files": ["src/main.ts", "src/main.server.ts"], + "include": ["src/**/*.ts"] +} diff --git a/projects/watchtower-lab-fragment/src/fragment.component.ts b/projects/watchtower-lab-fragment/src/fragment.component.ts new file mode 100644 index 0000000..7ecce44 --- /dev/null +++ b/projects/watchtower-lab-fragment/src/fragment.component.ts @@ -0,0 +1,118 @@ +import { + ChangeDetectionStrategy, + Component, + DestroyRef, + inject, + signal +} from '@angular/core'; +import { + UwtBITelemetryService, + UwtLoggerService, + UwtScenarioTelemetryService, + UwtTelemetrySink +} from '@absaoss-cps/ngx-ui-watchtower'; +import { CpsButtonComponent } from 'cps-ui-kit'; + +const CARLA = '5c8e2f14-1b7d-4a96-9e3c-8d2b7f4a6c10'; + +@Component({ + selector: 'lab-fragment-root', + imports: [CpsButtonComponent], + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` +
+

Embedded application

+

+ Session {{ identity().sessionId ?? '—' }} · user + {{ identity().userId ?? '—' }}, as the shell announced them. +

+
+ + + + + + + +
+
{{ readout() || 'Nothing yet.' }}
+
+ `, + styles: ` + .lab-fragment { + padding: 1rem; + } + ` +}) +export class FragmentComponent { + protected readonly sink = inject(UwtTelemetrySink); + protected readonly carla = CARLA; + protected readonly identity = signal<{ sessionId?: string; userId?: string }>( + {} + ); + + protected readonly readout = signal(''); + + private readonly bi = inject(UwtBITelemetryService); + private readonly scenarios = inject(UwtScenarioTelemetryService); + private readonly loggerService = inject(UwtLoggerService); + private readonly logger = this.loggerService.getLogger('fragment'); + + constructor() { + const timer = setInterval( + () => + this.identity.set({ + sessionId: this.sink.getSessionId(), + userId: this.sink.getUserId() + }), + 1000 + ); + inject(DestroyRef).onDestroy(() => clearInterval(timer)); + } + + protected track(): void { + this.bi.track('fragment_button_clicked', { at: Date.now() }); + this.readout.set('fragment_button_clicked sent to the shell.'); + } + + protected scenario(): void { + const journey = this.scenarios.start({ name: 'fragment-journey' }); + journey.step('fetch'); + journey.step('render'); + journey.complete(); + this.readout.set('fragment-journey completed and sent to the shell.'); + } + + protected warn(): void { + this.logger.warn('A warning from the fragment'); + this.readout.set('Warning sent to the shell, for its log backend.'); + } + + /** Mirrored to RUM: the shell records it, named for this fragment. */ + protected error(): void { + this.logger.error('The fragment failed', { + error: new Error('Fragment boom') + }); + this.readout.set('Error sent to the shell, and mirrored to RUM there.'); + } + + protected async query(): Promise { + const records = await this.loggerService.query({ logger: 'fragment' }); + this.readout.set( + `${records.length} record(s), answered by the shell's log backend:\n` + + JSON.stringify(records, null, 2) + ); + } +} diff --git a/projects/watchtower-lab-fragment/src/fragment.config.ts b/projects/watchtower-lab-fragment/src/fragment.config.ts new file mode 100644 index 0000000..dbfd398 --- /dev/null +++ b/projects/watchtower-lab-fragment/src/fragment.config.ts @@ -0,0 +1,52 @@ +import { + ApplicationConfig, + provideZonelessChangeDetection +} from '@angular/core'; +import { provideAnimations } from '@angular/platform-browser/animations'; +import { + UWT_BROADCAST_CHANNEL, + UWT_DEFAULT_BROADCAST_CHANNEL, + provideUwtTelemetry, + provideUwtTelemetrySink, + withLogging +} from '@absaoss-cps/ngx-ui-watchtower'; + +declare global { + interface Window { + __uwtTelemetryChannel?: string; + } +} + +/** + * The shell's channel for this tab, published on the top-level window — as + * a plain iframe or a Web Fragment, the fragment shares the shell's origin, + * so `window.top` reaches it. On its own, the default channel. + */ +function shellChannel(): string { + try { + return window.top?.__uwtTelemetryChannel ?? UWT_DEFAULT_BROADCAST_CHANNEL; + } catch { + return UWT_DEFAULT_BROADCAST_CHANNEL; + } +} + +/** + * The fragment: zoneless, and forwarding everything — events, log records, + * identity — over the shell's channel, to the shell, which records it. + */ +export const fragmentConfig: ApplicationConfig = { + providers: [ + provideZonelessChangeDetection(), + provideAnimations(), + provideUwtTelemetry( + { + application: 'Embedded application', + environment: 'lab', + version: '1.0.0' + }, + withLogging({ minLevel: 'log', mirrorErrorsToRum: true }) + ), + provideUwtTelemetrySink('broadcast'), + { provide: UWT_BROADCAST_CHANNEL, useFactory: shellChannel } + ] +}; diff --git a/projects/watchtower-lab-fragment/src/index.html b/projects/watchtower-lab-fragment/src/index.html new file mode 100644 index 0000000..5a7de71 --- /dev/null +++ b/projects/watchtower-lab-fragment/src/index.html @@ -0,0 +1,13 @@ + + + + + Embedded application · Watchtower Lab + + + + + + + + diff --git a/projects/watchtower-lab-fragment/src/main.ts b/projects/watchtower-lab-fragment/src/main.ts new file mode 100644 index 0000000..6dc07c4 --- /dev/null +++ b/projects/watchtower-lab-fragment/src/main.ts @@ -0,0 +1,11 @@ +import { bootstrapApplication } from '@angular/platform-browser'; +import { FragmentComponent } from './fragment.component'; +import { fragmentConfig } from './fragment.config'; + +/** + * The lab's fragment: a second app, shown by the shell in a plain iframe and + * as a Web Fragment. Either way it runs in its own JavaScript realm. + */ +bootstrapApplication(FragmentComponent, fragmentConfig).catch( + (error: unknown) => console.error(error) +); diff --git a/projects/watchtower-lab-fragment/src/styles.scss b/projects/watchtower-lab-fragment/src/styles.scss new file mode 100644 index 0000000..844892e --- /dev/null +++ b/projects/watchtower-lab-fragment/src/styles.scss @@ -0,0 +1,8 @@ +// The same base as the shell's. +@use '../../watchtower-lab/src/lab-base'; + +// Embedded, it sits in a white box: an iframe shows its own page, which +// would otherwise be the lab's grey. +body { + background-color: #fff; +} diff --git a/projects/watchtower-lab-fragment/src/telemetry.schema.ts b/projects/watchtower-lab-fragment/src/telemetry.schema.ts new file mode 100644 index 0000000..3cd23fc --- /dev/null +++ b/projects/watchtower-lab-fragment/src/telemetry.schema.ts @@ -0,0 +1,18 @@ +// Every scenario, step, BI event and logger name the fragment uses. +declare module '@absaoss-cps/ngx-ui-watchtower' { + interface UwtScenarioNames { + 'fragment-journey': true; + } + interface UwtScenarioSteps { + fetch: true; + render: true; + } + interface UwtBIEventNames { + fragment_button_clicked: true; + } + interface UwtLoggerNames { + fragment: true; + } +} + +export {}; diff --git a/projects/watchtower-lab-fragment/tsconfig.app.json b/projects/watchtower-lab-fragment/tsconfig.app.json new file mode 100644 index 0000000..9dff14a --- /dev/null +++ b/projects/watchtower-lab-fragment/tsconfig.app.json @@ -0,0 +1,10 @@ +/* The Watchtower Lab's fragment: a second app, built from the library's source. */ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "../../out-tsc/watchtower-lab-fragment", + "types": [] + }, + "files": ["src/main.ts"], + "include": ["src/**/*.ts"] +} diff --git a/projects/watchtower-lab/server/dev.mjs b/projects/watchtower-lab/server/dev.mjs new file mode 100644 index 0000000..0d2ae17 --- /dev/null +++ b/projects/watchtower-lab/server/dev.mjs @@ -0,0 +1,27 @@ +// `npm run lab`: the Watchtower Lab in development. The fragment is rebuilt on +// every change, and `ng serve` runs the shell with server/server.ts — the +// mock backend, the fragment's gateway and server-side rendering — on +// http://localhost:4280. The fragment's pages answer 503 until its first +// build is done. +import { spawn } from 'node:child_process'; + +const ng = (...args) => + spawn('npx', ['ng', ...args], { + stdio: 'inherit', + shell: process.platform === 'win32' + }); + +const children = [ + ng('build', 'watchtower-lab-fragment', '--watch'), + ng('serve', 'watchtower-lab') +]; + +const stop = () => children.forEach((child) => child.kill()); +process.on('SIGINT', stop); +process.on('SIGTERM', stop); +for (const child of children) { + child.on('exit', (code) => { + stop(); + process.exitCode ||= code ?? 0; + }); +} diff --git a/projects/watchtower-lab/server/lab-backend.ts b/projects/watchtower-lab/server/lab-backend.ts new file mode 100644 index 0000000..81b7226 --- /dev/null +++ b/projects/watchtower-lab/server/lab-backend.ts @@ -0,0 +1,419 @@ +// The lab's mock backend. It stands in for everything the library talks to, +// so the real aws-rum-web runs fully offline: +// +// GET /rum/init mock RUM broker: app monitor config and fake +// credentials; its mode decides the answer +// POST /appmonitors/:id mock RUM dataplane: keeps every PutRumEvents +// batch, beacons included +// POST /api/logs mock log backend: stores records +// GET /api/logs?… answers the log provider's query() +// GET /api/data?delay&status a slow or failing endpoint, for HTTP scenarios +// GET /__lab/rum, /__lab/logs, /__lab/state +// GET, POST /__lab/custom what the lab's custom sink was given +// POST /__lab/reset, /__lab/broker-mode, /__lab/telemetries, +// /__lab/broker-config test hooks +import { Request, Router } from 'express'; +import { promised } from './promised'; +import { gunzipSync } from 'node:zlib'; +import { + labClearServerSends, + labServerSends +} from '../src/lab/lab-server-sends'; + +const BROKER_MODES = [ + 'ok', + 'null', + 'fail', + 'short-expiry', + 'flaky', + 'invalid', + 'null-on-refresh', + 'no-credentials-on-refresh' +]; +const TELEMETRIES = ['errors', 'performance', 'interaction', 'http', 'replay']; +const LEVELS: Record = { log: 0, warn: 1, error: 2 }; + +interface LogRecord { + level: string; + logger?: string; + correlationId?: string; + timestamp?: string; +} + +interface ReceivedEvent { + id: string; + receivedAt: string; + batch: number; + user: Record; + type: string; + metadata: unknown; + details: unknown; +} + +// `telemetries: null` leaves them out of the broker's answer: the library's +// default applies. `brokerConfig` is laid over the answer's config, and +// `brokerCalls` counts `/rum/init` calls since the mode was set. +const state: { + brokerMode: string; + brokerCalls: number; + brokerConfig: Record; + telemetries: string[] | null; + batches: number; + rum: ReceivedEvent[]; + logs: LogRecord[]; + custom: unknown[]; +} = { + brokerMode: 'ok', + brokerCalls: 0, + brokerConfig: {}, + telemetries: null, + batches: 0, + rum: [], + logs: [], + custom: [] +}; + +/** + * The most a request may bring, before and after gunzipping, and the most + * of each kind the lab keeps — it holds what it receives in memory. + */ +const MAX_BODY_BYTES = 5 * 1024 * 1024; +const MAX_KEPT = 5000; + +/** Adds `items` to `list`, dropping the oldest past {@link MAX_KEPT}. */ +function keep(list: T[], items: T[]): void { + list.push(...items); + list.splice(0, Math.max(0, list.length - MAX_KEPT)); +} + +/** A JSON object, not an array or a value. */ +function isRecord(value: unknown): value is Record { + return value !== null && typeof value === 'object' && !Array.isArray(value); +} + +/** + * The body as text, gunzipped if RUM compressed it. A page that closes while + * sending leaves it empty or cut short; either way it is `''`, which nothing + * keeps. + */ +function readBody(req: Request): Promise { + return new Promise((resolve) => { + const chunks: Buffer[] = []; + let size = 0; + req.on('data', (chunk: Buffer) => { + size += chunk.length; + if (size <= MAX_BODY_BYTES) { + chunks.push(chunk); + } + }); + req.on('end', () => { + if (size > MAX_BODY_BYTES) { + resolve(''); + return; + } + const raw = Buffer.concat(chunks); + const gzip = + req.headers['content-encoding'] === 'gzip' || + (raw[0] === 0x1f && raw[1] === 0x8b); + // Thrown here, outside the promise, an error would stop the server. + try { + resolve( + (gzip + ? gunzipSync(raw, { maxOutputLength: MAX_BODY_BYTES }) + : raw + ).toString('utf8') + ); + } catch { + resolve(''); + } + }); + req.on('error', () => resolve('')); + }); +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function parseJson(text: string | undefined): any { + try { + return JSON.parse(text ?? ''); + } catch { + return undefined; + } +} + +/** + * What the broker answers, by mode and the page's call — its first, at + * startup, or a later one, a retry or refresh: `undefined` for an error, + * `enabled: false` to turn RUM off for the page. + * + * - `flaky`: the first two calls fail, as a broker starting up might + * - `invalid`: `pagesToExclude` as strings, which JSON makes of patterns + * - `null-on-refresh`, `no-credentials-on-refresh`: short-lived credentials, + * then a refresh answered with `null`, or without credentials + */ +function broker(req: Request) { + const mode = state.brokerMode; + state.brokerCalls++; + // The page counts its own calls: each load starts again at 1. + const call = Number(req.query.call) || 1; + if (mode === 'fail' || (mode === 'flaky' && call <= 2)) { + return undefined; + } + const refreshing = call > 1; + if (mode === 'null' || (mode === 'null-on-refresh' && refreshing)) { + return { enabled: false }; + } + const shortLived = [ + 'short-expiry', + 'null-on-refresh', + 'no-credentials-on-refresh' + ].includes(mode); + const origin = `${req.protocol}://${req.get('x-forwarded-host') ?? req.get('host')}`; + const minutes = shortLived ? 5 + 5 / 60 : 60; + return { + enabled: true, + config: { + applicationId: '00000000-0000-4000-8000-00000000a1ab', + applicationVersion: '1.0.0', + region: 'eu-west-1', + endpoint: origin, + signing: false, + compressionStrategy: { enabled: false }, + sessionSampleRate: 1, + // RUM stops recording a session after 200 events by default, and the + // session outlives reloads in a cookie: one burst from the Event + // Inspector page would silence the lab for half an hour. + sessionEventLimit: 0, + dispatchInterval: 2000, + allowCookies: true, + // The lab is plain http on localhost, where WebKit keeps none of the + // RUM client's cookies as it writes them by default, and with them go + // the session and its start: no Secure cookie over http, and on Linux + // no Domain=localhost, as it has no dot; an empty Domain is host-only. + // Lax, as WebKit on Linux may hide a Strict cookie from a page opened + // from about:blank, as Playwright opens them. + cookieAttributes: { secure: false, domain: '', sameSite: 'Lax' }, + // Names only: the lab's credentials provider adds their options. + ...(state.telemetries ? { telemetries: state.telemetries } : {}), + ...(mode === 'invalid' ? { pagesToExclude: ['^/private'] } : {}), + ...state.brokerConfig + }, + // Never used to sign — signing is off — but the sink schedules its + // refresh from their expiry, which the short-lived modes rely on. + ...(mode === 'no-credentials-on-refresh' && refreshing + ? {} + : { + credentials: { + accessKeyId: 'LAB-FAKE-KEY', + secretAccessKey: 'lab-fake-secret', + sessionToken: 'lab-fake-token', + expiration: new Date(Date.now() + minutes * 60_000).toISOString() + } + }) + }; +} + +function keepBatch(body: string): void { + const batch = parseJson(body); + if (!batch) { + return; + } + // Only RUM's shape: one stray event would break every page's reading. + const events = (Array.isArray(batch.RumEvents) ? batch.RumEvents : []).filter( + (event: unknown) => isRecord(event) && typeof event.type === 'string' + ); + if (events.length === 0) { + return; + } + state.batches++; + keep( + state.rum, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + events.map((event: any) => ({ + id: event.id, + receivedAt: new Date().toISOString(), + batch: state.batches, + user: batch.UserDetails ?? {}, + type: event.type, + metadata: parseJson(event.metadata) ?? {}, + details: parseJson(event.details) ?? {} + })) + ); +} + +function queryLogs(query: Request['query']): LogRecord[] { + const param = (name: string) => + typeof query[name] === 'string' ? query[name] : undefined; + const minLevel = param('minLevel'); + const limit = Number(param('limit')) || Infinity; + return state.logs + .filter( + (r) => + (!param('logger') || r.logger === param('logger')) && + (!param('correlationId') || + r.correlationId === param('correlationId')) && + (!minLevel || LEVELS[r.level] >= LEVELS[minLevel]) && + (!param('from') || (r.timestamp ?? '') >= param('from')!) && + (!param('to') || (r.timestamp ?? '') <= param('to')!) + ) + .slice(-limit); +} + +export const labBackend = Router(); + +// Only the lab's own pages, and the suite, which sends no Origin, change its +// state: another site's page could otherwise point RUM at its own endpoint. +labBackend.use('/__lab', (req, res, next) => { + const origin = req.get('origin'); + if ( + req.method === 'POST' && + origin && + new URL(origin).host !== req.get('host') + ) { + res.status(403).end(); + return; + } + next(); +}); + +labBackend.get('/rum/init', (req, res) => { + // Lab-only: what the broker was set to, for the header to show what this + // page load got. A real broker sends nothing like it. + const lab = { mode: state.brokerMode, telemetries: state.telemetries }; + const answer = broker(req); + if (answer) { + res.json({ ...answer, lab }); + } else { + res.status(500).json({ + error: `broker unavailable (lab mode: ${state.brokerMode})`, + lab + }); + } +}); + +labBackend.post( + '/appmonitors/:id', + promised(async (req, res) => { + keepBatch(await readBody(req)); + res.json({}); + }) +); + +labBackend + .route('/api/logs') + .post( + promised(async (req, res) => { + const body = parseJson(await readBody(req)); + keep( + state.logs, + (Array.isArray(body) ? body : [body]).filter( + (record): record is LogRecord => isRecord(record) + ) + ); + res.status(204).end(); + }) + ) + .get((req, res) => { + res.json(queryLogs(req.query)); + }); + +labBackend.get( + '/api/data', + promised(async (req, res) => { + const delay = Math.min(Number(req.query.delay) || 0, 30_000); + const status = Number(req.query.status) || 200; + await new Promise((resolve) => setTimeout(resolve, delay)); + res.status(status).json({ status, delay, at: new Date().toISOString() }); + }) +); + +labBackend.get('/__lab/rum', (_req, res) => { + res.json({ batches: state.batches, events: state.rum }); +}); + +labBackend.get('/__lab/logs', (_req, res) => { + res.json(state.logs); +}); + +labBackend.get('/__lab/state', (_req, res) => { + res.json({ + brokerMode: state.brokerMode, + brokerCalls: state.brokerCalls, + brokerConfig: state.brokerConfig, + modes: BROKER_MODES, + telemetries: state.telemetries, + // Asked of the lab's destinations while the server rendered: none. + serverSends: labServerSends() + }); +}); + +labBackend.post('/__lab/reset', (_req, res) => { + Object.assign(state, { batches: 0, rum: [], logs: [], custom: [] }); + labClearServerSends(); + res.status(204).end(); +}); + +labBackend.post( + '/__lab/broker-mode', + promised(async (req, res) => { + const mode = parseJson(await readBody(req))?.mode; + if (!BROKER_MODES.includes(mode)) { + res + .status(400) + .json({ error: `mode must be one of ${BROKER_MODES.join(', ')}` }); + return; + } + Object.assign(state, { brokerMode: mode, brokerCalls: 0 }); + res.json({ brokerMode: mode }); + }) +); + +labBackend.post( + '/__lab/telemetries', + promised(async (req, res) => { + const telemetries = parseJson(await readBody(req))?.telemetries; + const valid = + telemetries === null || + (Array.isArray(telemetries) && + telemetries.every((t) => TELEMETRIES.includes(t))); + if (!valid) { + res.status(400).json({ + error: `telemetries must be null or some of ${TELEMETRIES.join(', ')}` + }); + return; + } + state.telemetries = telemetries; + res.json({ telemetries }); + }) +); + +labBackend.post( + '/__lab/broker-config', + promised(async (req, res) => { + const config = parseJson(await readBody(req))?.config; + if (!config || typeof config !== 'object' || Array.isArray(config)) { + res.status(400).json({ error: 'config must be an object' }); + return; + } + state.brokerConfig = config; + res.json({ config }); + }) +); + +labBackend + .route('/__lab/custom') + .post( + promised(async (req, res) => { + const body = parseJson(await readBody(req)); + const receivedAt = new Date().toISOString(); + keep( + state.custom, + (Array.isArray(body) ? body : [body]) + .filter(isRecord) + .map((entry) => ({ ...entry, receivedAt })) + ); + res.status(204).end(); + }) + ) + .get((_req, res) => { + res.json(state.custom); + }); diff --git a/projects/watchtower-lab/server/lab-fragments.ts b/projects/watchtower-lab/server/lab-fragments.ts new file mode 100644 index 0000000..6326ddb --- /dev/null +++ b/projects/watchtower-lab/server/lab-fragments.ts @@ -0,0 +1,82 @@ +// The lab's fragment, served two ways from its one build +// (`ng build watchtower-lab-fragment`): +// +// /web-fragment, /_fragment/lab-fragment/… the Web Fragments gateway +// /fragment-frame/ its page, for a plain iframe +import { Router } from 'express'; +import { promised } from './promised'; +import { existsSync } from 'node:fs'; +import { readFile } from 'node:fs/promises'; +import { extname, join, normalize } from 'node:path'; +import { FragmentGateway } from 'web-fragments/gateway'; +import { getNodeMiddleware } from 'web-fragments/gateway/node'; + +// Relative to where the lab runs from — the repository's root — both under +// `ng serve` and as the built server. +const FRAGMENT_DIR = join( + process.cwd(), + 'dist/watchtower-lab-fragment/browser' +); +const FRAGMENT_INDEX = join(FRAGMENT_DIR, 'index.html'); + +const CONTENT_TYPES: Record = { + '.html': 'text/html', + '.js': 'text/javascript', + '.css': 'text/css', + '.svg': 'image/svg+xml', + '.woff2': 'font/woff2', + '.map': 'application/json' +}; + +/** + * The fragment's endpoint, in-process: a file of its build, or its + * index.html for any other path — the page the gateway hands the + * `` element. + */ +const fragmentEndpoint: typeof fetch = async (input) => { + const url = new URL(input instanceof Request ? input.url : String(input)); + const path = url.pathname.replace(/^\/_fragment\/lab-fragment\//, '/'); + const file = join(FRAGMENT_DIR, normalize(path)); + const found = + !!extname(file) && file.startsWith(FRAGMENT_DIR) && existsSync(file); + const served = found ? file : FRAGMENT_INDEX; + if (!existsSync(served)) { + return new Response('Build the fragment: npm run lab:build', { + status: 503 + }); + } + return new Response(await readFile(served), { + headers: { + 'content-type': + CONTENT_TYPES[extname(served)] ?? 'application/octet-stream' + } + }); +}; + +const gateway = new FragmentGateway(); +gateway.registerFragment({ + fragmentId: 'lab-fragment', + // Loaded by the element once the shell runs, rather than + // pierced into the shell's server-rendered page. + piercing: false, + routePatterns: ['/web-fragment/:_*', '/_fragment/lab-fragment/:_*'], + endpoint: fragmentEndpoint +}); + +export const labFragments = Router(); + +const gatewayMiddleware = getNodeMiddleware(gateway); +labFragments.use((req, res, next) => { + gatewayMiddleware(req, res, next).catch(next); +}); + +labFragments.get( + ['/fragment-frame', '/fragment-frame/'], + promised(async (_req, res) => { + if (!existsSync(FRAGMENT_INDEX)) { + res.status(503).send('Build the fragment: npm run lab:build'); + return; + } + res.type('html').send(await readFile(FRAGMENT_INDEX)); + }) +); diff --git a/projects/watchtower-lab/server/promised.ts b/projects/watchtower-lab/server/promised.ts new file mode 100644 index 0000000..148a042 --- /dev/null +++ b/projects/watchtower-lab/server/promised.ts @@ -0,0 +1,14 @@ +import { NextFunction, Request, RequestHandler, Response } from 'express'; + +/** + * An async route handler, as Express takes one. Built with zone.js, an async + * function returns zone's own promise, which Express warns about; this + * returns nothing and passes a failure to `next`. + */ +export function promised( + handler: (req: Request, res: Response) => Promise +): RequestHandler { + return (req: Request, res: Response, next: NextFunction) => { + handler(req, res).catch(next); + }; +} diff --git a/projects/watchtower-lab/server/server.ts b/projects/watchtower-lab/server/server.ts new file mode 100644 index 0000000..279e648 --- /dev/null +++ b/projects/watchtower-lab/server/server.ts @@ -0,0 +1,58 @@ +import { + AngularNodeAppEngine, + createNodeRequestHandler, + isMainModule, + writeResponseToNodeResponse +} from '@angular/ssr/node'; +import express from 'express'; +import { join } from 'node:path'; +import { labBackend } from './lab-backend'; +import { labFragments } from './lab-fragments'; + +/** + * The Watchtower Lab's server: the mock backend, the fragment and the + * server-rendered shell, all on one origin. `ng serve` runs it in + * development; the suite runs the built `server.mjs`. + */ +const app = express(); +const browserDistFolder = join(import.meta.dirname, '../browser'); +const angularApp = new AngularNodeAppEngine(); + +app.use(labBackend); +app.use(labFragments); +// The app's own catch-all redirect drops the query, and with it the startup +// options (`?destination=`, `?config=`, …): the start page keeps them. +app.get('/', (req, res) => { + const query = req.originalUrl.indexOf('?'); + res.redirect( + 302, + `/scenarios${query < 0 ? '' : req.originalUrl.slice(query)}` + ); +}); +app.use( + express.static(browserDistFolder, { + index: false, + redirect: false + }) +); +app.use((req, res, next) => { + angularApp + .handle(req) + .then((response) => + response ? writeResponseToNodeResponse(response, res) : next() + ) + .catch(next); +}); + +if (isMainModule(import.meta.url)) { + const port = process.env.PORT || 4280; + app.listen(port, (error) => { + if (error) { + throw error; + } + console.log(`[watchtower-lab] on http://localhost:${port}`); + }); +} + +/** Used by `ng serve` and the build. */ +export const reqHandler = createNodeRequestHandler(app); diff --git a/projects/watchtower-lab/src/_lab-base.scss b/projects/watchtower-lab/src/_lab-base.scss new file mode 100644 index 0000000..d2b3ace --- /dev/null +++ b/projects/watchtower-lab/src/_lab-base.scss @@ -0,0 +1,106 @@ +// What the lab's shell and fragment share. cps-ui-kit's global styles come +// without their primeicons import: the lab doesn't use PrimeIcons, and the +// package isn't installed here. +@use 'cps-ui-kit/styles/colors'; +@use 'cps-ui-kit/styles/fonts'; +@use 'cps-ui-kit/styles/cps-tooltip-style'; + +*, +*::before, +*::after { + box-sizing: border-box; +} + +.p-scrollbar-measure { + position: absolute; + top: -9999px; + width: 100px; + height: 100px; + overflow: scroll; +} + +.cps-sr-only { + position: absolute; + width: 0.0625rem; + height: 0.0625rem; + margin: -0.0625rem; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +body { + margin: 0; + background-color: #f4f6f9; + color: var(--cps-color-text-dark); + font-family: 'Source Sans Pro', sans-serif; +} + +.lab-page { + display: flex; + flex-direction: column; + gap: 1rem; + max-width: 64rem; + + h1 { + margin: 0; + font-size: 1.6rem; + } + + > p { + margin: 0; + color: var(--cps-text-secondary); + } +} + +.lab-card { + display: flex; + flex-direction: column; + gap: 0.75rem; + padding: 1rem 1.25rem; + border: 0.0625rem solid var(--cps-color-line-mid); + border-radius: 0.5rem; + background-color: #fff; + + h2 { + margin: 0; + font-size: 1.1rem; + } + + p { + margin: 0; + color: var(--cps-text-secondary); + font-size: 0.9rem; + } +} + +.lab-switches { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 0.5rem; +} + +.lab-actions { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.5rem; +} + +.lab-readout { + max-height: 20rem; + margin: 0; + padding: 0.75rem; + overflow: auto; + border-radius: 0.25rem; + background-color: #f8fafc; + font: + 0.8rem/1.5 ui-monospace, + Menlo, + Consolas, + monospace; + white-space: pre-wrap; +} diff --git a/projects/watchtower-lab/src/index.html b/projects/watchtower-lab/src/index.html new file mode 100644 index 0000000..6412b3f --- /dev/null +++ b/projects/watchtower-lab/src/index.html @@ -0,0 +1,13 @@ + + + + + Watchtower Lab + + + + + + + + diff --git a/projects/watchtower-lab/src/lab/lab-channel.ts b/projects/watchtower-lab/src/lab/lab-channel.ts new file mode 100644 index 0000000..5afb76f --- /dev/null +++ b/projects/watchtower-lab/src/lab/lab-channel.ts @@ -0,0 +1,28 @@ +import { UWT_DEFAULT_BROADCAST_CHANNEL } from '@absaoss-cps/ngx-ui-watchtower'; + +declare global { + interface Window { + __uwtTelemetryChannel?: string; + } +} + +/** + * This tab's own broadcast channel, as DESIGN.md §13 recommends for a + * composed page open in several tabs: one per page load, so each tab's + * fragments reach their own shell. + */ +const TAB_CHANNEL = `ngx-ui-watchtower-${crypto.randomUUID()}`; + +/** + * The shell's channel: this tab's own, or — `shared` — the default one every + * tab joins, where the tabs' hosts elect one leader. Published on the window + * for the fragments to read, before any of them starts; browser-only, so + * this is safe under SSR. + */ +export function labChannel(shared: boolean): string { + const channel = shared ? UWT_DEFAULT_BROADCAST_CHANNEL : TAB_CHANNEL; + if (typeof window !== 'undefined') { + window.__uwtTelemetryChannel = channel; + } + return channel; +} diff --git a/projects/watchtower-lab/src/lab/lab-config-check.ts b/projects/watchtower-lab/src/lab/lab-config-check.ts new file mode 100644 index 0000000..69b3ce9 --- /dev/null +++ b/projects/watchtower-lab/src/lab/lab-config-check.ts @@ -0,0 +1,43 @@ +/** A value's type as the check names it: `array`, `object`, `number`… */ +function typeOf(value: unknown): string { + return Array.isArray(value) + ? 'array' + : value === null + ? 'null' + : typeof value; +} + +/** + * What's wrong with a `?config=` overlay, or `''`: a section the lab doesn't + * take, a setting the section doesn't have, or a value of another type than + * the setting's. `known` holds each section's settings, with values of the + * types they take — the settings in effect will do. + */ +export function labCheckConfig( + overlay: Record, + known: Record> +): string { + for (const [section, settings] of Object.entries(overlay)) { + // Own properties only: `toString` or `__proto__` is no section. + const valid = Object.hasOwn(known, section) ? known[section] : undefined; + if (!valid) { + return `"${section}" isn't a section; use ${Object.keys(known).join(', ')}.`; + } + if (typeOf(settings) !== 'object') { + return `"${section}" should be an object.`; + } + for (const [key, value] of Object.entries( + settings as Record + )) { + if (!Object.hasOwn(valid, key)) { + return `"${section}.${key}" isn't a setting; ${section} has ${Object.keys(valid).join(', ')}.`; + } + // Patterns, written as text, sit in arrays: checked as arrays. + const expected = typeOf(valid[key]); + if (typeOf(value) !== expected) { + return `"${section}.${key}" should be ${expected === 'array' || expected === 'object' ? 'an' : 'a'} ${expected}.`; + } + } + } + return ''; +} diff --git a/projects/watchtower-lab/src/lab/lab-custom.sink.ts b/projects/watchtower-lab/src/lab/lab-custom.sink.ts new file mode 100644 index 0000000..1449cea --- /dev/null +++ b/projects/watchtower-lab/src/lab/lab-custom.sink.ts @@ -0,0 +1,84 @@ +import { DOCUMENT, inject, Injectable } from '@angular/core'; +import { + UwtTelemetryError, + UwtTelemetryMetadata, + UwtTelemetrySink, + uwtClassifyTelemetryEvent, + uwtMarkSessionStart, + uwtUuid +} from '@absaoss-cps/ngx-ui-watchtower'; +import { labSentOnServer } from './lab-server-sends'; + +/** + * A destination of the lab's own, as an app would write one for its own + * backend: each event, classified, posted to the mock backend's + * `/__lab/custom`. With `?destination=custom`. + * + * Events wait for the end of the task, then go together. As the page goes, + * the library records what is left and calls `flush(true)` straight after: + * those go by beacon, which outlives the page where a fetch is cancelled. + */ +@Injectable() +export class LabCustomSink extends UwtTelemetrySink { + private readonly document = inject(DOCUMENT); + /** One session per page load: it starts now, for `sessionElapsed`. */ + private readonly sessionId = uwtUuid(); + private userId?: string; + private queue: object[] = []; + + /** Run once at startup, by `provideUwtTelemetryDestination`'s `init`. */ + init(): void { + uwtMarkSessionStart(this.sessionId); + this.post({ kind: 'init' }); + } + + record( + eventType: string, + payload: object, + metadata?: UwtTelemetryMetadata + ): void { + const { kind } = uwtClassifyTelemetryEvent(eventType, payload); + this.post({ kind, eventType, payload, metadata }); + } + + recordError(error: UwtTelemetryError, metadata?: UwtTelemetryMetadata): void { + this.post({ kind: 'error', error, metadata }); + } + + getSessionId(): string { + return this.sessionId; + } + + setUserId(userId: string | undefined): void { + this.userId = userId || undefined; + } + + getUserId(): string | undefined { + return this.userId; + } + + flush(beacon = false): void { + if (this.queue.length === 0) { + return; + } + const json = JSON.stringify(this.queue); + this.queue = []; + if (labSentOnServer('POST /__lab/custom')) { + return; + } + const sent = + beacon && + this.document.defaultView?.navigator.sendBeacon('/__lab/custom', json); + if (!sent) { + fetch('/__lab/custom', { method: 'POST', body: json }).catch( + () => undefined + ); + } + } + + private post(event: object): void { + if (this.queue.push(event) === 1) { + queueMicrotask(() => this.flush()); + } + } +} diff --git a/projects/watchtower-lab/src/lab/lab-json.ts b/projects/watchtower-lab/src/lab/lab-json.ts new file mode 100644 index 0000000..e2567a9 --- /dev/null +++ b/projects/watchtower-lab/src/lab/lab-json.ts @@ -0,0 +1,16 @@ +/** + * `text` as a JSON object, or `undefined` for anything else: not JSON, an + * array, a number. The lab's JSON fields all take objects. + */ +export function parseJsonObject( + text: string +): Record | undefined { + try { + const value: unknown = JSON.parse(text); + return value && typeof value === 'object' && !Array.isArray(value) + ? (value as Record) + : undefined; + } catch { + return undefined; + } +} diff --git a/projects/watchtower-lab/src/lab/lab-layout.service.ts b/projects/watchtower-lab/src/lab/lab-layout.service.ts new file mode 100644 index 0000000..67ae0c3 --- /dev/null +++ b/projects/watchtower-lab/src/lab/lab-layout.service.ts @@ -0,0 +1,17 @@ +import { DOCUMENT } from '@angular/common'; +import { effect, inject, Injectable, signal } from '@angular/core'; + +/** + * Whether the lab's fixed header and status bar follow an Event Inspector + * pane, through the `--uwt-inspector-inset-*` variables it publishes. Off, + * they show what a pane covers in an app that hasn't opted in. + */ +@Injectable({ providedIn: 'root' }) +export class LabLayout { + readonly insets = signal(true); + + constructor() { + const root = inject(DOCUMENT).documentElement; + effect(() => root.classList.toggle('lab-insets', this.insets())); + } +} diff --git a/projects/watchtower-lab/src/lab/lab-log-api.provider.ts b/projects/watchtower-lab/src/lab/lab-log-api.provider.ts new file mode 100644 index 0000000..eeb18b2 --- /dev/null +++ b/projects/watchtower-lab/src/lab/lab-log-api.provider.ts @@ -0,0 +1,69 @@ +import { DOCUMENT, inject, Injectable } from '@angular/core'; +import { + UwtLogApiProvider, + UwtLogQuery, + UwtLogRecord +} from '@absaoss-cps/ngx-ui-watchtower'; +import { LAB_OPTIONS } from './lab-options'; +import { labSentOnServer } from './lab-server-sends'; + +/** + * Sends log records to the lab's mock backend, as the README's example does: + * each at once, or — with the `lab.logBatching` setting — in batches, sent + * every 10 seconds and whenever the library calls `flush()`, as the page is + * hidden or closes. + */ +@Injectable({ providedIn: 'root' }) +export class LabLogApiProvider implements UwtLogApiProvider { + private readonly document = inject(DOCUMENT); + private readonly batching = + inject(LAB_OPTIONS, { optional: true })?.config.lab?.logBatching ?? false; + + private pending: UwtLogRecord[] = []; + private timer?: ReturnType; + + send(record: UwtLogRecord): void { + if (!this.batching) { + this.post([record]); + return; + } + this.pending.push(record); + this.timer ??= setTimeout(() => this.flush(), 10_000); + } + + /** + * Called by the library as the page is hidden or goes away. Kept alive + * either way: WebKit still says `visible` while a reloading page hides, and + * cancels a plain request then. + */ + flush(): void { + clearTimeout(this.timer); + this.timer = undefined; + if (this.pending.length > 0) { + this.post(this.pending, true); + this.pending = []; + } + } + + query(filter: UwtLogQuery): Promise { + const params = new URLSearchParams( + Object.entries(filter) + .filter(([, value]) => value !== undefined) + .map(([key, value]) => [key, String(value)]) + ); + return fetch(`/api/logs?${params}`).then((r) => r.json()); + } + + private post(records: UwtLogRecord[], closing = false): void { + if (labSentOnServer('POST /api/logs')) { + return; + } + fetch('/api/logs', { + method: 'POST', + body: JSON.stringify(records), + // Only while the page may be closing: browsers reject keepalive + // requests once all in flight exceed 64 KiB. + keepalive: closing || this.document.visibilityState === 'hidden' + }).catch(() => undefined); + } +} diff --git a/projects/watchtower-lab/src/lab/lab-logger-levels.component.ts b/projects/watchtower-lab/src/lab/lab-logger-levels.component.ts new file mode 100644 index 0000000..fc9a08d --- /dev/null +++ b/projects/watchtower-lab/src/lab/lab-logger-levels.component.ts @@ -0,0 +1,104 @@ +import { + ChangeDetectionStrategy, + Component, + computed, + input, + output, + signal +} from '@angular/core'; +import { + CpsButtonComponent, + CpsInputComponent, + CpsSelectComponent +} from 'cps-ui-kit'; +import { LAB_LOG_LEVELS } from './lab-settings-fields'; + +/** + * The level each named logger logs at, as `logging.levels` has it: the ones + * set, each with its level and a way to remove it, and a way to add one. + */ +@Component({ + selector: 'lab-logger-levels', + imports: [CpsButtonComponent, CpsInputComponent, CpsSelectComponent], + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` +
+ @for (entry of entries(); track entry.name) { +
+ {{ entry.name }} + + +
+ } @empty { +

No logger has a level of its own.

+ } +
+ + + +
+
+ ` +}) +export class LabLoggerLevelsComponent { + /** The levels as they are, by logger name. */ + readonly levels = input.required>(); + readonly disabled = input(false); + /** The levels as the reader has changed them. */ + readonly changed = output>(); + + protected readonly options = LAB_LOG_LEVELS; + protected readonly name = signal(''); + protected readonly level = signal('warn'); + protected readonly entries = computed(() => + Object.entries(this.levels()).map(([name, level]) => ({ name, level })) + ); + + protected setLevel(name: string, level: string): void { + this.changed.emit({ ...this.levels(), [name]: level }); + } + + protected remove(name: string): void { + const { [name]: _removed, ...rest } = this.levels(); + this.changed.emit(rest); + } + + protected add(): void { + const name = this.name().trim(); + if (name) { + this.changed.emit({ ...this.levels(), [name]: this.level() }); + this.name.set(''); + } + } +} diff --git a/projects/watchtower-lab/src/lab/lab-options.ts b/projects/watchtower-lab/src/lab/lab-options.ts new file mode 100644 index 0000000..f02c759 --- /dev/null +++ b/projects/watchtower-lab/src/lab/lab-options.ts @@ -0,0 +1,198 @@ +import { InjectionToken } from '@angular/core'; +import { + UwtBITelemetryConfig, + UwtLogConfig, + UwtRedactConfig, + UwtScenarioTelemetryConfig, + UwtTelemetryIdentity +} from '@absaoss-cps/ngx-ui-watchtower'; +import { UwtEventInspectorConfig } from '@absaoss-cps/ngx-ui-watchtower/event-inspector'; +import { parseJsonObject } from './lab-json'; + +/** Value transforms a {@link LabConfig} can name. */ +export const LAB_VALUE_TRANSFORMS = { + /** The lab's order ids, `A-1001`, as `A-****`. */ + 'mask-order-ids': (value: string) => value.replace(/\bA-\d{4}\b/g, 'A-****') +}; + +/** + * Library settings for one run, as JSON can carry them, laid over the lab's + * own. A pattern is a `RegExp` source, a value transform one of + * {@link LAB_VALUE_TRANSFORMS}. + */ +export interface LabConfig { + identity?: Partial; + scenarios?: Partial; + logging?: Partial; + bi?: Partial; + redaction?: Partial< + Omit< + UwtRedactConfig, + 'extraKeyPatterns' | 'extraValuePatterns' | 'extraValueTransforms' + > + > & { + extraKeyPatterns?: string[]; + extraValuePatterns?: string[]; + extraValueTransforms?: (keyof typeof LAB_VALUE_TRANSFORMS)[]; + }; + inspector?: Pick< + Partial, + | 'maxEventsPerSection' + | 'maxPayloadCharsInView' + | 'shortcuts' + | 'captureBeforeOpen' + >; + /** The lab's own: its log provider batches, and sends on `flush()`. */ + lab?: { logBatching?: boolean }; +} + +/** + * The lab's own settings, laid over the library's defaults: a {@link LabConfig} + * is laid over these. Settings shows them as the lab's defaults. + */ +export const LAB_DEFAULTS = { + identity: { + application: 'watchtower-lab', + environment: 'lab', + version: '1.0.0' + }, + logging: { + minLevel: 'log', + levels: { noisy: 'warn' }, + mirrorErrorsToRum: true + }, + scenarios: { defaultTimeoutMs: 30_000 }, + redaction: { + scanValuePatterns: ['email', 'creditCard', 'ssn', 'ipv4', 'phone'] + } +} satisfies { + identity: UwtTelemetryIdentity; + logging: Partial; + scenarios: Partial; + redaction: Partial; +}; + +/** + * How the lab was started, read once from the query string — the way an app + * picks its configuration at startup: + * + * - `?destination=rum` (default), `noop`, or `custom`: the lab's own sink + * - `?telemetry=off` turns telemetry off with `withTelemetryEnabled` + * - `?channel=shared`: the shell joins the default broadcast channel, shared + * with every tab, rather than one of its own + * - `?inspector=off` disables the Event Inspector; `noshortcut` drops its + * keyboard shortcut + * - `?config=` lays a {@link LabConfig} over the lab's settings + */ +export interface LabOptions { + destination: 'rum' | 'noop' | 'custom'; + telemetry: boolean; + channel: 'tab' | 'shared'; + inspector: 'on' | 'off' | 'noshortcut'; + config: LabConfig; +} + +export const LAB_OPTIONS = new InjectionToken('LAB_OPTIONS'); + +export function labOptions(search: string): LabOptions { + const params = new URLSearchParams(search); + const inspector = params.get('inspector'); + const destination = params.get('destination'); + return { + destination: + destination === 'noop' || destination === 'custom' ? destination : 'rum', + telemetry: params.get('telemetry') !== 'off', + channel: params.get('channel') === 'shared' ? 'shared' : 'tab', + inspector: + inspector === 'off' || inspector === 'noshortcut' ? inspector : 'on', + config: parseConfig(params.get('config')) + }; +} + +/** The query string that starts the lab with `options`. */ +export function labSearch(options: LabOptions): string { + const params = new URLSearchParams(); + if (options.destination !== 'rum') { + params.set('destination', options.destination); + } + if (!options.telemetry) { + params.set('telemetry', 'off'); + } + if (options.channel === 'shared') { + params.set('channel', 'shared'); + } + if (options.inspector !== 'on') { + params.set('inspector', options.inspector); + } + if (Object.keys(options.config).length > 0) { + params.set('config', JSON.stringify(options.config)); + } + const search = params.toString(); + return search ? `?${search}` : ''; +} + +/** + * The overlay's redaction settings as the library takes them. A pattern + * that isn't a `RegExp` source, or a transform the lab doesn't know, is left + * out with a warning, rather than stop the lab from starting. + */ +export function labRedaction( + redaction: LabConfig['redaction'] = {} +): Partial { + const { + extraKeyPatterns, + extraValuePatterns, + extraValueTransforms, + ...rest + } = redaction; + const transforms = list(extraValueTransforms, 'extraValueTransforms'); + return { + ...rest, + ...(extraKeyPatterns && { + extraKeyPatterns: patterns(extraKeyPatterns, 'extraKeyPatterns', 'i') + }), + ...(extraValuePatterns && { + extraValuePatterns: patterns(extraValuePatterns, 'extraValuePatterns') + }), + ...(transforms && { + extraValueTransforms: transforms.flatMap((name) => { + const transform = + LAB_VALUE_TRANSFORMS[name as keyof typeof LAB_VALUE_TRANSFORMS]; + if (!transform) { + console.warn(`[lab] ?config= has no value transform "${name}".`); + } + return transform ? [transform] : []; + }) + }) + }; +} + +function patterns(sources: unknown, field: string, flags?: string): RegExp[] { + return (list(sources, field) ?? []).flatMap((source) => { + try { + return [new RegExp(String(source), flags)]; + } catch { + console.warn(`[lab] ?config= ${field}: "${source}" isn't a RegExp.`); + return []; + } + }); +} + +function list(value: unknown, field: string): unknown[] | undefined { + if (value === undefined || Array.isArray(value)) { + return value; + } + console.warn(`[lab] ?config= ${field} isn't a list; left out.`); + return undefined; +} + +function parseConfig(json: string | null): LabConfig { + if (!json) { + return {}; + } + const config = parseJsonObject(json); + if (!config) { + console.warn('[lab] ?config= is not a JSON object; ignored.'); + } + return config ?? {}; +} diff --git a/projects/watchtower-lab/src/lab/lab-results.component.ts b/projects/watchtower-lab/src/lab/lab-results.component.ts new file mode 100644 index 0000000..4c050f9 --- /dev/null +++ b/projects/watchtower-lab/src/lab/lab-results.component.ts @@ -0,0 +1,154 @@ +import { + ChangeDetectionStrategy, + Component, + computed, + inject, + signal +} from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { + UwtTelemetryMonitor, + UwtTelemetryObservedEvent +} from '@absaoss-cps/ngx-ui-watchtower'; +import { LabWire } from './lab-wire.service'; + +/** How many of the page's latest events are listed. */ +const LISTED = 8; + +const KIND: Record = { + bi: 'BI', + scenario: 'Scenario', + 'scenario-step': 'Step', + log: 'Log', + error: 'Error', + unknown: 'Event' +}; + +function field(payload: unknown, key: string): string { + const value = (payload as Record | null)?.[key]; + return value === undefined || value === null ? '' : String(value); +} + +/** What an event is called, as the list shows it. */ +function nameOf(event: UwtTelemetryObservedEvent): string { + const p = event.payload; + switch (event.kind) { + case 'bi': + return field(p, 'eventName'); + case 'scenario': + return `${field(p, 'scenarioName')} · ${field(p, 'status')}`; + case 'scenario-step': + return `${field(p, 'scenarioName')} · ${field(p, 'name')}`; + case 'log': + return `${field(p, 'level')} · ${field(p, 'message')}`; + case 'error': + return `${field(p, 'name')}: ${field(p, 'message')}`; + default: + return 'eventType' in event ? event.eventType : ''; + } +} + +/** + * A page's results, beside its actions: the page's own readouts, projected, + * then what the page has sent since it opened, each ticked once the lab's + * mock backend has it. The latest change is said in a polite live region. + * + * Arrival is matched by content: a BI or scenario event's payload is RUM's + * details, a log record arrives as it was handed over, and an error, which + * RUM records its own way, by its message. + */ +@Component({ + selector: 'lab-results', + changeDetection: ChangeDetectionStrategy.OnPush, + host: { class: 'lab-results' }, + template: ` +

Results

+ +

Sent from this page

+ @if (items().length === 0) { +

Nothing yet.

+ } @else { +
    + @for (item of items(); track item.sequence) { +
  1. + {{ item.kind }} + {{ + item.name + }} + {{ item.received ? '✓ received' : 'sent' }} +
  2. + } +
+ } +

{{ said() }}

+ ` +}) +export class LabResultsComponent { + private readonly wire = inject(LabWire); + private readonly sent = signal([]); + + /** Whether the mock backend has an event. */ + private readonly arrived = computed(() => { + const rum = this.wire.rum(); + const custom = this.wire.custom(); + // A payload as RUM's details, or as the lab's own sink was given it. + const details = new Set([ + ...rum.map((e) => JSON.stringify(e.details)), + ...custom.map((e) => JSON.stringify(e.payload)) + ]); + const errors = [ + ...rum + .filter((e) => e.type.endsWith('js_error_event')) + .map((e) => ({ + message: field(e.details, 'message'), + at: e.receivedAt + })), + ...custom + .filter((e) => e.kind === 'error') + .map((e) => ({ message: field(e.error, 'message'), at: e.receivedAt })) + ]; + const logs = new Set(this.wire.logs().map((r) => JSON.stringify(r))); + return (event: UwtTelemetryObservedEvent): boolean => + event.destination === 'log-provider' + ? logs.has(JSON.stringify(event.payload)) + : event.kind === 'error' + ? // Only its message is known, and an earlier error can share it: + // one that arrived after this one was sent. + errors.some( + (e) => + e.message === field(event.payload, 'message') && + Date.parse(e.at) >= Date.parse(event.capturedAt) + ) + : details.has(JSON.stringify(event.payload)); + }); + + protected readonly items = computed(() => { + const arrived = this.arrived(); + return this.sent().map((event) => ({ + sequence: event.sequence, + kind: KIND[event.kind], + name: nameOf(event), + received: arrived(event) + })); + }); + + /** The latest event and where it is, for screen readers. */ + protected readonly said = computed(() => { + const [latest] = this.items(); + return latest + ? `${latest.kind} ${latest.name}: ${latest.received ? 'received' : 'sent'}.` + : ''; + }); + + constructor() { + inject(UwtTelemetryMonitor) + .events$.pipe(takeUntilDestroyed()) + .subscribe((event) => + this.sent.update((sent) => [event, ...sent].slice(0, LISTED)) + ); + } +} diff --git a/projects/watchtower-lab/src/lab/lab-rum-credentials.provider.ts b/projects/watchtower-lab/src/lab/lab-rum-credentials.provider.ts new file mode 100644 index 0000000..162c360 --- /dev/null +++ b/projects/watchtower-lab/src/lab/lab-rum-credentials.provider.ts @@ -0,0 +1,80 @@ +import { Injectable, signal } from '@angular/core'; +import { + UwtRumBootstrap, + UwtRumCredentialsProvider +} from '@absaoss-cps/ngx-ui-watchtower/rum'; +import { labSentOnServer } from './lab-server-sends'; + +/** + * Options for the RUM telemetries the broker names, which its JSON can't + * carry: + * + * - `http` as in the library's default. + * - `interaction` recording every button click — it records nothing without + * a list of elements. + * - `replay` leaving out the status bar and "On the wire": they show what + * reached the dataplane, so each replay batch would change them, and be + * recorded, forever. + */ +const telemetryOptions = (origin: string): Record => ({ + http: { + addXRayTraceIdHeader: [ + new RegExp('^' + origin.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '/') + ] + }, + interaction: { + enableMutationObserver: true, + events: [{ event: 'click', cssLocator: 'button' }] + }, + replay: { recordOptions: { blockSelector: '.lab-status, lab-wire-page' } } +}); + +/** How the mock broker was set when this page load started RUM. */ +export interface LabBrokerStartup { + mode: string; + /** `null` for the library's default. */ + telemetries: readonly string[] | null; +} + +/** + * Asks the lab's mock broker for the app monitor config and credentials, as + * the library's own example does: a broker that says RUM is off returns + * `null`, and a failing broker throws — two different paths in the sink. + */ +@Injectable({ providedIn: 'root' }) +export class LabRumCredentialsProvider implements UwtRumCredentialsProvider { + /** This page's calls, so the broker can tell startup from a refresh. */ + private calls = 0; + + /** What this page load got, from the broker's lab-only echo. */ + readonly startup = signal(undefined); + + async load(): Promise { + const call = ++this.calls; + if (labSentOnServer('GET /rum/init')) { + return null; + } + const response = await fetch(`/rum/init?call=${call}`); + const body = await response.json().catch(() => ({})); + if (call === 1 && body.lab) { + this.startup.set(body.lab); + } + if (!response.ok) { + throw new Error(`RUM bootstrap failed: ${response.status}`); + } + // Only these: the lab's own echo goes no further. + const { enabled, config, credentials } = body; + if (!enabled) { + return null; + } + // Left out by the broker, the library's default applies. + const options = telemetryOptions(location.origin); + const telemetries = (config.telemetries as string[] | undefined)?.map( + (name) => (options[name] ? [name, options[name]] : name) + ); + return { + config: telemetries ? { ...config, telemetries } : config, + credentials + }; + } +} diff --git a/projects/watchtower-lab/src/lab/lab-server-sends.ts b/projects/watchtower-lab/src/lab/lab-server-sends.ts new file mode 100644 index 0000000..3fd41a4 --- /dev/null +++ b/projects/watchtower-lab/src/lab/lab-server-sends.ts @@ -0,0 +1,33 @@ +/** + * What the lab's destinations were asked to send while the server rendered: + * nothing, unless the library's browser guards slip. On the server a send + * can't reach anything — its URL is relative — so it would fail unseen; + * here it is kept instead, on `globalThis`, where the mock backend, in the + * same process, reads it for `/__lab/state`. + */ +const KEY = '__labServerSends'; + +type Kept = { [KEY]?: string[] }; + +/** + * Keeps `what` — say `POST /api/logs` — when on the server, where it is + * then not sent. + * + * @returns whether it was kept, so the caller doesn't send it + */ +export function labSentOnServer(what: string): boolean { + if (typeof window !== 'undefined') { + return false; + } + ((globalThis as Kept)[KEY] ??= []).push(what); + return true; +} + +/** What was kept since the last {@link labClearServerSends}. */ +export function labServerSends(): readonly string[] { + return (globalThis as Kept)[KEY] ?? []; +} + +export function labClearServerSends(): void { + (globalThis as Kept)[KEY] = []; +} diff --git a/projects/watchtower-lab/src/lab/lab-setting-state.component.ts b/projects/watchtower-lab/src/lab/lab-setting-state.component.ts new file mode 100644 index 0000000..caff2c5 --- /dev/null +++ b/projects/watchtower-lab/src/lab/lab-setting-state.component.ts @@ -0,0 +1,45 @@ +import { ChangeDetectionStrategy, Component, input } from '@angular/core'; +import { CpsButtonComponent } from 'cps-ui-kit'; +import { LabSettingsEntry } from './lab-settings-draft.service'; + +/** + * Under a setting's control, where it stands: changed and not applied yet, + * with Undo; or otherwise than the lab's default, with the default and a + * Reset. Nothing while it is as applied, and as by default. + */ +@Component({ + selector: 'lab-setting-state', + imports: [CpsButtonComponent], + changeDetection: ChangeDetectionStrategy.OnPush, + host: { class: 'lab-setting-state' }, + template: ` + @if (change(); as change) { + Not applied + + } @else if (modified(); as modified) { + Modified + Lab default: {{ modified.from }} + + } + ` +}) +export class LabSettingStateComponent { + /** The setting's change not applied yet, if it has one. */ + readonly change = input(); + /** How it differs from the lab's default, if it does. */ + readonly modified = input(); +} diff --git a/projects/watchtower-lab/src/lab/lab-settings-draft.service.ts b/projects/watchtower-lab/src/lab/lab-settings-draft.service.ts new file mode 100644 index 0000000..c49079f --- /dev/null +++ b/projects/watchtower-lab/src/lab/lab-settings-draft.service.ts @@ -0,0 +1,913 @@ +import { + computed, + DOCUMENT, + inject, + Injectable, + linkedSignal, + signal +} from '@angular/core'; +import { + UWT_BI_TELEMETRY_CONFIG, + UWT_DEFAULT_TELEMETRY_CONFIG, + UWT_LOG_CONFIG, + UWT_REDACT_CONFIG, + UWT_SCENARIO_TELEMETRY_CONFIG, + UWT_TELEMETRY_IDENTITY, + uwtIsDebugEnabled +} from '@absaoss-cps/ngx-ui-watchtower'; +import { + UWT_DEFAULT_INSPECTOR_CONFIG, + UWT_DEFAULT_INSPECTOR_SHORTCUTS, + UWT_EVENT_INSPECTOR_CONFIG +} from '@absaoss-cps/ngx-ui-watchtower/event-inspector'; +import { labCheckConfig } from './lab-config-check'; +import { parseJsonObject } from './lab-json'; +import { + LAB_DEFAULTS, + LAB_OPTIONS, + labOptions, + labSearch, + LabOptions +} from './lab-options'; +import { + LAB_DEBUG_FLAGS, + LAB_SETTINGS_SECTIONS, + LabDebugFlag, + LabField, + labSettingsSection, + LabSettingsSection +} from './lab-settings-fields'; +import { labDescribeShortcut, LabShortcut } from './lab-shortcuts.component'; +import { + LabBrokerMode, + labTelemetriesLabel, + LabTelemetry, + LabWire +} from './lab-wire.service'; + +/** How the lab starts: its options, but the overlay. */ +type LabStart = Omit; + +/** + * A setting in one of Settings' lists: changed and not applied yet, or one + * this load runs with otherwise than the lab's default. + */ +export interface LabSettingsEntry { + /** Where it is kept: {@link labSettingId} for a field's. */ + id: string; + /** The section it is in. */ + section: LabSettingsSection; + /** The setting, as people read it. */ + label: string; + /** + * Not applied: as applied, and as it will be. Modified: the lab's default, + * and as it is. Both empty when the JSON isn't an object. + */ + from: string; + to: string; + /** + * Takes it back: a change to what is applied; a modified setting to the + * lab's default, which then waits to be applied — a debug flag at once. + */ + revert: () => void; +} + +/** Why Apply didn't go ahead, and the section to fix it in. */ +export interface LabSettingsProblem { + section: LabSettingsSection; + message: string; +} + +/** Where a field is kept: `logging.minLevel`, `start.destination`, `broker.allowCookies`. */ +export function labSettingId(field: LabField): string { + return `${field.section}.${field.key}`; +} + +/** The library's default telemetries, where an edited list starts. */ +const DEFAULT_TELEMETRIES: LabTelemetry[] = ['errors', 'performance', 'http']; + +const JSON_SECTION = labSettingsSection('json'); +const BROKER_SECTION = labSettingsSection('rum-broker'); +const MONITOR_SECTION = labSettingsSection('app-monitor'); +const DEBUG_SECTION = labSettingsSection('debug-flags'); + +/** Each field, by where it is kept, with the section it is in. */ +const FIELDS = new Map( + LAB_SETTINGS_SECTIONS.flatMap((section) => + section.fields.map( + (field) => [labSettingId(field), { field, section }] as const + ) + ) +); + +/** How the lab starts with no options in the address. */ +const START_DEFAULTS = startOf(labOptions('')); + +/** Each overlay section's settings with no `?config=`: the lab's own, over the library's defaults. */ +const OVERLAY_DEFAULTS: Record> = { + identity: { + eventNamespace: UWT_DEFAULT_TELEMETRY_CONFIG.eventNamespace, + ...LAB_DEFAULTS.identity + }, + scenarios: { + ...UWT_DEFAULT_TELEMETRY_CONFIG.scenario, + ...LAB_DEFAULTS.scenarios + }, + logging: { ...UWT_DEFAULT_TELEMETRY_CONFIG.logs, ...LAB_DEFAULTS.logging }, + bi: { ...UWT_DEFAULT_TELEMETRY_CONFIG.bi }, + redaction: { + ...UWT_DEFAULT_TELEMETRY_CONFIG.redact, + ...LAB_DEFAULTS.redaction + }, + inspector: { + maxEventsPerSection: UWT_DEFAULT_INSPECTOR_CONFIG.maxEventsPerSection, + maxPayloadCharsInView: UWT_DEFAULT_INSPECTOR_CONFIG.maxPayloadCharsInView, + captureBeforeOpen: UWT_DEFAULT_INSPECTOR_CONFIG.captureBeforeOpen, + shortcuts: UWT_DEFAULT_INSPECTOR_SHORTCUTS + }, + lab: { logBatching: false } +}; + +/** + * What Settings will apply, edited in any of its sections: how the lab + * starts, the library's settings as a `?config=` overlay, and the mock + * broker's answer. They apply together, with a reload. Provided by the + * Settings area, so it lasts while the sections change, and goes with it. + */ +@Injectable() +export class LabSettingsDraft { + private readonly window = inject(DOCUMENT).defaultView!; + private readonly options = inject(LAB_OPTIONS); + private readonly wire = inject(LabWire); + /** The overlay this load was started with. */ + private readonly applied = this.options.config as Record; + + /** How the lab starts, to be: the load's own to begin with. */ + readonly start = signal(startOf(this.options)); + + /** The settings in effect, which the controls show until changed. */ + private readonly inEffect: Record> = (() => { + const inspector = inject(UWT_EVENT_INSPECTOR_CONFIG); + return { + identity: { + ...inject(UWT_TELEMETRY_IDENTITY), + eventNamespace: inject(UWT_TELEMETRY_IDENTITY).eventNamespace ?? '' + }, + scenarios: { ...inject(UWT_SCENARIO_TELEMETRY_CONFIG) }, + logging: { levels: {}, ...inject(UWT_LOG_CONFIG) }, + bi: { ...inject(UWT_BI_TELEMETRY_CONFIG) }, + redaction: { ...inject(UWT_REDACT_CONFIG) }, + inspector: { + maxEventsPerSection: inspector.maxEventsPerSection, + maxPayloadCharsInView: inspector.maxPayloadCharsInView, + shortcuts: inspector.shortcuts, + captureBeforeOpen: inspector.captureBeforeOpen + }, + lab: { logBatching: false } + }; + })(); + + /** + * What each section takes, of the types they take: the library's defaults + * over what is in effect, which an overlay in the address may have given + * a value of the wrong type. + */ + private readonly known: Record> = (() => { + const library = UWT_DEFAULT_TELEMETRY_CONFIG; + const inspector = UWT_DEFAULT_INSPECTOR_CONFIG; + const defaults: Record = { + identity: { eventNamespace: library.eventNamespace }, + scenarios: library.scenario, + logging: library.logs, + bi: library.bi, + redaction: library.redact, + inspector: { + maxEventsPerSection: inspector.maxEventsPerSection, + maxPayloadCharsInView: inspector.maxPayloadCharsInView, + captureBeforeOpen: inspector.captureBeforeOpen + } + }; + return Object.fromEntries( + Object.entries(this.inEffect).map(([section, values]) => [ + section, + { ...values, ...defaults[section] } + ]) + ); + })(); + + /** The overlay being edited: the one in effect, to start with. */ + readonly overlayText = signal(this.appliedText()); + readonly overlayError = signal(''); + /** The overlay as an object, or `undefined` while it isn't one. */ + readonly overlay = computed(() => + parseJsonObject(this.overlayText().trim() || '{}') + ); + + /** The broker's answer to be, starting from the one it gives now. */ + readonly mode = linkedSignal(() => this.wire.brokerMode()); + readonly telemetries = linkedSignal(() => this.wire.telemetries()); + readonly brokerText = linkedSignal(() => + JSON.stringify(this.wire.brokerConfig()) + ); + + readonly brokerError = signal(''); + /** The broker's config as an object, or `undefined` while it isn't one. */ + readonly brokerObject = computed(() => + parseJsonObject(this.brokerText().trim() || '{}') + ); + + /** What the last Apply found wrong, until the JSON at fault is edited. */ + readonly problems = computed(() => [ + ...(this.overlayError() + ? [{ section: JSON_SECTION, message: this.overlayError() }] + : []), + ...(this.brokerError() + ? [{ section: MONITOR_SECTION, message: this.brokerError() }] + : []) + ]); + + readonly debug = signal(this.readDebug()); + + /** What applying would change, in the order of the sections. */ + readonly changes = computed(() => + bySection([ + ...this.startChanges(), + ...this.overlayChanges(), + ...this.brokerChanges() + ]) + ); + + /** What this load runs with otherwise than the lab's defaults. */ + readonly modified = computed(() => + bySection([ + ...this.startModified(), + ...this.overlayModified(), + ...this.brokerModified(), + ...this.debugModified() + ]) + ); + + /** The changes not applied, by their setting's id. */ + readonly changeById = computed( + () => new Map(this.changes().map((entry) => [entry.id, entry])) + ); + + /** The modified settings, by their id. */ + readonly modifiedById = computed( + () => new Map(this.modified().map((entry) => [entry.id, entry])) + ); + + /** What the library was given, read back from its tokens. */ + readonly effective = JSON.stringify( + { + identity: inject(UWT_TELEMETRY_IDENTITY), + scenarios: inject(UWT_SCENARIO_TELEMETRY_CONFIG), + logging: inject(UWT_LOG_CONFIG), + bi: inject(UWT_BI_TELEMETRY_CONFIG), + redaction: inject(UWT_REDACT_CONFIG), + inspector: inject(UWT_EVENT_INSPECTOR_CONFIG) + }, + readable, + 2 + ); + + /** + * A field's value as the next load will have it: as the draft has it, as + * it is in effect where neither sets it, or the lab's default where the + * draft takes it out. + */ + value(field: LabField): unknown { + switch (field.section) { + case 'start': + return this.start()[field.key as keyof LabStart]; + case 'rum': + return field.key === 'mode' ? this.mode() : this.telemetries(); + case 'broker': + return this.brokerObject()?.[field.key] ?? field.default; + case 'debug': + return this.debug()[field.key as LabDebugFlag]; + default: { + const { section, key } = field; + const drafted = settingIn(this.overlay(), section, key); + if (drafted !== undefined) { + return drafted; + } + return settingIn(this.applied, section, key) === undefined + ? this.inEffect[section][key] + : own(OVERLAY_DEFAULTS, section)?.[key]; + } + } + } + + /** Whether a field's control is off: while the JSON it writes into isn't an object. */ + disabled(field: LabField): boolean { + switch (field.section) { + case 'broker': + return !this.brokerObject(); + case 'start': + case 'rum': + case 'debug': + return false; + default: + return !this.overlay(); + } + } + + /** + * Writes a control's value into the draft, which shows it — but a debug + * flag's, which applies at once. + */ + set(field: LabField, value: unknown): void { + switch (field.section) { + case 'start': + this.setStart(field.key as keyof LabStart, value); + return; + case 'rum': + if (field.key === 'mode') { + this.mode.set(value as LabBrokerMode); + } else { + this.telemetries.set(value as LabTelemetry[] | null); + } + return; + case 'broker': + this.putBroker(field.key, value); + return; + case 'debug': + this.setDebug(field.key as LabDebugFlag, !!value); + return; + default: + this.putOverlay(field.section, field.key, value); + } + } + + setNumber(field: LabField, value: unknown): void { + const number = Number(value); + if (value !== '' && Number.isFinite(number)) { + this.set(field, number); + } + } + + /** A text of lines into the list it stands for: blank lines are none. */ + setLines(field: LabField, text: string): void { + this.set( + field, + text + .split('\n') + .map((line) => line.trim()) + .filter(Boolean) + ); + } + + editOverlay(text: string): void { + this.overlayText.set(text); + this.overlayError.set(''); + } + + editBroker(text: string): void { + this.brokerText.set(text); + this.brokerError.set(''); + } + + /** Back to the library's default, or a list to edit that starts there. */ + useDefaultTelemetries(useDefault: boolean): void { + this.telemetries.set(useDefault ? null : DEFAULT_TELEMETRIES); + } + + /** + * Sets the broker, then reloads with the overlay and how the lab is to + * start. Nothing changes while either JSON is wrong. + */ + async apply(): Promise { + const overlay = this.overlay(); + const brokerConfig = this.brokerObject(); + this.overlayError.set( + overlay ? labCheckConfig(overlay, this.known) : 'Not a JSON object.' + ); + this.brokerError.set(brokerConfig ? '' : 'Not a JSON object.'); + if (this.overlayError() || !overlay || !brokerConfig) { + return; + } + await this.wire.setBrokerMode(this.mode()); + await this.wire.setTelemetries(this.telemetries()); + await this.wire.setBrokerConfig(brokerConfig); + this.reload(overlay, this.start()); + } + + /** Every change dropped: back to what is applied. */ + discard(): void { + this.start.set(startOf(this.options)); + this.editOverlay(this.appliedText()); + this.mode.set(this.wire.brokerMode()); + this.telemetries.set(this.wire.telemetries()); + this.editBroker(JSON.stringify(this.wire.brokerConfig())); + } + + /** + * Every setting back to the lab's default, as each one's Reset puts it: + * to apply with the rest, a debug flag at once. + */ + resetAll(): void { + for (const setting of this.modified()) { + setting.revert(); + } + } + + setDebug(flag: LabDebugFlag, on: boolean): void { + try { + if (on) { + this.window.localStorage.setItem(flag, 'true'); + } else { + this.window.localStorage.removeItem(flag); + } + } catch { + // Storage blocked: the flags stay as they were. + } + this.debug.set(this.readDebug()); + } + + private startChanges(): LabSettingsEntry[] { + const start = this.start(); + return startKeys() + .filter((key) => start[key] !== this.options[key]) + .map((key) => + this.startEntry(key, this.options[key], start[key], () => + this.setStart(key, this.options[key]) + ) + ); + } + + private startModified(): LabSettingsEntry[] { + return startKeys() + .filter((key) => this.options[key] !== START_DEFAULTS[key]) + .map((key) => + this.startEntry(key, START_DEFAULTS[key], this.options[key], () => + this.setStart(key, START_DEFAULTS[key]) + ) + ); + } + + /** + * The overlay's settings set otherwise than applied. One set to what it + * already is, unset before, is no change; a section the lab doesn't take, + * or one that isn't an object, is the JSON's to show. + */ + private overlayChanges(): LabSettingsEntry[] { + const draft = this.overlay(); + if (!draft) { + return [ + notAnObject('json', JSON_SECTION, () => + this.editOverlay(this.appliedText()) + ) + ]; + } + const applied = this.applied; + const entries: LabSettingsEntry[] = []; + for (const name of keysOf(applied, draft)) { + const inEffect = own(this.inEffect, name); + const before = applied[name] ?? {}; + const after = draft[name] ?? {}; + if (!inEffect || !isObject(before) || !isObject(after)) { + if (!same(applied[name], draft[name])) { + entries.push({ + id: name, + section: JSON_SECTION, + label: name, + from: show(applied[name]), + to: show(draft[name]), + revert: () => this.putOverlaySection(name, applied[name]) + }); + } + continue; + } + for (const key of keysOf(before, after)) { + const was = before[key] ?? inEffect[key]; + if ( + same(before[key], after[key]) || + (before[key] === undefined && same(after[key], was)) + ) { + continue; + } + entries.push( + this.overlayEntry( + name, + key, + was, + after[key] ?? own(OVERLAY_DEFAULTS, name)?.[key], + () => this.putOverlay(name, key, before[key]) + ) + ); + } + } + return entries; + } + + /** The overlay's settings this load runs with otherwise than the lab's defaults. */ + private overlayModified(): LabSettingsEntry[] { + const entries: LabSettingsEntry[] = []; + for (const [name, settings] of Object.entries(this.applied)) { + const defaults = own(OVERLAY_DEFAULTS, name); + if (!defaults || !isObject(settings)) { + entries.push({ + id: name, + section: JSON_SECTION, + label: name, + from: show(undefined), + to: show(settings), + revert: () => this.putOverlaySection(name, undefined) + }); + continue; + } + for (const [key, value] of Object.entries(settings)) { + if (!same(value, defaults[key])) { + entries.push( + this.overlayEntry(name, key, defaults[key], value, () => + this.putOverlay(name, key, undefined) + ) + ); + } + } + } + return entries; + } + + /** The broker's answer, set otherwise than the server has it. */ + private brokerChanges(): LabSettingsEntry[] { + const entries: LabSettingsEntry[] = []; + const mode = this.wire.brokerMode(); + if (this.mode() !== mode) { + entries.push(modeEntry(mode, this.mode(), () => this.mode.set(mode))); + } + const telemetries = this.wire.telemetries(); + if (!same(this.telemetries(), telemetries)) { + entries.push( + telemetriesEntry(telemetries, this.telemetries(), () => + this.telemetries.set(telemetries) + ) + ); + } + const draft = this.brokerObject(); + if (!draft) { + return [ + ...entries, + notAnObject('broker-json', MONITOR_SECTION, () => + this.editBroker(JSON.stringify(this.wire.brokerConfig())) + ) + ]; + } + const applied = this.wire.brokerConfig(); + for (const key of keysOf(applied, draft)) { + const field = FIELDS.get(`broker.${key}`)?.field; + const was = applied[key] ?? field?.default; + if ( + same(applied[key], draft[key]) || + (applied[key] === undefined && + field !== undefined && + same(draft[key], was)) + ) { + continue; + } + entries.push( + brokerEntry(key, was, draft[key] ?? field?.default, () => + this.putBroker(key, applied[key]) + ) + ); + } + return entries; + } + + /** The broker's answer, as the server has it, otherwise than by default. */ + private brokerModified(): LabSettingsEntry[] { + const entries: LabSettingsEntry[] = []; + const mode = this.wire.brokerMode(); + if (mode !== 'ok') { + entries.push(modeEntry('ok', mode, () => this.mode.set('ok'))); + } + const telemetries = this.wire.telemetries(); + if (telemetries !== null) { + entries.push( + telemetriesEntry(null, telemetries, () => this.telemetries.set(null)) + ); + } + for (const [key, value] of Object.entries(this.wire.brokerConfig())) { + const field = FIELDS.get(`broker.${key}`)?.field; + if (!field || !same(value, field.default)) { + entries.push( + brokerEntry(key, field?.default, value, () => + this.putBroker(key, undefined) + ) + ); + } + } + return entries; + } + + /** The debug flags that are on: off by default, and back off at once. */ + private debugModified(): LabSettingsEntry[] { + const debug = this.debug(); + return LAB_DEBUG_FLAGS.filter((flag) => debug[flag]).map((flag) => ({ + id: `debug.${flag}`, + section: DEBUG_SECTION, + label: flag, + from: 'off', + to: 'on', + revert: () => this.setDebug(flag, false) + })); + } + + private startEntry( + key: keyof LabStart, + from: unknown, + to: unknown, + revert: () => void + ): LabSettingsEntry { + const { field, section } = FIELDS.get(`start.${key}`)!; + return { + id: labSettingId(field), + section, + label: field.label, + from: show(from, field), + to: show(to, field), + revert + }; + } + + private overlayEntry( + name: string, + key: string, + from: unknown, + to: unknown, + revert: () => void + ): LabSettingsEntry { + const found = FIELDS.get(`${name}.${key}`); + return { + id: `${name}.${key}`, + section: found?.section ?? JSON_SECTION, + label: found?.field.label ?? `${name}.${key}`, + from: show(from, found?.field), + to: show(to, found?.field), + revert + }; + } + + private setStart(key: keyof LabStart, value: unknown): void { + this.start.update((start) => ({ ...start, [key]: value })); + } + + /** + * Sets one of the overlay's settings in the draft; `undefined` takes it + * out, and its section with it once empty. + */ + private putOverlay(name: string, key: string, value: unknown): void { + const overlay = this.overlay(); + if (!overlay) { + return; + } + const current = overlay[name]; + const settings = isObject(current) ? current : {}; + const next = + value === undefined + ? without(settings, key) + : { ...settings, [key]: value }; + this.putOverlaySection(name, Object.keys(next).length ? next : undefined); + } + + /** Sets a whole section of the overlay in the draft; `undefined` takes it out. */ + private putOverlaySection(name: string, value: unknown): void { + const overlay = this.overlay(); + if (overlay) { + this.editOverlay( + JSON.stringify( + value === undefined + ? without(overlay, name) + : { ...overlay, [name]: value }, + null, + 2 + ) + ); + } + } + + /** Sets one of the broker's settings in the draft; `undefined` takes it out. */ + private putBroker(key: string, value: unknown): void { + const config = this.brokerObject(); + if (config) { + this.editBroker( + JSON.stringify( + value === undefined + ? without(config, key) + : { ...config, [key]: value } + ) + ); + } + } + + private appliedText(): string { + return JSON.stringify(this.options.config, null, 2); + } + + /** Starts the lab again, with `config`, and as `start` says. */ + private reload(config: Record, start: LabStart): void { + this.window.location.assign( + `${this.window.location.pathname}${labSearch({ ...start, config })}` + ); + } + + /** As the library reads them: a list of logger names is not "on". */ + private readDebug(): Record { + return { + debugScenario: uwtIsDebugEnabled('debugScenario'), + debugBI: uwtIsDebugEnabled('debugBI'), + debugLogger: uwtIsDebugEnabled('debugLogger') + }; + } +} + +function startOf({ + destination, + telemetry, + channel, + inspector +}: LabOptions): LabStart { + return { destination, telemetry, channel, inspector }; +} + +function startKeys(): (keyof LabStart)[] { + return Object.keys(START_DEFAULTS) as (keyof LabStart)[]; +} + +function modeEntry( + from: LabBrokerMode, + to: LabBrokerMode, + revert: () => void +): LabSettingsEntry { + return { + id: 'rum.mode', + section: BROKER_SECTION, + label: 'Answer /rum/init with', + from, + to, + revert + }; +} + +function telemetriesEntry( + from: readonly LabTelemetry[] | null, + to: readonly LabTelemetry[] | null, + revert: () => void +): LabSettingsEntry { + const text = (telemetries: readonly LabTelemetry[] | null) => + telemetries === null + ? "the library's default" + : labTelemetriesLabel(telemetries); + return { + id: 'rum.telemetries', + section: BROKER_SECTION, + label: 'Telemetries', + from: text(from), + to: text(to), + revert + }; +} + +function brokerEntry( + key: string, + from: unknown, + to: unknown, + revert: () => void +): LabSettingsEntry { + const field = FIELDS.get(`broker.${key}`)?.field; + return { + id: `broker.${key}`, + section: MONITOR_SECTION, + label: field?.label ?? key, + from: show(from, field), + to: show(to, field), + revert + }; +} + +function notAnObject( + id: string, + section: LabSettingsSection, + revert: () => void +): LabSettingsEntry { + return { + id, + section, + label: 'Not a JSON object yet', + from: '', + to: '', + revert + }; +} + +/** In the order of the sections, and of the settings within each. */ +function bySection(entries: LabSettingsEntry[]): LabSettingsEntry[] { + return entries.sort( + (a, b) => + LAB_SETTINGS_SECTIONS.indexOf(a.section) - + LAB_SETTINGS_SECTIONS.indexOf(b.section) + ); +} + +function isObject(value: unknown): value is Record { + return !!value && typeof value === 'object' && !Array.isArray(value); +} + +/** One setting of an overlay: `undefined` where it isn't set, or the overlay isn't an object. */ +function settingIn( + overlay: Record | undefined, + section: string, + key: string +): unknown { + const settings = overlay?.[section]; + return isObject(settings) ? settings[key] : undefined; +} + +/** An object's own property: what every object inherits, `toString`, is none. */ +function own(object: Record, key: string): T | undefined { + return Object.hasOwn(object, key) ? object[key] : undefined; +} + +function without( + object: Record, + key: string +): Record { + const { [key]: _gone, ...rest } = object; + return rest; +} + +/** The keys of either object, once each. */ +function keysOf(a: object, b: object): string[] { + return [...new Set([...Object.keys(a), ...Object.keys(b)])]; +} + +/** + * Whether two settings are the same, as the JSON would have them: a pattern + * as its source, a transform as its name, an object's keys in any order. + */ +function same(a: unknown, b: unknown): boolean { + return comparable(a) === comparable(b); +} + +function comparable(value: unknown): string { + return ( + JSON.stringify(value, (_key, v: unknown) => + v instanceof RegExp + ? v.source + : typeof v === 'function' + ? v.name + : isObject(v) + ? Object.fromEntries( + Object.keys(v) + .sort() + .map((key) => [key, v[key]]) + ) + : v + ) ?? 'undefined' + ); +} + +/** A setting's value as the lists show it. */ +function show(value: unknown, field?: LabField): string { + if (value === undefined) { + return 'not set'; + } + if (field?.kind === 'shortcuts' && Array.isArray(value)) { + return ( + value + .map((shortcut: LabShortcut) => labDescribeShortcut(shortcut)) + .join(', ') || 'none' + ); + } + if (typeof value === 'boolean') { + return value ? 'on' : 'off'; + } + if (value instanceof RegExp) { + return value.source; + } + if (typeof value === 'function') { + return value.name; + } + if (Array.isArray(value)) { + return value.map((item) => show(item)).join(', ') || 'none'; + } + if (isObject(value)) { + return ( + Object.entries(value) + .map(([key, item]) => `${key}: ${show(item)}`) + .join(', ') || 'none' + ); + } + return value === '' ? 'empty' : String(value); +} + +/** RegExps and functions, readable in JSON. */ +function readable(_key: string, value: unknown): unknown { + if (value instanceof RegExp) { + return String(value); + } + return typeof value === 'function' + ? `ƒ ${value.name || '(anonymous)'}` + : value; +} diff --git a/projects/watchtower-lab/src/lab/lab-settings-fields.ts b/projects/watchtower-lab/src/lab/lab-settings-fields.ts new file mode 100644 index 0000000..cf77b20 --- /dev/null +++ b/projects/watchtower-lab/src/lab/lab-settings-fields.ts @@ -0,0 +1,641 @@ +import { LAB_VALUE_TRANSFORMS, LabOptions } from './lab-options'; +import { LabBrokerMode } from './lab-wire.service'; + +type Option = { label: string; value: string }; + +/** + * A setting Settings offers as a control. `section` is where it is kept: + * `start` for how the lab starts, `rum` for the mock broker's answer, + * `broker` for its app monitor config, `debug` for the debug flags, and + * otherwise its section of the `?config=` overlay. `multi` picks several of + * its options; `lines` is a list of strings, one to a line; `levels`, + * `shortcuts` and `telemetries` have editors of their own. + */ +export type LabField = { + section: string; + key: string; + /** What it is, as people read it; its control adds the setting's name. */ + label: string; + /** What it does, from the documentation of what it sets. */ + help: string; + /** What the app monitor does with none given: broker settings only. */ + default?: unknown; +} & ( + | { + kind: + | 'switch' + | 'number' + | 'text' + | 'lines' + | 'levels' + | 'shortcuts' + | 'telemetries'; + } + | { kind: 'select' | 'multi'; options: Option[] } +); + +/** Kept elsewhere than in the `?config=` overlay. */ +const OUTSIDE_OVERLAY = new Set(['start', 'rum', 'broker', 'debug']); + +/** Whether a field writes into the `?config=` overlay, and so waits for its JSON. */ +export function labInOverlay(field: LabField): boolean { + return !OUTSIDE_OVERLAY.has(field.section); +} + +/** + * The setting's name as the JSON has it: `logging.minLevel` in the overlay, + * `sessionSampleRate` in the app monitor config. Empty for the others, + * which have no JSON. + */ +export function labSettingName(field: LabField): string { + switch (field.section) { + case 'start': + case 'rum': + case 'debug': + return ''; + case 'broker': + return field.key; + default: + return `${field.section}.${field.key}`; + } +} + +/** + * A field's control label: what it is, then the setting's name as the JSON + * has it, so the control and the JSON can be read together. + */ +export function labFieldLabel(field: LabField): string { + const name = labSettingName(field); + return name ? `${field.label} (${name})` : field.label; +} + +/** The groups of sections in the Settings list, in order. */ +export const LAB_SETTINGS_AREAS = [ + 'Library', + 'Mock backend', + 'Diagnostics', + 'Advanced' +] as const; + +/** How a section's settings take effect, as its tag says. */ +export const LAB_SETTINGS_APPLIES = { + reload: 'Applies on reload', + server: 'Applies on reload, in every tab', + now: 'Applies at once, in this browser' +} as const; + +/** A section of Settings: a page of its own, with what it sets. */ +export interface LabSettingsSection { + /** Its address, under `/settings`. */ + path: string; + label: string; + area: (typeof LAB_SETTINGS_AREAS)[number]; + /** How its settings take effect; none for a readout. */ + applies?: keyof typeof LAB_SETTINGS_APPLIES; + /** What it sets, under its title. */ + lead: string; + fields: LabField[]; +} + +export const LAB_DEBUG_FLAGS = [ + 'debugScenario', + 'debugBI', + 'debugLogger' +] as const; +export type LabDebugFlag = (typeof LAB_DEBUG_FLAGS)[number]; + +export const LAB_LOG_LEVELS = [ + { label: 'log — everything', value: 'log' }, + { label: 'warn — warnings and errors', value: 'warn' }, + { label: 'error — errors only', value: 'error' } +]; + +/** How the lab starts; each is read once, at startup, from the address. */ +const DESTINATIONS: { label: string; value: LabOptions['destination'] }[] = [ + { label: 'rum — the RUM sink, to the mock backend', value: 'rum' }, + { label: 'noop — nothing is sent', value: 'noop' }, + { label: "custom — the lab's own sink", value: 'custom' } +]; +const CHANNELS: { label: string; value: LabOptions['channel'] }[] = [ + { label: "tab — the shell's own channel", value: 'tab' }, + { label: 'shared — one channel for every tab', value: 'shared' } +]; +const INSPECTOR_MODES: { label: string; value: LabOptions['inspector'] }[] = [ + { label: 'on — with its keyboard shortcut', value: 'on' }, + { label: 'no shortcut — open it from the page', value: 'noshortcut' }, + { label: 'off — disabled', value: 'off' } +]; + +export const LAB_BROKER_MODES: { label: string; value: LabBrokerMode }[] = [ + { label: 'ok — RUM records normally', value: 'ok' }, + { label: 'null — RUM off for the page', value: 'null' }, + { label: 'fail — broker error, the app carries on', value: 'fail' }, + { + label: 'short-expiry — credentials refresh within seconds', + value: 'short-expiry' + }, + { label: 'flaky — fails twice, then answers', value: 'flaky' }, + { + label: 'invalid — a pattern as a string: RUM not started', + value: 'invalid' + }, + { + label: 'null-on-refresh — RUM off at the first refresh', + value: 'null-on-refresh' + }, + { + label: 'no-credentials-on-refresh — a refresh without credentials', + value: 'no-credentials-on-refresh' + } +]; + +/** What redaction being off for a concern leaves, as the library documents it. */ +const REDACT_HELP = + 'Turned off, only the configurable scrubbing stops: key and value patterns, and URL queries. The built-in key denylist, size caps and value transforms still apply.'; + +/** + * The sections of Settings, by what their settings change, each with the + * controls it offers and what they do. The library's settings are the ones + * the lab takes in its `?config=` overlay; the app monitor's are the ones + * most worth trying, with the values the broker answers with, or the app + * monitor takes, when none is given. Any others are the JSON's. + */ +export const LAB_SETTINGS_SECTIONS: LabSettingsSection[] = [ + { + path: 'app-identity', + label: 'App identity', + area: 'Library', + applies: 'reload', + lead: "What the telemetry says it comes from: the application, its environment and version, and the namespace of the library's event types.", + fields: [ + { + kind: 'text', + section: 'identity', + key: 'application', + label: 'Application name', + help: 'The application, as each session records it.' + }, + { + kind: 'text', + section: 'identity', + key: 'environment', + label: 'Environment', + help: 'Where it is deployed: dev, qa, prod.' + }, + { + kind: 'text', + section: 'identity', + key: 'version', + label: 'Version', + help: "The application's version, such as 22.0.0." + }, + { + kind: 'text', + section: 'identity', + key: 'eventNamespace', + label: 'Event namespace', + help: 'The prefix of the event types the library sends: {namespace}.scenario, {namespace}.scenario.step and {namespace}.bi. com.uwt when empty.' + } + ] + }, + { + path: 'delivery', + label: 'Delivery', + area: 'Library', + applies: 'reload', + lead: 'Whether the library sends telemetry, where it goes and over which broadcast channel, and whether the lab batches its logs.', + fields: [ + { + kind: 'select', + section: 'start', + key: 'destination', + label: 'Destination', + help: "Where the library's telemetry goes: to the mock backend through the RUM sink, nowhere, or to the lab's own sink, given with provideUwtTelemetryDestination.", + options: DESTINATIONS + }, + { + kind: 'select', + section: 'start', + key: 'channel', + label: 'Broadcast channel', + help: "The channel the shell hosts its fragments' telemetry on: its own, or the default one, shared by every tab, whose hosts elect one leader.", + options: CHANNELS + }, + { + kind: 'switch', + section: 'start', + key: 'telemetry', + label: 'Telemetry', + help: 'Off, every service and destination is inert, through withTelemetryEnabled: nothing is recorded or sent.' + }, + { + kind: 'switch', + section: 'lab', + key: 'logBatching', + label: 'Batch logs, sent on flush()', + help: "The lab's log provider keeps the records, and sends them when the library flushes it." + } + ] + }, + { + path: 'scenarios', + label: 'Scenarios', + area: 'Library', + applies: 'reload', + lead: 'How scenarios time out, count their steps, and report them.', + fields: [ + { + kind: 'number', + section: 'scenarios', + key: 'defaultTimeoutMs', + label: 'Default timeout, ms', + help: 'After this long, a scenario still unsettled settles as timeout. 0 turns the default off; a scenario can still set its own.' + }, + { + kind: 'switch', + section: 'scenarios', + key: 'emitLifecycleEvents', + label: 'Lifecycle step events', + help: "An event for each step, rather than one packed event when the scenario settles. They count against RUM's session event limit: for debugging, not production." + }, + { + kind: 'number', + section: 'scenarios', + key: 'maxSteps', + label: 'Step limit', + help: 'Steps kept per scenario. Later ones are counted in stepCount but not stored, bounding a runaway loop.' + }, + { + kind: 'switch', + section: 'scenarios', + key: 'userTimings', + label: 'User timings', + help: "Scenarios and their steps as performance marks and measures, on DevTools' Performance track. The debugScenario flag turns them on too." + }, + { + kind: 'number', + section: 'scenarios', + key: 'markCleanupFallbackMs', + label: 'Mark cleanup fallback, ms', + help: "How long the marks of a scenario that never settles stay, with user timings on and no timeout. 0 keeps them for the page's life." + }, + { + kind: 'switch', + section: 'scenarios', + key: 'redact', + label: 'Redact scenario payloads', + help: `Whether scenario records are redacted. ${REDACT_HELP}` + } + ] + }, + { + path: 'bi-events', + label: 'BI events', + area: 'Library', + applies: 'reload', + lead: 'How the library drops repeated BI events, and whether it redacts their payloads.', + fields: [ + { + kind: 'number', + section: 'bi', + key: 'dedupWindowMs', + label: 'BI dedup window, ms', + help: 'An identical event within this long of another is a double fire, and is dropped. 0 keeps every one.' + }, + { + kind: 'number', + section: 'bi', + key: 'dedupMaxKeys', + label: 'BI dedup keys kept', + help: 'Distinct events remembered before expired ones are swept, so a long session stays bounded.' + }, + { + kind: 'switch', + section: 'bi', + key: 'redact', + label: 'Redact BI payloads', + help: `Whether BI events are redacted. ${REDACT_HELP}` + } + ] + }, + { + path: 'logging', + label: 'Logging', + area: 'Library', + applies: 'reload', + lead: "How the library's loggers filter, mirror and redact records.", + fields: [ + { + kind: 'select', + section: 'logging', + key: 'minLevel', + label: 'Minimum level', + help: 'Records below this severity are discarded.', + options: LAB_LOG_LEVELS + }, + { + kind: 'switch', + section: 'logging', + key: 'mirrorErrorsToRum', + label: 'Mirror errors to RUM', + help: "logger.error calls go to RUM as errors too, counting against the session's event budget with scenarios and BI events." + }, + { + kind: 'levels', + section: 'logging', + key: 'levels', + label: 'Logger levels', + help: 'A level of its own for a named logger, over the minimum level; the others keep the minimum.' + }, + { + kind: 'switch', + section: 'logging', + key: 'redact', + label: 'Redact log values', + help: `Whether log records are redacted. ${REDACT_HELP}` + } + ] + }, + { + path: 'privacy', + label: 'Privacy & redaction', + area: 'Library', + applies: 'reload', + lead: 'What the library redacts from everything it sends: long strings and stacks, URL queries, and the keys and values that match a pattern.', + fields: [ + { + kind: 'number', + section: 'redaction', + key: 'maxStringLength', + label: 'Longest string', + help: 'A string value longer than this is truncated.' + }, + { + kind: 'number', + section: 'redaction', + key: 'maxKeys', + label: 'Most keys', + help: 'Attributes kept on one payload; the rest are dropped.' + }, + { + kind: 'number', + section: 'redaction', + key: 'maxStackLength', + label: 'Longest stack', + help: 'A stack trace longer than this is truncated.' + }, + { + kind: 'switch', + section: 'redaction', + key: 'includeStack', + label: 'Include stacks', + help: 'Whether stack traces are captured at all.' + }, + { + kind: 'switch', + section: 'redaction', + key: 'stripUrlQuery', + label: 'Strip URL queries', + help: 'Query strings and fragments are stripped from URLs in string values.' + }, + { + kind: 'multi', + section: 'redaction', + key: 'scanValuePatterns', + label: 'Value patterns scanned', + help: 'Shapes of personal data looked for in values, besides the key denylist. Heuristics: phone is the noisiest; creditCard also needs a Luhn match.', + options: ['email', 'creditCard', 'ssn', 'ipv4', 'phone'].map( + (name) => ({ + label: name, + value: name + }) + ) + }, + { + kind: 'multi', + section: 'redaction', + key: 'extraValueTransforms', + label: 'Value transforms', + help: 'Functions run on every string value after the patterns, for what no pattern can express. A failing one is skipped.', + options: Object.keys(LAB_VALUE_TRANSFORMS).map((name) => ({ + label: name, + value: name + })) + }, + { + kind: 'lines', + section: 'redaction', + key: 'extraKeyPatterns', + label: 'Extra key patterns, one RegExp per line', + help: 'Keys redacted besides the built-in denylist, matched against attribute names, ignoring case.' + }, + { + kind: 'lines', + section: 'redaction', + key: 'extraValuePatterns', + label: 'Extra value patterns, one RegExp per line', + help: 'Values redacted besides the scanned shapes: the content counterpart to the key patterns.' + } + ] + }, + { + path: 'event-inspector', + label: 'Event Inspector', + area: 'Library', + applies: 'reload', + lead: 'Whether the inspector is there and how it opens, and how much it keeps and shows.', + fields: [ + { + kind: 'select', + section: 'start', + key: 'inspector', + label: 'Availability', + help: "Whether the inspector can be opened: with its keyboard shortcut as well as from the page, from the page's button only, or not at all.", + options: INSPECTOR_MODES + }, + { + kind: 'number', + section: 'inspector', + key: 'maxEventsPerSection', + label: 'Events kept per kind', + help: 'Events kept of each kind: BI, scenarios, logs. The oldest of a kind go first, so a burst of one never pushes out another.' + }, + { + kind: 'number', + section: 'inspector', + key: 'maxPayloadCharsInView', + label: 'Payload characters shown', + help: 'A payload longer than this, in characters, shows truncated, at most 65,536; the download has it in full.' + }, + { + kind: 'number', + section: 'inspector', + key: 'captureBeforeOpen', + label: 'Keep events from before opening', + help: "Events of each kind kept from the app's start, to show what happened before it opened. 0 keeps none, and spares copying every event." + }, + { + kind: 'shortcuts', + section: 'inspector', + key: 'shortcuts', + label: 'Shortcuts', + help: "The keys that open the inspector. With none, the page's button is the only way in." + } + ] + }, + { + path: 'rum-broker', + label: 'RUM broker', + area: 'Mock backend', + applies: 'server', + lead: "How the mock RUM broker answers /rum/init, and the RUM telemetries it names. The lab's server keeps them, for every tab.", + fields: [ + { + kind: 'select', + section: 'rum', + key: 'mode', + label: 'Answer /rum/init with', + help: 'How the broker answers the start of RUM: normally, turning RUM off, failing, or in one of the ways the RUM sink has to survive.', + options: LAB_BROKER_MODES + }, + { + kind: 'telemetries', + section: 'rum', + key: 'telemetries', + label: 'Telemetries', + help: "RUM's own telemetry: the library's default is errors, performance and same-origin HTTP. With none, RUM records only page views, the session start and the library's events." + } + ] + }, + { + path: 'app-monitor', + label: 'App monitor', + area: 'Mock backend', + applies: 'server', + lead: "The app monitor config the mock broker lays over its answer: the settings most worth trying, and the JSON, which has the rest and is what is sent. The lab's server keeps it, for every tab.", + fields: [ + { + kind: 'number', + section: 'broker', + key: 'sessionSampleRate', + label: 'Session sample rate, 0 to 1', + help: 'The share of sessions RUM records: 0 none, 1 every one.', + default: 1 + }, + { + kind: 'number', + section: 'broker', + key: 'sessionEventLimit', + label: 'Session event limit, 0 for none', + help: 'The most events RUM records in one session.', + default: 0 + }, + { + kind: 'number', + section: 'broker', + key: 'dispatchInterval', + label: 'Dispatch interval, ms', + help: 'How often RUM sends the events it has gathered.', + default: 2000 + }, + { + kind: 'switch', + section: 'broker', + key: 'allowCookies', + label: 'Allow cookies', + help: 'RUM keeps its session and user ids in cookies, cwr_s and cwr_u, so it can count sessions and returning users.', + default: true + }, + { + kind: 'switch', + section: 'broker', + key: 'enableXRay', + label: 'X-Ray tracing', + help: 'An X-Ray trace for the page load and each HTTP request RUM records, with the trace header added to the requests.', + default: false + }, + { + kind: 'switch', + section: 'broker', + key: 'disableAutoPageView', + label: 'Automatic page view off', + help: 'RUM records no page views of its own: the app records them.', + default: false + }, + { + kind: 'switch', + section: 'broker', + key: 'recordResourceUrl', + label: 'Record resource URLs', + help: 'Whether RUM records the URLs of the resources the page downloads, which may hold personal data.', + default: true + }, + { + kind: 'lines', + section: 'broker', + key: 'pagesToExclude', + label: 'Pages to exclude, one RegExp per line', + help: 'Pages RUM records nothing on; they win over the pages to include.', + default: [] + }, + { + kind: 'lines', + section: 'broker', + key: 'pagesToInclude', + label: 'Pages to include, one RegExp per line', + help: 'The pages RUM records; every page when there are none.', + default: [] + } + ] + }, + { + path: 'debug-flags', + label: 'Debug flags', + area: 'Diagnostics', + applies: 'now', + lead: 'Console lines for what is sent. The library reads them on every event, so they apply at once, without a reload, and are never waiting to be applied.', + fields: [ + { + kind: 'switch', + section: 'debug', + key: 'debugScenario', + label: 'debugScenario', + help: 'A line for each scenario event sent. Turns user timings on too.' + }, + { + kind: 'switch', + section: 'debug', + key: 'debugBI', + label: 'debugBI', + help: 'A line for each BI event sent.' + }, + { + kind: 'switch', + section: 'debug', + key: 'debugLogger', + label: 'debugLogger', + help: 'A line for each log record sent. Set from DevTools to logger names, such as checkout,cart, it prints only theirs; the switch then shows off.' + } + ] + }, + { + path: 'json', + label: 'Configuration as JSON', + area: 'Advanced', + applies: 'reload', + lead: "The library's settings from every section, as JSON. Edit it to paste a whole configuration, or to set what has no control.", + fields: [] + }, + { + path: 'effective', + label: 'Effective configuration', + area: 'Advanced', + lead: 'What the library was given on this load, read back from its tokens.', + fields: [] + } +]; + +/** The section with this path. */ +export function labSettingsSection(path: string): LabSettingsSection { + return LAB_SETTINGS_SECTIONS.find((section) => section.path === path)!; +} diff --git a/projects/watchtower-lab/src/lab/lab-shortcuts.component.ts b/projects/watchtower-lab/src/lab/lab-shortcuts.component.ts new file mode 100644 index 0000000..b07589e --- /dev/null +++ b/projects/watchtower-lab/src/lab/lab-shortcuts.component.ts @@ -0,0 +1,154 @@ +import { + ChangeDetectionStrategy, + Component, + input, + output, + signal +} from '@angular/core'; +import { + CpsButtonComponent, + CpsInputComponent, + CpsSwitchComponent +} from 'cps-ui-kit'; + +/** A shortcut as `inspector.shortcuts` has it. */ +export interface LabShortcut { + code: string; + after?: string; + ctrl?: boolean; + alt?: boolean; + shift?: boolean; + meta?: boolean; +} + +const MODIFIERS = ['ctrl', 'alt', 'shift', 'meta'] as const; + +/** A shortcut as people read it: `Shift+E+I`, `F9`, `Ctrl+Alt+D`. */ +export function labDescribeShortcut(shortcut: LabShortcut): string { + const key = (code: string | undefined) => code?.replace(/^Digit|^Key/, ''); + return [ + shortcut.ctrl && 'Ctrl', + shortcut.alt && 'Alt', + shortcut.shift && 'Shift', + shortcut.meta && 'Meta', + key(shortcut.after), + key(shortcut.code) + ] + .filter(Boolean) + .join('+'); +} + +/** + * The keys that open the Event Inspector: the ones set, each with a way to + * remove it, and a way to add one. None at all is no shortcut. + */ +@Component({ + selector: 'lab-shortcuts', + imports: [CpsButtonComponent, CpsInputComponent, CpsSwitchComponent], + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` +
+ @for (shortcut of shortcuts(); track $index) { +
+ {{ describe(shortcut) }} + +
+ } @empty { +

+ No shortcut: the inspector opens from the page. +

+ } +
+ + +
+
+ @for (modifier of modifiers; track modifier) { + + } + +
+
+ ` +}) +export class LabShortcutsComponent { + readonly shortcuts = input.required(); + readonly disabled = input(false); + /** The shortcuts as the reader has changed them. */ + readonly changed = output(); + + protected readonly describe = labDescribeShortcut; + protected readonly modifiers = MODIFIERS.map((key) => ({ + key, + label: key === 'meta' ? 'Meta (⌘)' : key[0].toUpperCase() + key.slice(1) + })); + + protected readonly code = signal(''); + protected readonly after = signal(''); + protected readonly held = signal>( + { + ctrl: false, + alt: false, + shift: false, + meta: false + } + ); + + protected hold(key: (typeof MODIFIERS)[number], on: boolean): void { + this.held.update((held) => ({ ...held, [key]: on })); + } + + protected remove(index: number): void { + this.changed.emit(this.shortcuts().filter((_, i) => i !== index)); + } + + protected add(): void { + const code = this.code().trim(); + if (!code) { + return; + } + const after = this.after().trim(); + const held = this.held(); + this.changed.emit([ + ...this.shortcuts(), + { + code, + ...(after && { after }), + ...Object.fromEntries( + MODIFIERS.filter((key) => held[key]).map((key) => [key, true]) + ) + } + ]); + this.code.set(''); + this.after.set(''); + this.held.set({ ctrl: false, alt: false, shift: false, meta: false }); + } +} diff --git a/projects/watchtower-lab/src/lab/lab-users.ts b/projects/watchtower-lab/src/lab/lab-users.ts new file mode 100644 index 0000000..17606e4 --- /dev/null +++ b/projects/watchtower-lab/src/lab/lab-users.ts @@ -0,0 +1,14 @@ +/** RUM user ids must be UUIDs. */ +export const ANNA = '3f2504e0-4f89-41d3-9a0c-0305e82c3301'; +export const BEN = '9b2d1c6e-7a53-4d0e-8f3b-2c1a5e6d7f80'; +export const CARLA = '5c8e2f14-1b7d-4a96-9e3c-8d2b7f4a6c10'; + +/** + * The people the lab signs in, by id. Carla signs in from the fragment, a + * separate app that keeps its own copy of her id. + */ +export const LAB_USER_NAMES: Record = { + [ANNA]: 'Anna', + [BEN]: 'Ben', + [CARLA]: 'Carla' +}; diff --git a/projects/watchtower-lab/src/lab/lab-wire.service.ts b/projects/watchtower-lab/src/lab/lab-wire.service.ts new file mode 100644 index 0000000..12a3c37 --- /dev/null +++ b/projects/watchtower-lab/src/lab/lab-wire.service.ts @@ -0,0 +1,184 @@ +import { + afterNextRender, + computed, + DestroyRef, + inject, + Injectable, + signal +} from '@angular/core'; +import { UwtLogRecord } from '@absaoss-cps/ngx-ui-watchtower'; + +/** One RUM event as the mock dataplane received it, with its batch's user. */ +/** An entry the lab's own sink sent, and when it arrived. */ +export interface LabCustomEntry { + kind: string; + eventType?: string; + payload?: unknown; + error?: unknown; + receivedAt: string; +} + +export interface LabRumEvent { + /** RUM's own id for the event. */ + id: string; + receivedAt: string; + batch: number; + user: { sessionId?: string; userId?: string }; + type: string; + metadata: Record; + details: Record; +} + +export type LabBrokerMode = + | 'ok' + | 'null' + | 'fail' + | 'short-expiry' + | 'flaky' + | 'invalid' + | 'null-on-refresh' + | 'no-credentials-on-refresh'; + +/** RUM's own telemetries, as the mock broker names them. */ +export const LAB_TELEMETRIES = [ + 'errors', + 'performance', + 'interaction', + 'http', + 'replay' +] as const; + +export type LabTelemetry = (typeof LAB_TELEMETRIES)[number]; + +/** RUM's telemetries as people read them: `default`, `none`, or a list. */ +export function labTelemetriesLabel( + telemetries: readonly string[] | null +): string { + return telemetries === null ? 'default' : telemetries.join(', ') || 'none'; +} + +function sameJson(a: unknown, b: unknown): boolean { + return JSON.stringify(a) === JSON.stringify(b); +} + +/** How often "On the wire" and the status bar ask the mock backend, in ms. */ +const POLL_MS = 1500; + +/** + * `fetch` as it was before RUM wrapped it: this module loads before RUM + * starts. RUM's `http` telemetry records the app's requests, and the lab's + * own polling would otherwise add an event every 1.5 s, forever. The server + * never polls. + */ +const labFetch = + typeof window === 'undefined' ? fetch : window.fetch.bind(window); + +/** + * What actually reached the lab's mock backend: every RUM event the + * dataplane received and every log record the log backend stored — the + * other side of what the app (and the Event Inspector) says it sent. + */ +@Injectable({ providedIn: 'root' }) +export class LabWire { + /** Whether the mock backend has answered yet. */ + readonly polled = signal(false); + readonly batches = signal(0); + readonly rum = signal([]); + readonly logs = signal([]); + /** What the lab's own sink received, with `?destination=custom`. */ + readonly custom = signal([]); + readonly brokerMode = signal('ok'); + + // The next two change only when their content does: each poll brings new + // objects, and a draft built from them would be reset under the user's + // typing. + + /** Laid over the broker's app monitor config. */ + readonly brokerConfig = signal>( + {}, + { equal: sameJson } + ); + + /** RUM telemetries the broker names; `null` for the library's default. */ + readonly telemetries = signal(null, { + equal: sameJson + }); + + readonly telemetriesLabel = computed(() => + labTelemetriesLabel(this.telemetries()) + ); + + /** The library's own events, as opposed to RUM's page views and timings. */ + readonly libraryEvents = computed( + () => this.rum().filter((e) => e.type.startsWith('com.uwt.')).length + ); + + constructor() { + const destroyRef = inject(DestroyRef); + // In the browser only: on the server, a timer would keep the render from + // ever finishing. + afterNextRender(() => { + const timer = setInterval(() => this.refresh(), POLL_MS); + destroyRef.onDestroy(() => clearInterval(timer)); + this.refresh(); + }); + } + + async refresh(): Promise { + try { + const [rum, logs, custom, state] = await Promise.all([ + labFetch('/__lab/rum').then((r) => r.json()), + labFetch('/__lab/logs').then((r) => r.json()), + labFetch('/__lab/custom').then((r) => r.json()), + labFetch('/__lab/state').then((r) => r.json()) + ]); + this.batches.set(rum.batches); + this.rum.set(rum.events); + this.logs.set(logs); + this.custom.set(custom); + this.brokerMode.set(state.brokerMode); + this.brokerConfig.set(state.brokerConfig); + this.telemetries.set(state.telemetries); + this.polled.set(true); + } catch { + // The mock backend isn't running: `npm run lab` starts it. + } + } + + async reset(): Promise { + await labFetch('/__lab/reset', { method: 'POST' }); + await this.refresh(); + } + + /** The broker answers the next `/rum/init` this way; reload to apply it. */ + async setBrokerMode(mode: LabBrokerMode): Promise { + await labFetch('/__lab/broker-mode', { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ mode }) + }); + this.brokerMode.set(mode); + } + + /** Laid over the broker's next answer; reload to apply it. */ + async setBrokerConfig(config: Record): Promise { + await labFetch('/__lab/broker-config', { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ config }) + }); + this.brokerConfig.set(config); + } + + /** The broker names these in its next answer; reload to apply them. */ + async setTelemetries( + telemetries: readonly LabTelemetry[] | null + ): Promise { + await labFetch('/__lab/telemetries', { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ telemetries }) + }); + this.telemetries.set(telemetries); + } +} diff --git a/projects/watchtower-lab/src/main.server.ts b/projects/watchtower-lab/src/main.server.ts new file mode 100644 index 0000000..c79df48 --- /dev/null +++ b/projects/watchtower-lab/src/main.server.ts @@ -0,0 +1,12 @@ +import { + BootstrapContext, + bootstrapApplication +} from '@angular/platform-browser'; +import { ShellComponent } from './shell/shell.component'; +import { serverConfig } from './shell/shell.config.server'; + +/** The shell, rendered on the server for each request. */ +const bootstrap = (context: BootstrapContext) => + bootstrapApplication(ShellComponent, serverConfig, context); + +export default bootstrap; diff --git a/projects/watchtower-lab/src/main.ts b/projects/watchtower-lab/src/main.ts new file mode 100644 index 0000000..55ae35f --- /dev/null +++ b/projects/watchtower-lab/src/main.ts @@ -0,0 +1,13 @@ +import { bootstrapApplication } from '@angular/platform-browser'; +import { initializeWebFragments } from 'web-fragments'; +import { labOptions } from './lab/lab-options'; +import { ShellComponent } from './shell/shell.component'; +import { shellConfig } from './shell/shell.config'; + +// Registers , before the shell renders one. +initializeWebFragments(); + +bootstrapApplication( + ShellComponent, + shellConfig(labOptions(location.search)) +).catch((error: unknown) => console.error(error)); diff --git a/projects/watchtower-lab/src/shell/pages/bi.page.ts b/projects/watchtower-lab/src/shell/pages/bi.page.ts new file mode 100644 index 0000000..64f2bf2 --- /dev/null +++ b/projects/watchtower-lab/src/shell/pages/bi.page.ts @@ -0,0 +1,81 @@ +import { + ChangeDetectionStrategy, + Component, + inject, + signal +} from '@angular/core'; +import { + UwtBITelemetryService, + UwtScenarioTelemetryService +} from '@absaoss-cps/ngx-ui-watchtower'; +import { CpsButtonComponent } from 'cps-ui-kit'; +import { LabResultsComponent } from '../../lab/lab-results.component'; + +@Component({ + selector: 'lab-bi-page', + imports: [CpsButtonComponent, LabResultsComponent], + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` +
+

BI events

+

+ Identical events within 400 ms are collapsed into one. Results shows + what was sent, and when it arrived. +

+
+
+

Tracking

+
+ + + +
+
+
+ +
{{ readout() || 'Nothing tracked yet.' }}
+
+
+ ` +}) +export class BIPage { + protected readonly readout = signal(''); + + private readonly bi = inject(UwtBITelemetryService); + private readonly scenarios = inject(UwtScenarioTelemetryService); + + protected click(): void { + this.bi.track('lab_button_clicked', { button: 'primary', page: 'bi' }); + this.readout.set( + 'lab_button_clicked — click again within 400 ms: no second event.' + ); + } + + protected doubleFire(): void { + this.bi.track('lab_double_fired', { source: 'click-and-keydown' }); + this.bi.track('lab_double_fired', { source: 'click-and-keydown' }); + this.readout.set('lab_double_fired tracked twice — one event arrives.'); + } + + protected feature(): void { + const scenario = this.scenarios.start({ name: 'lab-happy-path' }); + this.bi.track( + 'lab_feature_used', + { theme: 'dark' }, + { feature: 'theme-switcher', scenarioId: scenario.id } + ); + scenario.complete(); + this.readout.set(`lab_feature_used, with scenarioId ${scenario.id}`); + } +} diff --git a/projects/watchtower-lab/src/shell/pages/fragment.page.ts b/projects/watchtower-lab/src/shell/pages/fragment.page.ts new file mode 100644 index 0000000..e619520 --- /dev/null +++ b/projects/watchtower-lab/src/shell/pages/fragment.page.ts @@ -0,0 +1,26 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; + +@Component({ + selector: 'lab-fragment-page', + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` +
+

Iframe

+

+ The embedded application in a plain iframe: its own page and its own + JavaScript realm, with the 'broadcast' sink. It hands everything to this + shell, which records it once. Its events show in the Event Inspector + marked ↪ Embedded application; signing in or out there applies here too. +

+ + @defer { + + } +
+ ` +}) +export class FragmentPage {} diff --git a/projects/watchtower-lab/src/shell/pages/identity.page.ts b/projects/watchtower-lab/src/shell/pages/identity.page.ts new file mode 100644 index 0000000..f6b74ef --- /dev/null +++ b/projects/watchtower-lab/src/shell/pages/identity.page.ts @@ -0,0 +1,71 @@ +import { + ChangeDetectionStrategy, + Component, + inject, + signal +} from '@angular/core'; +import { UwtTelemetrySink } from '@absaoss-cps/ngx-ui-watchtower'; +import { CpsButtonComponent } from 'cps-ui-kit'; +import { LabResultsComponent } from '../../lab/lab-results.component'; +import { ANNA, BEN } from '../../lab/lab-users'; + +@Component({ + selector: 'lab-identity-page', + imports: [CpsButtonComponent, LabResultsComponent], + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` +
+

Identity

+

+ The status bar shows the session and user. Signing out while signed in + starts a new session; signing out while signed out changes nothing. On + the wire, each event's metadata carries signedIn. +

+
+
+

Signing in and out

+
+ + + + +
+
+
+ +
{{ readout() || 'No change yet.' }}
+
+
+ ` +}) +export class IdentityPage { + protected readonly anna = ANNA; + protected readonly ben = BEN; + protected readonly readout = signal(''); + + private readonly sink = inject(UwtTelemetrySink); + + protected setUser(userId: string | undefined): void { + this.sink.setUserId(userId); + this.readout.set( + `setUserId(${userId === undefined ? 'undefined' : `'${userId}'`}): ` + + (userId + ? `events from now on carry ${userId === ANNA ? 'Anna' : 'Ben'}'s id.` + : 'signed out — the status bar shows any new session.') + ); + } +} diff --git a/projects/watchtower-lab/src/shell/pages/inspector.page.ts b/projects/watchtower-lab/src/shell/pages/inspector.page.ts new file mode 100644 index 0000000..f0a8285 --- /dev/null +++ b/projects/watchtower-lab/src/shell/pages/inspector.page.ts @@ -0,0 +1,176 @@ +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; +import { + UwtBITelemetryService, + UwtLoggerService, + UwtScenarioTelemetryService, + UwtTelemetryMonitor, + UwtTelemetryPublishInput +} from '@absaoss-cps/ngx-ui-watchtower'; +import { + UwtEventInspectorService, + UwtInspectorDock +} from '@absaoss-cps/ngx-ui-watchtower/event-inspector'; +import { + CpsButtonComponent, + CpsButtonToggleComponent, + CpsSwitchComponent +} from 'cps-ui-kit'; +import { LabLayout } from '../../lab/lab-layout.service'; +import { LAB_OPTIONS } from '../shell.config'; + +const DOCKS: { value: UwtInspectorDock; label: string }[] = [ + { value: 'dialog', label: 'Dialog' }, + { value: 'window', label: 'Separate window' }, + { value: 'left', label: 'Left pane' }, + { value: 'right', label: 'Right pane' }, + { value: 'bottom', label: 'Bottom pane' } +]; + +@Component({ + selector: 'lab-inspector-page', + imports: [CpsButtonComponent, CpsButtonToggleComponent, CpsSwitchComponent], + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` +
+

Event Inspector

+

+ Shows, live, what this app hands to its telemetry destinations, with + events to try it on below. + {{ + options.inspector === 'off' + ? 'Turned off in Settings (Event Inspector: off).' + : options.inspector === 'noshortcut' + ? 'No keyboard shortcut (Event Inspector: no shortcut, in Settings): open it from here.' + : 'Shortcut: Shift+E+I.' + }} +

+ +
+

Where it opens

+ +
+ + +
+ +
+ +
+

Events

+
+ + +
+
+ +
+

Synthetic events

+

+ Published straight to the telemetry monitor, so only the Event + Inspector sees them — shapes the library's own services never produce. +

+
+ + + +
+
+
+ ` +}) +export class InspectorPage { + protected readonly inspector = inject(UwtEventInspectorService); + protected readonly layout = inject(LabLayout); + protected readonly options = inject(LAB_OPTIONS); + protected readonly docks = DOCKS; + + private readonly bi = inject(UwtBITelemetryService); + private readonly scenarios = inject(UwtScenarioTelemetryService); + private readonly logger = inject(UwtLoggerService).getLogger('lab'); + private readonly monitor = inject(UwtTelemetryMonitor); + + protected eachKind(): void { + this.bi.track('lab_button_clicked', { + button: 'each-kind', + at: Date.now() + }); + const scenario = this.scenarios.start({ name: 'lab-happy-path' }); + scenario.step('fetch'); + scenario.complete(); + this.logger.warn('A warning, from the Event Inspector page'); + this.logger.error('An error, mirrored to RUM', { + error: new Error('Lab error') + }); + } + + protected burst(): void { + for (let n = 0; n < 600; n++) { + this.bi.track('lab_burst', { n }); + } + } + + protected huge(): void { + this.publish({ + kind: 'bi', + eventType: 'com.uwt.bi', + payload: { eventName: 'lab_huge_payload', blob: 'x'.repeat(300_000) } + }); + } + + protected odd(): void { + this.publish({ + kind: 'bi', + eventType: 'com.uwt.bi', + payload: { + eventName: 'lab_odd_shapes', + unicode: 'Zürich · 東京 · 🚀', + nothing: null, + nested: { deeper: { deepest: [1, [2, [3, { four: 4 }]]] } }, + list: [{ name: 'a' }, { name: 'b' }], + empty: {} + } + }); + } + + protected unknown(): void { + this.publish({ + kind: 'unknown', + eventType: 'com.example.custom', + payload: { what: 'an event type the library does not emit' } + }); + } + + private publish( + event: Pick & { + eventType: string; + payload: object; + } + ): void { + this.monitor.publish({ + ...event, + destination: 'sink', + origin: { forwarded: false } + } as UwtTelemetryPublishInput); + } +} diff --git a/projects/watchtower-lab/src/shell/pages/lifecycle.page.ts b/projects/watchtower-lab/src/shell/pages/lifecycle.page.ts new file mode 100644 index 0000000..902bc8d --- /dev/null +++ b/projects/watchtower-lab/src/shell/pages/lifecycle.page.ts @@ -0,0 +1,104 @@ +import { + ChangeDetectionStrategy, + Component, + inject, + signal +} from '@angular/core'; +import { + UwtScenarioTelemetryService, + UwtTelemetrySink +} from '@absaoss-cps/ngx-ui-watchtower'; +import { UwtRumTelemetrySink } from '@absaoss-cps/ngx-ui-watchtower/rum'; +import { CpsButtonComponent } from 'cps-ui-kit'; +import { LabResultsComponent } from '../../lab/lab-results.component'; + +@Component({ + selector: 'lab-lifecycle-page', + imports: [CpsButtonComponent, LabResultsComponent], + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` +
+

Lifecycle

+

+ What the sink does around the page's own life: sending what it holds on + request or when the tab hides, a scenario left running as the page goes, + and page views the router doesn't see. +

+
+
+

Flushing

+

+ Sends what the sink is holding now, rather than at its next batch. +

+
+ +
+
+
+

Leaving with a scenario running

+

+ Starts lab-abandoned, then reloads. As the page goes, the scenario + is settled as abandoned and sent by beacon: look for it On the wire. +

+
+ +
+
+
+

A page view of its own

+

+ The RUM sink's recordPageView, for a view the router doesn't see: + /lab/virtual-page. Only with the RUM destination. +

+
+ +
+
+
+

Hiding the tab

+

+ Switch to another tab, then come back: the sink flushes when the + page is hidden, so whatever was waiting shows up On the wire + straight away. +

+
+
+ +
{{ readout() || 'Nothing done yet.' }}
+
+
+ ` +}) +export class LifecyclePage { + private readonly sink = inject(UwtTelemetrySink); + private readonly scenarios = inject(UwtScenarioTelemetryService); + + protected readonly readout = signal(''); + protected readonly rum = + this.sink instanceof UwtRumTelemetrySink ? this.sink : undefined; + + protected flush(): void { + this.sink.flush(); + this.readout.set('Flushed: what the sink held is on its way.'); + } + + protected leave(): void { + this.scenarios.start({ name: 'lab-abandoned' }).step('fetch'); + location.reload(); + } + + protected pageView(): void { + this.rum?.recordPageView('/lab/virtual-page'); + this.readout.set('Recorded a page view of /lab/virtual-page.'); + } +} diff --git a/projects/watchtower-lab/src/shell/pages/logging.page.ts b/projects/watchtower-lab/src/shell/pages/logging.page.ts new file mode 100644 index 0000000..34cabb2 --- /dev/null +++ b/projects/watchtower-lab/src/shell/pages/logging.page.ts @@ -0,0 +1,128 @@ +import { + ChangeDetectionStrategy, + Component, + inject, + signal +} from '@angular/core'; +import { + UwtLoggerService, + UwtScenarioTelemetryService +} from '@absaoss-cps/ngx-ui-watchtower'; +import { CpsButtonComponent, CpsTextareaComponent } from 'cps-ui-kit'; +import { LabResultsComponent } from '../../lab/lab-results.component'; +import { parseJsonObject } from '../../lab/lab-json'; + +@Component({ + selector: 'lab-logging-page', + imports: [CpsButtonComponent, CpsTextareaComponent, LabResultsComponent], + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` +
+

Logging

+

+ Records go to the lab's mock log backend. Errors are also mirrored to + RUM (mirrorErrorsToRum). The noisy logger only writes + warnings and up. +

+
+
+

The lab logger

+
+ + + +
+
+
+

The noisy logger (warn and up)

+
+ + +
+
+
+

Tied to a journey

+

+ A warning whose correlationId is a scenario's id: the filter below + is set to find it. +

+
+ +
+
+
+

Reading back

+ + +
+ +
+
+
+ +
{{ readout() || 'No query yet.' }}
+
+
+ ` +}) +export class LoggingPage { + protected readonly readout = signal(''); + protected readonly filter = signal('{ "logger": "lab", "limit": 20 }'); + protected readonly filterError = signal(''); + + private readonly logger = inject(UwtLoggerService); + protected readonly lab = this.logger.getLogger('lab'); + protected readonly noisy = this.logger.getLogger('noisy'); + private readonly scenarios = inject(UwtScenarioTelemetryService); + + protected error(): void { + this.lab.error('Saving the order failed', { + error: new Error('Connection reset'), + context: 'checkout', + metadata: { orderId: 'A-1001' } + }); + } + + protected correlated(): void { + const scenario = this.scenarios.start({ name: 'lab-correlated' }); + this.lab.warn('Inside a journey', { correlationId: scenario.id }); + scenario.complete(); + this.filter.set(JSON.stringify({ correlationId: scenario.id })); + } + + /** Any filter the log provider takes: logger, correlationId, minLevel… */ + protected async runQuery(): Promise { + const filter = parseJsonObject(this.filter().trim() || '{}'); + this.filterError.set(filter ? '' : 'Not a JSON object.'); + if (filter) { + const records = await this.logger.query(filter); + this.readout.set(JSON.stringify(records, null, 2)); + } + } +} diff --git a/projects/watchtower-lab/src/shell/pages/privacy.page.ts b/projects/watchtower-lab/src/shell/pages/privacy.page.ts new file mode 100644 index 0000000..9e305d2 --- /dev/null +++ b/projects/watchtower-lab/src/shell/pages/privacy.page.ts @@ -0,0 +1,172 @@ +import { HttpErrorResponse } from '@angular/common/http'; +import { + ChangeDetectionStrategy, + Component, + computed, + inject, + signal +} from '@angular/core'; +import { + UWT_REDACT_CONFIG, + UwtBITelemetryService, + UwtLoggerService, + UwtScenarioTelemetryService, + uwtNormalizeError, + uwtRedactMetadata, + uwtScrubString +} from '@absaoss-cps/ngx-ui-watchtower'; +import { CpsButtonComponent, CpsTextareaComponent } from 'cps-ui-kit'; +import { LabResultsComponent } from '../../lab/lab-results.component'; + +/** Values that must never leave the browser as they are. */ +const PERSONAL = { + email: 'anna.smith@example.com', + note: 'card 4111 1111 1111 1111, phone +27 82 555 0123, SSN 123-45-6789', + ip: '10.1.2.3', + password: 'hunter2', + authToken: 'eyJhbGciOiJIUzI1NiJ9.lab' +}; + +@Component({ + selector: 'lab-privacy-page', + imports: [CpsButtonComponent, CpsTextareaComponent, LabResultsComponent], + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` +
+

Privacy

+

+ Each button sends the values below; with key patterns and + scanValuePatterns on, none of them should appear as they are — not in + the Event Inspector, not on the wire. +

+
+
+

Personal values

+
{{ personal }}
+
+ + + +
+
+ +
+

Errors of any shape

+

+ Logged as errors: a string is scrubbed, an object is reduced to its + type, and an HTTP error keeps its status but not its URL's query. +

+
+ + + +
+
+ +
+

Scrub anything

+

+ What the library makes of any text, with this app's redaction + settings: as a string, as metadata, and as an error's message. +

+ + +
{{ scrubbed() }}
+
+
+ +
{{ sent() || 'Nothing sent yet.' }}
+
+
+ ` +}) +export class PrivacyPage { + protected readonly personal = JSON.stringify(PERSONAL, null, 2); + protected readonly sent = signal(''); + + private readonly biTelemetry = inject(UwtBITelemetryService); + private readonly scenarios = inject(UwtScenarioTelemetryService); + private readonly logger = inject(UwtLoggerService).getLogger('privacy'); + private readonly redaction = inject(UWT_REDACT_CONFIG); + + protected readonly text = signal( + `Mail ${PERSONAL.email} from https://shop.example/orders?token=abc` + ); + + protected readonly scrubbed = computed(() => { + const text = this.text(); + return JSON.stringify( + { + uwtScrubString: uwtScrubString(text, this.redaction), + uwtRedactMetadata: uwtRedactMetadata( + { note: text, password: text }, + this.redaction + ), + uwtNormalizeError: uwtNormalizeError(new Error(text), { + ...this.redaction, + includeStack: false + }) + }, + null, + 2 + ); + }); + + protected bi(): void { + this.biTelemetry.track('lab_privacy_checked', PERSONAL); + this.sent.set('Sent as lab_privacy_checked.'); + } + + protected log(): void { + this.logger.warn(`Customer ${PERSONAL.email} paid with ${PERSONAL.note}`, { + metadata: { ip: PERSONAL.ip, password: PERSONAL.password } + }); + this.sent.set('Sent as a warning from the privacy logger.'); + } + + protected scenario(): void { + this.scenarios + .start({ name: 'lab-privacy', metadata: { email: PERSONAL.email } }) + .fail({ error: new Error(`Lookup failed for ${PERSONAL.email}`) }); + this.sent.set('Sent as the failed lab-privacy scenario.'); + } + + protected errorAs(shape: 'string' | 'object' | 'http'): void { + const error = { + string: `Lookup failed for ${PERSONAL.email}`, + object: { code: 42, customer: PERSONAL.email }, + http: new HttpErrorResponse({ + status: 404, + statusText: 'Not Found', + url: `https://shop.example/api/customers?email=${PERSONAL.email}` + }) + }[shape]; + this.logger.error(`The ${shape} error`, { error }); + this.sent.set(`Logged the ${shape} error.`); + } +} diff --git a/projects/watchtower-lab/src/shell/pages/scenarios.page.ts b/projects/watchtower-lab/src/shell/pages/scenarios.page.ts new file mode 100644 index 0000000..ef4dfb3 --- /dev/null +++ b/projects/watchtower-lab/src/shell/pages/scenarios.page.ts @@ -0,0 +1,388 @@ +import { HttpClient } from '@angular/common/http'; +import { + ChangeDetectionStrategy, + Component, + inject, + signal +} from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { Subject, switchMap } from 'rxjs'; +import { + traceScenario, + UwtScenario, + UwtScenarioTelemetryService +} from '@absaoss-cps/ngx-ui-watchtower'; +import { CpsButtonComponent } from 'cps-ui-kit'; +import { LabResultsComponent } from '../../lab/lab-results.component'; + +const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + +@Component({ + selector: 'lab-scenarios-page', + imports: [CpsButtonComponent, LabResultsComponent], + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` +
+

Scenarios

+

+ Each settled scenario's record appears in Results, as the sink receives + it. +

+
+
+

Settling

+
+ + + + + +
+
+ +
+

Timing

+
+ + +
+
+ +
+

HTTP, with traceScenario

+

+ Search cancels the search in flight (switchMap): click it several + times quickly, and only the last one completes; the others settle as + abandoned, with abandonedBy: caller. +

+
+ + + +
+
+ +
+

A running scenario

+
+ + + +
+
+ +
+

The rest of the scenario API

+
+ + + + +
+
+ +
+

Active scenarios

+
+ + + +
+
+ +
+

traceScenario's options

+
+ + + +
+
+
+ +
{{ readout() || 'Nothing settled yet.' }}
+
+
+ ` +}) +export class ScenariosPage { + protected readonly readout = signal(''); + protected readonly searches = new Subject(); + + private readonly scenarios = inject(UwtScenarioTelemetryService); + private readonly http = inject(HttpClient); + private running?: UwtScenario; + private long?: UwtScenario; + + constructor() { + this.scenarios.settled$ + .pipe(takeUntilDestroyed()) + .subscribe((record) => this.readout.set(JSON.stringify(record, null, 2))); + + this.searches + .pipe( + switchMap((delay) => + this.http.get('/api/data', { params: { delay } }).pipe( + traceScenario( + this.scenarios.start({ + name: 'lab-superseded', + metadata: { delay } + }) + ) + ) + ), + takeUntilDestroyed() + ) + .subscribe(); + } + + protected async happyPath(): Promise { + const scenario = this.scenarios.start({ name: 'lab-happy-path' }); + scenario.step('fetch'); + await wait(300); + scenario.step('validate'); + await wait(200); + scenario.step('render'); + await wait(100); + scenario.complete(); + } + + protected failure(): void { + const scenario = this.scenarios.start({ name: 'lab-failure' }); + scenario.step('fetch'); + scenario.fail({ error: new Error('The backend said no'), statusCode: 503 }); + } + + protected cancel(): void { + this.scenarios.start({ name: 'lab-cancel' }).cancel(); + } + + protected incomplete(): void { + this.scenarios.start({ name: 'lab-incomplete' }).incomplete(); + } + + protected timeout(): void { + this.scenarios + .start({ name: 'lab-timeout', timeoutMs: 1500 }) + .step('fetch'); + } + + protected backdated(): void { + this.scenarios + .start({ name: 'lab-backdated', startedAt: Date.now() - 1500 }) + .complete(); + } + + protected async aggregates(): Promise { + const scenario = this.scenarios.start({ name: 'lab-aggregates' }); + for (let batch = 0; batch < 5; batch++) { + scenario.aggregateStart('batch'); + await wait(40); + scenario.aggregateEnd('batch'); + } + scenario.complete(); + } + + protected request(): void { + const scenario = this.scenarios.start({ name: 'lab-http' }); + this.http + .get('/api/data', { params: { delay: 600 } }) + .pipe(traceScenario(scenario)) + .subscribe(); + } + + protected failingRequest(): void { + const scenario = this.scenarios.start({ name: 'lab-http-error' }); + this.http + .get('/api/data', { params: { status: 500 } }) + .pipe(traceScenario(scenario)) + .subscribe({ error: () => undefined }); + } + + protected startRunning(): void { + this.running = this.scenarios.start({ name: 'lab-happy-path' }); + this.running.step('fetch'); + this.showRunning(); + } + + protected showRunning(): void { + this.readout.set( + this.running + ? JSON.stringify(this.running.toRecord(), null, 2) + : 'Start a scenario first.' + ); + } + + protected completeRunning(): void { + this.running?.complete(); + this.running = undefined; + } + + protected detailed(): void { + const scenario = this.scenarios.start({ name: 'lab-detailed' }); + scenario.step('fetch', { rows: 3 }); + scenario.endStep({ message: 'Fetched' }); + scenario.step('validate'); + scenario.failStep(new Error('Row 2 is invalid'), { reason: 'validation' }); + scenario.setData({ orders: 2 }); + scenario.complete({ + statusCode: 200, + message: 'Saved', + reason: 'all-valid', + metadata: { source: 'lab' } + }); + } + + protected family(): void { + const parent = this.scenarios.start({ name: 'lab-parent' }); + this.scenarios + .start({ + name: 'lab-child', + parentScenarioId: parent.id, + feature: 'checkout', + operation: 'pay', + route: '/orders/:id' + }) + .complete(); + parent.complete(); + } + + protected settleDirectly(): void { + this.scenarios + .start({ name: 'lab-settled' }) + .settle('incomplete', { reason: 'no-results' }); + } + + /** One past the 50 the library warns about, in development. */ + protected crowd(): void { + const crowd = Array.from({ length: 51 }, () => + this.scenarios.start({ name: 'lab-crowd' }) + ); + crowd.forEach((scenario) => scenario.cancel()); + } + + /** Never times out: the one before it, if still running, is cancelled. */ + protected startLong(): void { + this.long?.cancel(); + this.long = this.scenarios.start({ name: 'lab-long', timeoutMs: 0 }); + this.lookUp(); + } + + protected lookUp(): void { + const id = this.long?.id; + this.readout.set( + JSON.stringify( + { + getActive: this.scenarios.getActive().map((s) => s.name), + findByName: this.scenarios.findByName('lab-long').map((s) => s.id), + find: id ? this.scenarios.find(id)?.name : undefined, + findByNameAndId: id + ? this.scenarios.findByNameAndId('lab-long', id)?.id + : undefined + }, + null, + 2 + ) + ); + } + + protected cancelActive(): void { + for (const scenario of this.scenarios.getActive()) { + scenario.cancel(); + } + this.lookUp(); + } + + protected outcomeFromResponse(): void { + this.http + .get<{ status: number }>('/api/data') + .pipe( + traceScenario(this.scenarios.start({ name: 'lab-trace-outcome' }), { + outcome: (response) => ({ + statusCode: response.status, + message: 'Data arrived' + }) + }) + ) + .subscribe(); + } + + protected unsubscribedEarly(): void { + const subscription = this.http + .get('/api/data', { params: { delay: 2000 } }) + .pipe( + traceScenario(this.scenarios.start({ name: 'lab-trace-cancel' }), { + cancelOutcome: { reason: 'navigated-away' } + }) + ) + .subscribe(); + setTimeout(() => subscription.unsubscribe(), 100); + } + + protected valueThenCancel(): void { + const values = new Subject(); + const subscription = values + .pipe( + traceScenario(this.scenarios.start({ name: 'lab-trace-teardown' }), { + valueThenTeardown: 'cancel' + }) + ) + .subscribe(); + values.next(1); + subscription.unsubscribe(); + } +} diff --git a/projects/watchtower-lab/src/shell/pages/settings-overview.page.ts b/projects/watchtower-lab/src/shell/pages/settings-overview.page.ts new file mode 100644 index 0000000..26b98a7 --- /dev/null +++ b/projects/watchtower-lab/src/shell/pages/settings-overview.page.ts @@ -0,0 +1,97 @@ +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; +import { RouterLink } from '@angular/router'; +import { CpsButtonComponent } from 'cps-ui-kit'; +import { LabSettingsDraft } from '../../lab/lab-settings-draft.service'; + +/** + * Settings' first page: what is waiting to be applied, and what this load + * runs with otherwise than the lab's defaults — each to undo or reset, or + * every one reset at once. + */ +@Component({ + selector: 'lab-settings-overview', + imports: [CpsButtonComponent, RouterLink], + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` +
+

Settings

+

+ What the next load runs with, by section: how the lab starts, the + library's settings and the mock backend's answers. Changes wait until + they are applied, all together, with a reload; the debug flags apply at + once. +

+ +
+

Not applied yet

+ @if (draft.changes().length) { + + } @else { +

Nothing: every section is as applied.

+ } +
+ +
+

Different from the lab's defaults

+ @if (draft.modified().length) { +

+ Reset puts one back, Reset all every one, to apply with the rest; a + debug flag goes back at once. +

+ +
+ +
+ } @else { +

Nothing: this load runs as the lab does by default.

+ } +
+
+ ` +}) +export class SettingsOverviewPage { + protected readonly draft = inject(LabSettingsDraft); +} diff --git a/projects/watchtower-lab/src/shell/pages/settings-section.page.ts b/projects/watchtower-lab/src/shell/pages/settings-section.page.ts new file mode 100644 index 0000000..72d7a69 --- /dev/null +++ b/projects/watchtower-lab/src/shell/pages/settings-section.page.ts @@ -0,0 +1,315 @@ +import { + afterNextRender, + ChangeDetectionStrategy, + Component, + ElementRef, + inject, + Injector, + signal +} from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { ActivatedRoute, RouterLink } from '@angular/router'; +import { + CpsInputComponent, + CpsSelectComponent, + CpsSwitchComponent, + CpsTextareaComponent +} from 'cps-ui-kit'; +import { LabLoggerLevelsComponent } from '../../lab/lab-logger-levels.component'; +import { LabSettingStateComponent } from '../../lab/lab-setting-state.component'; +import { + LabSettingsDraft, + labSettingId +} from '../../lab/lab-settings-draft.service'; +import { + LAB_SETTINGS_APPLIES, + LabField, + labFieldLabel, + labInOverlay, + LabSettingsSection +} from '../../lab/lab-settings-fields'; +import { + LabShortcut, + LabShortcutsComponent +} from '../../lab/lab-shortcuts.component'; +import { LAB_TELEMETRIES } from '../../lab/lab-wire.service'; + +/** + * One section of Settings: its controls, which write into the shared draft, + * each with what it does and where it stands — not applied yet, or other + * than the lab's default. The setting a search led to, named in the + * address's fragment, is shown and focused. + */ +@Component({ + selector: 'lab-settings-section', + imports: [ + CpsInputComponent, + CpsSelectComponent, + CpsSwitchComponent, + CpsTextareaComponent, + LabLoggerLevelsComponent, + LabSettingStateComponent, + LabShortcutsComponent, + RouterLink + ], + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` +
+
+

{{ section.label }}

+ @if (applies) { + {{ applies }} + } +
+

{{ section.lead }}

+ + @if (writesOverlay && !draft.overlay()) { +

+ These controls are off while the JSON in + Configuration as JSON + isn't an object. +

+ } + + @if (section.fields.length) { +
+
+ @for (field of section.fields; track id(field)) { +
+ @switch (field.kind) { + @case ('switch') { + +

{{ field.help }}

+ } + @case ('number') { + + } + @case ('text') { + + } + @case ('select') { + + } + @case ('multi') { + + } + @case ('lines') { + + + } + @case ('levels') { +

{{ label(field) }}

+

{{ field.help }}

+ + } + @case ('shortcuts') { +

{{ label(field) }}

+

{{ field.help }}

+ + } + @case ('telemetries') { + +

{{ field.help }}

+ @if (draft.telemetries(); as chosen) { + + } + } + } + +
+ } +
+ @if (section.path === 'app-monitor') { + + + } +
+ } + + @switch (section.path) { + @case ('json') { +
+ + +
+ } + @case ('effective') { +
+
{{ draft.effective }}
+
+ } + } +
+ ` +}) +export class SettingsSectionPage { + protected readonly draft = inject(LabSettingsDraft); + private readonly route = inject(ActivatedRoute); + private readonly host = inject>(ElementRef); + private readonly injector = inject(Injector); + + protected readonly section: LabSettingsSection = + this.route.snapshot.data.section; + + protected readonly applies = + this.section.applies && LAB_SETTINGS_APPLIES[this.section.applies]; + + /** Whether its controls write into the overlay, and so wait for its JSON. */ + protected readonly writesOverlay = this.section.fields.some(labInOverlay); + + /** The setting a search led to, by its id. */ + protected readonly found = signal(null); + + protected readonly id = labSettingId; + protected readonly label = labFieldLabel; + protected readonly telemetryOptions = LAB_TELEMETRIES.map((t) => ({ + label: t, + value: t + })); + + constructor() { + this.route.fragment.pipe(takeUntilDestroyed()).subscribe((id) => { + this.found.set(id); + if (id) { + afterNextRender(() => this.reveal(id), { injector: this.injector }); + } + }); + } + + protected selectOptions(field: LabField): { label: string; value: string }[] { + return field.kind === 'select' || field.kind === 'multi' + ? field.options + : []; + } + + /** + * A list setting as text, for a control: what the draft has, or what is in + * effect, where a pattern is a `RegExp` and a transform a function. + */ + protected list(value: unknown): string[] { + return (Array.isArray(value) ? value : []).map((item) => + item instanceof RegExp + ? item.source + : typeof item === 'function' + ? item.name + : String(item) + ); + } + + protected levelsOf(value: unknown): Record { + return value && typeof value === 'object' && !Array.isArray(value) + ? (value as Record) + : {}; + } + + protected shortcutsOf(value: unknown): LabShortcut[] { + return Array.isArray(value) ? (value as LabShortcut[]) : []; + } + + /** Brings a setting into view, and its control into focus. */ + private reveal(id: string): void { + const field = this.host.nativeElement.ownerDocument.getElementById( + `setting-${id}` + ); + field?.scrollIntoView({ block: 'center' }); + field + ?.querySelector('input, textarea, [role="combobox"], button') + ?.focus({ preventScroll: true }); + } +} diff --git a/projects/watchtower-lab/src/shell/pages/settings.page.ts b/projects/watchtower-lab/src/shell/pages/settings.page.ts new file mode 100644 index 0000000..1772cfb --- /dev/null +++ b/projects/watchtower-lab/src/shell/pages/settings.page.ts @@ -0,0 +1,324 @@ +import { + ChangeDetectionStrategy, + Component, + computed, + ElementRef, + inject, + signal, + viewChild +} from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { + NavigationEnd, + Router, + RouterLink, + RouterLinkActive, + RouterOutlet +} from '@angular/router'; +import { + CpsAutocompleteComponent, + CpsButtonComponent, + CpsSelectComponent +} from 'cps-ui-kit'; +import { filter } from 'rxjs'; +import { + LabSettingsDraft, + labSettingId +} from '../../lab/lab-settings-draft.service'; +import { + LAB_SETTINGS_AREAS, + LAB_SETTINGS_SECTIONS, + labSettingName +} from '../../lab/lab-settings-fields'; + +/** The picker's value for the overview, which has no path of its own. */ +const OVERVIEW = 'overview'; + +/** A section, or a setting in one, as the search finds it. */ +interface LabSearchResult { + label: string; + /** Where it is, under its label. */ + info: string; + /** What else it is found by: its name in the JSON, section and help. */ + alias: string; + path: string; + /** The setting's id, to show it; none for a section. */ + id?: string; +} + +/** Each section, then its settings, as the search lists them. */ +const SEARCHABLE: LabSearchResult[] = LAB_SETTINGS_SECTIONS.flatMap( + (section) => [ + { + label: section.label, + info: `Section · ${section.area}`, + alias: `${section.area} ${section.lead}`, + path: section.path + }, + ...section.fields.map((field) => { + const name = labSettingName(field); + return { + label: field.label, + info: name ? `${section.label} · ${name}` : section.label, + alias: `${name} ${section.label} ${field.help}`, + path: section.path, + id: labSettingId(field) + }; + }) + ] +); + +/** The section an address is in: `logging` for `/settings/logging?…`. */ +function sectionOf(url: string): string { + return url.split(/[?#]/)[0].split('/')[2] || OVERVIEW; +} + +/** + * Settings: a list of its sections beside the one shown — a picker, when + * there's no room for the list — and, while there are any, the changes not + * applied yet, from every section, in a bar that applies them together. + */ +@Component({ + selector: 'lab-settings-page', + imports: [ + CpsAutocompleteComponent, + CpsButtonComponent, + CpsSelectComponent, + RouterLink, + RouterLinkActive, + RouterOutlet + ], + providers: [LabSettingsDraft], + changeDetection: ChangeDetectionStrategy.OnPush, + host: { '(document:keydown)': 'focusSearch($event)' }, + template: ` +
+ + +
+ +
+ +
+ + + + + + @if (draft.changes(); as changes) { + @if (changes.length) { +
+

+ + {{ changes.length }} + {{ changes.length === 1 ? 'change' : 'changes' }} not + applied + {{ listed() }} +

+
+ @if (current() !== overview) { + Review + } + + +
+ @for (problem of draft.problems(); track problem.section.path) { + + } +
+ } + } +
+
+ ` +}) +export class SettingsPage { + protected readonly draft = inject(LabSettingsDraft); + private readonly router = inject(Router); + private readonly host = inject>(ElementRef); + private readonly search = viewChild.required(CpsAutocompleteComponent); + + protected readonly overview = OVERVIEW; + protected readonly searchable = SEARCHABLE; + protected readonly areas = LAB_SETTINGS_AREAS.map((name) => ({ + name, + id: `lab-settings-${name.toLowerCase().replace(/\W+/g, '-')}`, + sections: LAB_SETTINGS_SECTIONS.filter((section) => section.area === name) + })); + + protected readonly picks = [ + { label: 'Overview', value: OVERVIEW }, + ...LAB_SETTINGS_SECTIONS.map((section) => ({ + label: `${section.area} › ${section.label}`, + value: section.path + })) + ]; + + protected readonly current = signal(sectionOf(this.router.url)); + /** The sections with changes not applied. */ + protected readonly changed = computed( + () => new Set(this.draft.changes().map((change) => change.section.path)) + ); + + /** How many settings each section has other than the lab's defaults. */ + protected readonly counts = computed(() => { + const counts = new Map(); + for (const { section } of this.draft.modified()) { + counts.set(section.path, (counts.get(section.path) ?? 0) + 1); + } + return counts; + }); + + /** Each section's marks, as read out: `2 modified, changes not applied`. */ + protected readonly notes = computed( + () => + new Map( + LAB_SETTINGS_SECTIONS.map((section) => { + const count = this.counts().get(section.path); + const notes = [ + count && `${count} modified`, + this.changed().has(section.path) && 'changes not applied' + ].filter(Boolean); + return [section.path, notes.join(', ')]; + }) + ) + ); + + protected readonly listed = computed(() => + this.draft + .changes() + .map((change) => `${change.section.label} › ${change.label}`) + .join(', ') + ); + + constructor() { + this.router.events + .pipe( + filter((event) => event instanceof NavigationEnd), + takeUntilDestroyed() + ) + .subscribe(() => this.current.set(sectionOf(this.router.url))); + } + + protected async go(section: string): Promise { + await this.router.navigate( + ['/settings', ...(section === OVERVIEW ? [] : [section])], + { queryParamsHandling: 'preserve' } + ); + } + + /** Goes to what the search found: its section, and the setting, shown there. */ + protected async find(result: LabSearchResult | undefined): Promise { + if (!result) { + return; + } + // Empty again for the next search, which may find the same one. + this.search().writeValue(undefined); + await this.router.navigate(['/settings', result.path], { + fragment: result.id, + queryParamsHandling: 'preserve' + }); + } + + /** `/`, outside a field: to the search, as on many sites. */ + protected focusSearch(event: KeyboardEvent): void { + const target = event.target instanceof Element ? event.target : null; + if ( + event.key !== '/' || + event.ctrlKey || + event.metaKey || + event.altKey || + target?.closest('input, textarea, select, [contenteditable]') + ) { + return; + } + event.preventDefault(); + this.host.nativeElement + .querySelector('.lab-settings__search input') + ?.focus(); + } +} diff --git a/projects/watchtower-lab/src/shell/pages/settings.routes.ts b/projects/watchtower-lab/src/shell/pages/settings.routes.ts new file mode 100644 index 0000000..af282f2 --- /dev/null +++ b/projects/watchtower-lab/src/shell/pages/settings.routes.ts @@ -0,0 +1,26 @@ +import { Routes } from '@angular/router'; +import { LAB_SETTINGS_SECTIONS } from '../../lab/lab-settings-fields'; +import { SettingsOverviewPage } from './settings-overview.page'; +import { SettingsSectionPage } from './settings-section.page'; +import { SettingsPage } from './settings.page'; + +/** Settings: its overview, and a page for each section, around what they share. */ +export const SETTINGS_ROUTES: Routes = [ + { + path: '', + component: SettingsPage, + children: [ + { + path: '', + title: 'Settings · Watchtower Lab', + component: SettingsOverviewPage + }, + ...LAB_SETTINGS_SECTIONS.map((section) => ({ + path: section.path, + title: `${section.label} · Settings · Watchtower Lab`, + component: SettingsSectionPage, + data: { section } + })) + ] + } +]; diff --git a/projects/watchtower-lab/src/shell/pages/web-fragment.page.ts b/projects/watchtower-lab/src/shell/pages/web-fragment.page.ts new file mode 100644 index 0000000..42a3679 --- /dev/null +++ b/projects/watchtower-lab/src/shell/pages/web-fragment.page.ts @@ -0,0 +1,27 @@ +import { + ChangeDetectionStrategy, + Component, + CUSTOM_ELEMENTS_SCHEMA +} from '@angular/core'; + +@Component({ + selector: 'lab-web-fragment-page', + changeDetection: ChangeDetectionStrategy.OnPush, + schemas: [CUSTOM_ELEMENTS_SCHEMA], + template: ` +
+

Web Fragment

+

+ The embedded application as a Web Fragment. Unlike in an iframe, its + page is part of this page's DOM: it is drawn inside this one, in a + shadow root, as ordinary content of the page, not as a framed document + of its own. Its JavaScript still runs in a hidden iframe, for a clean + context. The gateway in the lab's server routes its files. It forwards + to this shell over this tab's channel, so its events show in the Event + Inspector marked ↪ Embedded application. +

+ +
+ ` +}) +export class WebFragmentPage {} diff --git a/projects/watchtower-lab/src/shell/pages/wire.page.ts b/projects/watchtower-lab/src/shell/pages/wire.page.ts new file mode 100644 index 0000000..99e2974 --- /dev/null +++ b/projects/watchtower-lab/src/shell/pages/wire.page.ts @@ -0,0 +1,170 @@ +import { + ChangeDetectionStrategy, + Component, + computed, + DOCUMENT, + inject, + signal +} from '@angular/core'; +import { UwtTelemetrySink } from '@absaoss-cps/ngx-ui-watchtower'; +import { CpsButtonComponent, CpsSwitchComponent } from 'cps-ui-kit'; +import { LabRumEvent, LabWire } from '../../lab/lab-wire.service'; + +@Component({ + selector: 'lab-wire-page', + imports: [CpsButtonComponent, CpsSwitchComponent], + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` +
+

On the wire

+

+ What the lab's mock backend actually received — the other side of what + the app, and the Event Inspector, say was sent. Newest first. The mock + RUM broker is set up in Settings. +

+ +
+

The mock backend

+
+ + + {{ status() }} +
+
+ +
+
+

RUM events ({{ rum().length }})

+ +
+
+ + + + + + + + + + + + @for (event of rum(); track event.id) { + + + + + + + + } + +
ReceivedTypeSession · usersignedInDetails
{{ event.receivedAt.slice(11, 19) }} + {{ event.type }} + + {{ short(event.user.sessionId) }} · + {{ short(event.user.userId) }} + {{ event.metadata['signedIn'] ?? '' }} +
+ {{ summary(event) }} +
{{ json(event) }}
+
+
+
+
+ +
+

Log records ({{ logs().length }})

+
+ + + + + + + + + + + @for (record of logs(); track $index) { + + + + + + + } + +
TimeLevelApplication · loggerMessage
{{ record.timestamp.slice(11, 19) }}{{ record.level }}{{ record.application }} · {{ record.logger }}{{ record.message }}
+
+
+
+ ` +}) +export class WirePage { + protected readonly wire = inject(LabWire); + protected readonly libraryOnly = signal(true); + protected readonly status = signal(''); + + private readonly sink = inject(UwtTelemetrySink); + private readonly window = inject(DOCUMENT).defaultView!; + + protected readonly rum = computed(() => + this.wire + .rum() + .filter((e) => !this.libraryOnly() || e.type.startsWith('com.uwt.')) + .slice() + .reverse() + ); + + protected readonly logs = computed(() => this.wire.logs().slice().reverse()); + + protected flush(): void { + this.sink.flush(); + this.status.set('Flushed: what the sink held is on its way.'); + } + + /** Empties the mock backend, once confirmed: there's no undoing it. */ + protected async reset(): Promise { + if ( + !this.window.confirm( + 'Empty the mock backend? Everything it received is lost.' + ) + ) { + return; + } + await this.wire.reset(); + this.status.set('Reset: the mock backend is empty.'); + } + + protected short(id?: string): string { + return id ? id.slice(0, 8) : '—'; + } + + protected summary(event: LabRumEvent): string { + const d = event.details; + return String(d.eventName ?? d.scenarioName ?? d.message ?? d.name ?? '…'); + } + + protected json(event: LabRumEvent): string { + return JSON.stringify( + { metadata: event.metadata, details: event.details }, + null, + 2 + ); + } +} diff --git a/projects/watchtower-lab/src/shell/shell.component.ts b/projects/watchtower-lab/src/shell/shell.component.ts new file mode 100644 index 0000000..418b6c3 --- /dev/null +++ b/projects/watchtower-lab/src/shell/shell.component.ts @@ -0,0 +1,344 @@ +import { NgTemplateOutlet } from '@angular/common'; +import { + afterNextRender, + ChangeDetectionStrategy, + Component, + computed, + DestroyRef, + DOCUMENT, + ElementRef, + inject, + signal, + viewChild +} from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { + NavigationEnd, + Router, + RouterLink, + RouterLinkActive, + RouterOutlet +} from '@angular/router'; +import { CpsIconComponent, CpsTooltipDirective } from 'cps-ui-kit'; +import { filter } from 'rxjs'; +import { UwtTelemetrySink } from '@absaoss-cps/ngx-ui-watchtower'; +import { UwtEventInspectorService } from '@absaoss-cps/ngx-ui-watchtower/event-inspector'; +import { LabLayout } from '../lab/lab-layout.service'; +import { LabRumCredentialsProvider } from '../lab/lab-rum-credentials.provider'; +import { LAB_USER_NAMES } from '../lab/lab-users'; +import { labTelemetriesLabel, LabWire } from '../lab/lab-wire.service'; +import { + LAB_OPTIONS, + SHELL_GROUPS, + SHELL_PAGES, + SHELL_SETTINGS +} from './shell.config'; + +/** + * The broker's setting as this page load got it, and the next load's when + * that differs: `ok → null after reload`. + */ +function inEffect(now?: string, next?: string): string { + if (now === undefined) { + return next ?? '…'; + } + return next === undefined || next === now + ? now + : `${now} → ${next} after reload`; +} + +/** The lab's frame: a fixed header, the pages, and a fixed status bar. */ +@Component({ + selector: 'lab-root', + imports: [ + CpsIconComponent, + CpsTooltipDirective, + NgTemplateOutlet, + RouterLink, + RouterLinkActive, + RouterOutlet + ], + changeDetection: ChangeDetectionStrategy.OnPush, + // On the document: Safari doesn't focus a button it clicks, so Escape + // after opening the nav with the mouse wouldn't come from inside it. + host: { '(document:keydown.escape)': 'closeNav()' }, + template: ` + Skip to content +
+ + Watchtower Lab + +
+ +
+
+ Run settings +
+ +
+
+ + {{ identity().userId ? 'Signed in' : 'Signed out' }} + +
+ + + + destination: {{ options.destination }} + telemetry: {{ options.telemetry ? 'on' : 'off' }} + broker: {{ broker() }} + rum telemetries: {{ telemetries() }} + @if (options.channel === 'shared') { + channel: shared + } + @if (customConfig) { + config: custom + } + + +
+
+ +
+ +
+
+
+ +
+ RUM events: {{ wire.rum().length }} + from the library: {{ wire.libraryEvents() }} + batches: {{ wire.batches() }} + logs: {{ wire.logs().length }} + session: {{ identity().sessionId ?? '—' }} + user: {{ identity().userId ?? '—' }} +
+ ` +}) +export class ShellComponent { + protected readonly options = inject(LAB_OPTIONS); + protected readonly customConfig = Object.keys(this.options.config).length > 0; + protected readonly wire = inject(LabWire); + protected readonly inspector = inject(UwtEventInspectorService); + protected readonly identity = signal<{ sessionId?: string; userId?: string }>( + {} + ); + + protected readonly groups = SHELL_GROUPS.map((name) => ({ + name, + pages: SHELL_PAGES.filter((page) => page.group === name) + })); + + /** Who is signed in: their name, when the lab knows it, and their id. */ + protected readonly userTitle = computed(() => { + const id = this.identity().userId; + if (!id) { + return 'Nobody is signed in'; + } + const name = LAB_USER_NAMES[id]; + return `Signed in as ${name ? `${name} (${id})` : id}`; + }); + + protected readonly settings = SHELL_SETTINGS; + + /** Whether the folded nav is open, on a narrow screen. */ + protected readonly navOpen = signal(false); + private readonly path = signal(''); + /** The page shown: Settings for any of its sections. */ + protected readonly current = computed( + () => + [...SHELL_PAGES, SHELL_SETTINGS].find( + (page) => page.path === this.path().split('/')[0] + )?.label ?? '' + ); + + private readonly credentials = inject(LabRumCredentialsProvider); + protected readonly broker = computed(() => + inEffect( + this.credentials.startup()?.mode, + this.wire.polled() ? this.wire.brokerMode() : undefined + ) + ); + + protected readonly telemetries = computed(() => { + const startup = this.credentials.startup(); + return inEffect( + startup && labTelemetriesLabel(startup.telemetries), + this.wire.polled() ? this.wire.telemetriesLabel() : undefined + ); + }); + + private readonly sink = inject(UwtTelemetrySink); + private readonly document = inject(DOCUMENT); + private readonly navToggle = + viewChild.required>('navToggle'); + + private readonly runSettings = + viewChild>('runSettings'); + + constructor() { + inject(LabLayout); + const router = inject(Router); + router.events + .pipe( + filter((event) => event instanceof NavigationEnd), + takeUntilDestroyed() + ) + .subscribe(() => { + this.path.set(router.url.split(/[?#]/)[0].replace(/^\//, '')); + this.navOpen.set(false); + // Its chips lead into Settings: it folds away once one is followed. + this.runSettings()?.nativeElement.removeAttribute('open'); + }); + + const destroyRef = inject(DestroyRef); + // In the browser only, like the wire's polling. + afterNextRender(() => { + // Hydrated: the page now answers clicks. The suite waits for this. + this.document.documentElement.setAttribute('data-lab-ready', ''); + const timer = setInterval( + () => + this.identity.set({ + sessionId: this.sink.getSessionId(), + userId: this.sink.getUserId() + }), + 1000 + ); + destroyRef.onDestroy(() => clearInterval(timer)); + }); + } + + /** Focuses the page's content; handled here, so the router stays out. */ + protected skipToContent(event: Event): void { + event.preventDefault(); + this.document.getElementById('lab-main')?.focus(); + } + + /** Escape folds the open nav, back to its button. */ + protected closeNav(): void { + if (this.navOpen()) { + // Focus goes back to the toggle, unless it is in a field elsewhere: + // Escape there is that field's. + const toggle = this.navToggle().nativeElement as HTMLElement; + const focused = toggle.ownerDocument.activeElement; + const elsewhere = + !!focused && + focused !== toggle.ownerDocument.body && + !focused.closest('.lab-nav'); + this.navOpen.set(false); + if (!elsewhere) { + toggle.focus(); + } + } + } +} diff --git a/projects/watchtower-lab/src/shell/shell.config.server.ts b/projects/watchtower-lab/src/shell/shell.config.server.ts new file mode 100644 index 0000000..1aac8b1 --- /dev/null +++ b/projects/watchtower-lab/src/shell/shell.config.server.ts @@ -0,0 +1,31 @@ +import { mergeApplicationConfig } from '@angular/core'; +import { + provideServerRendering, + RenderMode, + ServerRoute, + withRoutes +} from '@angular/ssr'; +import { labOptions } from '../lab/lab-options'; +import { shellConfig } from './shell.config'; + +/** Every page is rendered on the server, for each request — but one. */ +const serverRoutes: ServerRoute[] = [ + // cps-ui-kit's cps-select creates a ResizeObserver in its constructor, + // which a server doesn't have: "On the wire" renders in the browser. + { path: 'wire', renderMode: RenderMode.Client }, + // Its effective settings are the browser's, from the query string. + { path: 'settings', renderMode: RenderMode.Client }, + { path: 'settings/**', renderMode: RenderMode.Client }, + { path: '**', renderMode: RenderMode.Server } +]; + +/** + * The shell on the server, with the default options: a query string's take + * effect once the browser takes over. + */ +export const serverConfig = mergeApplicationConfig( + shellConfig(labOptions('')), + { + providers: [provideServerRendering(withRoutes(serverRoutes))] + } +); diff --git a/projects/watchtower-lab/src/shell/shell.config.ts b/projects/watchtower-lab/src/shell/shell.config.ts new file mode 100644 index 0000000..4d79d7e --- /dev/null +++ b/projects/watchtower-lab/src/shell/shell.config.ts @@ -0,0 +1,186 @@ +import { provideHttpClient, withFetch } from '@angular/common/http'; +import { + ApplicationConfig, + inject, + provideZoneChangeDetection +} from '@angular/core'; +import { + provideClientHydration, + withEventReplay +} from '@angular/platform-browser'; +import { provideAnimations } from '@angular/platform-browser/animations'; +import { provideRouter, Route, Routes } from '@angular/router'; +import { + UWT_LOG_API_PROVIDER, + provideUwtTelemetry, + provideUwtTelemetryBroadcastHost, + provideUwtTelemetryDestination, + provideUwtTelemetrySink, + withBIEvents, + withLogging, + withRedaction, + withScenarios, + withTelemetryEnabled +} from '@absaoss-cps/ngx-ui-watchtower'; +import { + UWT_RUM_CREDENTIALS_PROVIDER, + provideUwtTelemetryRumSink +} from '@absaoss-cps/ngx-ui-watchtower/rum'; +import { provideUwtEventInspector } from '@absaoss-cps/ngx-ui-watchtower/event-inspector'; +import { + LAB_DEFAULTS, + LAB_OPTIONS, + labRedaction, + LabOptions +} from '../lab/lab-options'; +import { labChannel } from '../lab/lab-channel'; +import { LabCustomSink } from '../lab/lab-custom.sink'; +import { LabLogApiProvider } from '../lab/lab-log-api.provider'; +import { LabRumCredentialsProvider } from '../lab/lab-rum-credentials.provider'; + +export { LAB_OPTIONS }; + +/** The side nav's groups, in order. */ +export const SHELL_GROUPS = ['Library', 'Embedded apps', 'Tools'] as const; + +export const SHELL_PAGES: { + path: string; + group: (typeof SHELL_GROUPS)[number]; + label: string; + load: NonNullable; +}[] = [ + { + path: 'scenarios', + group: 'Library', + label: 'Scenarios', + load: () => import('./pages/scenarios.page').then((m) => m.ScenariosPage) + }, + { + path: 'bi', + group: 'Library', + label: 'BI events', + load: () => import('./pages/bi.page').then((m) => m.BIPage) + }, + { + path: 'logging', + group: 'Library', + label: 'Logging', + load: () => import('./pages/logging.page').then((m) => m.LoggingPage) + }, + { + path: 'identity', + group: 'Library', + label: 'Identity', + load: () => import('./pages/identity.page').then((m) => m.IdentityPage) + }, + { + path: 'privacy', + group: 'Library', + label: 'Privacy', + load: () => import('./pages/privacy.page').then((m) => m.PrivacyPage) + }, + { + path: 'lifecycle', + group: 'Library', + label: 'Lifecycle', + load: () => import('./pages/lifecycle.page').then((m) => m.LifecyclePage) + }, + { + path: 'iframe', + group: 'Embedded apps', + label: 'Iframe', + load: () => import('./pages/fragment.page').then((m) => m.FragmentPage) + }, + { + path: 'web-fragment', + group: 'Embedded apps', + label: 'Web Fragment', + load: () => + import('./pages/web-fragment.page').then((m) => m.WebFragmentPage) + }, + { + path: 'inspector', + group: 'Tools', + label: 'Event Inspector', + load: () => import('./pages/inspector.page').then((m) => m.InspectorPage) + }, + { + path: 'wire', + group: 'Tools', + label: 'On the wire', + load: () => import('./pages/wire.page').then((m) => m.WirePage) + } +]; + +/** + * Settings, at the foot of the nav, apart from the pages: it sets up the run + * they all share, rather than being one of them. A page for each section. + */ +export const SHELL_SETTINGS = { path: 'settings', label: 'Settings' }; + +const routes: Routes = [ + ...SHELL_PAGES.map(({ path, label, load }) => ({ + path, + title: `${label} · Watchtower Lab`, + loadComponent: load + })), + { + path: SHELL_SETTINGS.path, + loadChildren: () => + import('./pages/settings.routes').then((m) => m.SETTINGS_ROUTES) + }, + { path: '**', redirectTo: 'scenarios' } +]; + +/** + * The shell: the realm with the real destination. RUM against the lab's + * mock dataplane (or noop, or the lab's own sink), logs to its mock backend, the broadcast host for + * the fragment, and the Event Inspector. A `?config=` overlay replaces the + * lab's own settings, one by one. + */ +export const shellConfig = (options: LabOptions): ApplicationConfig => { + const { identity, scenarios, logging, bi, inspector } = options.config; + + return { + providers: [ + provideZoneChangeDetection({ eventCoalescing: true }), + provideClientHydration(withEventReplay()), + provideAnimations(), + provideHttpClient(withFetch()), + provideRouter(routes), + { provide: LAB_OPTIONS, useValue: options }, + provideUwtTelemetry( + { ...LAB_DEFAULTS.identity, ...identity }, + // The factory form, read once at startup in an injection context. + withTelemetryEnabled(() => inject(LAB_OPTIONS).telemetry), + withLogging({ ...LAB_DEFAULTS.logging, ...logging }), + withScenarios({ ...LAB_DEFAULTS.scenarios, ...scenarios }), + withBIEvents(bi), + withRedaction({ + ...LAB_DEFAULTS.redaction, + ...labRedaction(options.config.redaction) + }) + ), + options.destination === 'noop' + ? provideUwtTelemetrySink('noop') + : options.destination === 'custom' + ? provideUwtTelemetryDestination(LabCustomSink, { + init: (sink) => sink.init() + }) + : provideUwtTelemetryRumSink(), + { + provide: UWT_RUM_CREDENTIALS_PROVIDER, + useExisting: LabRumCredentialsProvider + }, + { provide: UWT_LOG_API_PROVIDER, useExisting: LabLogApiProvider }, + provideUwtTelemetryBroadcastHost( + labChannel(options.channel === 'shared') + ), + provideUwtEventInspector({ + enabled: options.inspector !== 'off', + ...(options.inspector === 'noshortcut' && { shortcuts: [] }), + ...inspector + }) + ] + }; +}; diff --git a/projects/watchtower-lab/src/styles.scss b/projects/watchtower-lab/src/styles.scss new file mode 100644 index 0000000..d111834 --- /dev/null +++ b/projects/watchtower-lab/src/styles.scss @@ -0,0 +1,969 @@ +// The shell's own layout, on top of what it shares with the fragment. +@use 'lab-base'; +@use 'cps-ui-kit/styles/mixins' as cps; + +// Hidden until focused, then shown over the header. +.lab-skip-link { + position: fixed; + top: 0.5rem; + left: 0.5rem; + z-index: 20; + padding: 0.5rem 0.75rem; + border-radius: 0.375rem; + background: #fff; + color: var(--cps-color-calm); + font-weight: 600; + transform: translateY(-200%); + + &:focus { + transform: none; + } + + &:focus-visible { + @include cps.focus-ring; + // The mixin makes it relative; it must stay fixed to the window. + position: fixed; + } +} + +// The embedded application's box, the same whether it is embedded in an +// iframe or as a Web Fragment: a fixed size, scrolling inside, on white. +.lab-embed { + display: block; + width: 100%; + height: 24rem; + overflow: auto; + border: 0.0625rem dashed var(--cps-color-line-mid); + border-radius: 0.5rem; + background-color: #fff; +} + +// A card's controls side by side where there is room, one under another where there isn't. +.lab-fields { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(min(26rem, 100%), 1fr)); + gap: 0.75rem 1.5rem; + align-items: start; +} + +// A control, what it does, and where it stands: not applied, or modified. +.lab-field { + display: flex; + flex-direction: column; + gap: 0.25rem; + min-width: 0; + border-radius: 0.25rem; +} + +// What a switch or an editor does: as the kit shows a field's hint. Past a +// card's paragraphs, which are larger. +.lab-field .lab-field__help { + max-width: 40rem; + margin: 0; + color: var(--cps-text-secondary); + font-size: 0.75rem; +} + +// The setting a search led to, picked out for a moment. +.lab-field--found { + animation: lab-found 2.5s ease-out; +} + +@keyframes lab-found { + from { + background-color: rgb(135 10 60 / 10%); + box-shadow: 0 0 0 0.5rem rgb(135 10 60 / 10%); + } + + to { + background-color: transparent; + box-shadow: 0 0 0 0.5rem transparent; + } +} + +// Without the motion, it stays outlined. +@media (prefers-reduced-motion: reduce) { + .lab-field--found { + animation: none; + outline: 0.125rem solid var(--cps-color-calm); + outline-offset: 0.375rem; + } +} + +// A control that is a list, or has several parts, takes the card's whole width. +.lab-field-wide { + grid-column: 1 / -1; +} + +.lab-setting-state { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.25rem 0.5rem; + font-size: 0.8125rem; + + &:empty { + display: none; + } +} + +.lab-setting-state__tag { + padding: 0 0.5rem; + border-radius: 0.625rem; + background-color: var(--cps-color-highlight-selected); + color: var(--cps-color-calm); + font-weight: 700; +} + +.lab-setting-state__tag--pending { + background-color: #fdf0dc; + color: #7a4300; +} + +.lab-setting-state__default { + min-width: 0; + overflow: hidden; + color: var(--cps-text-secondary); + text-overflow: ellipsis; + white-space: nowrap; +} + +.lab-field-wide__label { + margin: 0 0 0.375rem; + font-size: 0.875rem; +} + +.lab-rows { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.lab-row { + display: flex; + flex-wrap: wrap; + gap: 0.75rem 1.5rem; + align-items: center; +} + +// With a label above each control: the name and the button sit by the boxes. +.lab-row--labelled { + align-items: flex-start; + + .lab-row__name { + margin-top: 1.9rem; + } + + > cps-button { + margin-top: 1.5rem; + } +} + +.lab-row__name { + min-width: 12rem; + font-family: ui-monospace, Menlo, Consolas, monospace; + font-size: 0.875rem; +} + +.lab-row__none { + margin: 0; + color: var(--cps-text-secondary); +} + +// Settings: its sections listed beside the one shown, or picked above it +// where the list has no room. +.lab-settings { + display: grid; + grid-template-columns: 14rem minmax(0, 1fr); + gap: 2rem; + align-items: start; +} + +// Its results are as wide as it is, each name with where it is beside it. +.lab-settings__search { + display: block; + max-width: 40rem; +} + +// In view as the section scrolls, below the header, scrolled on its own if +// the window is short. +.lab-settings__nav { + position: sticky; + top: 5rem; + max-height: calc(100vh - 8.75rem - var(--uwt-inspector-inset-bottom, 0px)); + overflow-y: auto; + + // The dot at the row's end; past the shared rows' rule, which comes later. + ul a { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.5rem; + } +} + +.lab-settings__area { + margin: 1rem 0 0.25rem 1.25rem; + color: var(--cps-color-text-darkest); + font-size: 0.8125rem; + font-weight: 700; + letter-spacing: 0.06em; + text-transform: uppercase; +} + +// A section's marks: how many of its settings are modified, and a dot when +// it has changes not applied — as the bar that lists them has too. +.lab-settings__marks { + display: flex; + align-items: center; + gap: 0.375rem; +} + +.lab-settings__count { + min-width: 1.25rem; + padding: 0 0.375rem; + border-radius: 0.625rem; + background-color: var(--cps-color-line-light); + color: var(--cps-color-text-dark); + font-size: 0.75rem; + font-weight: 600; + text-align: center; +} + +.lab-settings__dot { + flex: none; + width: 0.5rem; + height: 0.5rem; + border-radius: 50%; + background-color: var(--cps-color-warn-darken2); +} + +@media (forced-colors: active) { + .lab-settings__dot { + border: 0.25rem solid CanvasText; + } +} + +.lab-settings__picker { + display: none; +} + +// At least as tall as the window, so the bar sits at its foot under a short +// section too. +.lab-settings__content { + display: flex; + flex-direction: column; + gap: 1.5rem; + min-height: calc(100vh - 8.75rem - var(--uwt-inspector-inset-bottom, 0px)); + + // Only where the section goes, after it: no gap of its own. + > router-outlet { + display: none; + } +} + +@container lab-main (max-width: 50rem) { + .lab-settings { + grid-template-columns: minmax(0, 1fr); + gap: 1rem; + } + + .lab-settings__nav { + display: none; + } + + .lab-settings__picker { + display: block; + } +} + +.lab-settings__title { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.5rem 0.75rem; +} + +.lab-settings__applies { + padding: 0.125rem 0.625rem; + border-radius: 1rem; + background-color: #eef1f6; + color: #3b4a63; + font-size: 0.8125rem; + font-weight: 600; +} + +.lab-settings__notice { + padding: 0.625rem 0.875rem; + border-left: 0.25rem solid var(--cps-color-warn-darken2); + border-radius: 0.25rem; + background-color: var(--cps-color-warn-bg); +} + +:is(.lab-settings__notice, .lab-changes, .lab-pending) a { + color: var(--cps-color-calm); + font-weight: 600; + + &:focus-visible { + @include cps.focus-ring; + } +} + +.lab-changes { + display: flex; + flex-direction: column; + gap: 0.5rem; + margin: 0; + padding: 0; + list-style: none; + + li { + display: flex; + flex-wrap: wrap; + align-items: baseline; + gap: 0.25rem 1rem; + } +} + +.lab-changes__values { + color: var(--cps-text-secondary); + font: + 0.85rem ui-monospace, + Menlo, + Consolas, + monospace; + overflow-wrap: anywhere; +} + +// In reach however far the section scrolls, above the status bar and any +// pane docked at the bottom. +.lab-pending { + position: sticky; + bottom: calc(3.75rem + var(--uwt-inspector-inset-bottom, 0px)); + z-index: 5; + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.5rem 1rem; + max-width: 64rem; + margin-top: auto; + padding: 0.625rem 0.75rem 0.625rem 1rem; + border: 0.0625rem solid var(--cps-color-line-mid); + border-radius: 0.5rem; + background-color: #fff; + box-shadow: 0 0.375rem 1.25rem rgb(0 0 0 / 15%); +} + +.lab-pending__summary { + display: flex; + flex: 1 1 18rem; + align-items: center; + gap: 0.5rem; + min-width: 0; + margin: 0; + + strong { + white-space: nowrap; + } +} + +.lab-pending__list { + min-width: 0; + overflow: hidden; + color: var(--cps-text-secondary); + text-overflow: ellipsis; + white-space: nowrap; +} + +.lab-pending__review { + padding: 0 0.5rem; +} + +.lab-pending__problem { + flex-basis: 100%; + margin: 0; + color: var(--cps-color-error); +} + +.lab-main:focus { + outline: none; +} + +// The header and status bar are fixed to the window on purpose: they show +// what an Event Inspector pane covers, and — with `lab-insets` on , +// switched on the Event Inspector page — how an app keeps them beside it. +.lab-header { + // Measured by its own width, which a docked pane narrows. + container: lab-header / inline-size; + position: fixed; + top: 0; + right: 0; + left: 0; + z-index: 10; + display: flex; + align-items: center; + gap: 1rem; + height: 3.5rem; + padding: 0 1.25rem; + background-color: #8a0b3c; + color: #fff; +} + +.lab-logo { + flex-shrink: 0; + margin-right: -0.25rem; + border-radius: 50%; + box-shadow: 0 0 0 0.125rem #ffffffb3; +} + +.lab-title { + font-size: 1.2rem; + white-space: nowrap; +} + +.lab-chips { + display: flex; + gap: 0.5rem; + min-width: 0; +} + +// Each leads to its setting. +.lab-chip { + padding: 0.15rem 0.6rem; + border-radius: 1rem; + background-color: #ffffff26; + color: inherit; + font-size: 0.85rem; + text-decoration: none; + white-space: nowrap; + + &:hover { + background-color: #ffffff40; + text-decoration: underline; + } + + &:focus-visible { + @include cps.focus-ring; + } +} + +.lab-run-settings { + display: none; + position: relative; + + > summary { + padding: 0.15rem 0.6rem; + border-radius: 1rem; + background-color: #ffffff26; + font-size: 0.85rem; + white-space: nowrap; + cursor: pointer; + + &:focus-visible { + @include cps.focus-ring; + } + } +} + +.lab-run-settings__panel { + position: absolute; + top: calc(100% + 0.75rem); + left: 0; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 0.375rem; + padding: 0.75rem; + border-radius: 0.5rem; + background-color: #8a0b3c; + box-shadow: 0 0.25rem 0.75rem #0000004d; + // Within a phone's width: long chips wrap rather than run off it. + max-width: min(24rem, calc(100vw - 6rem)); + + .lab-chip { + white-space: normal; + } +} + +.lab-spacer { + flex: 1; +} + +.lab-user { + white-space: nowrap; + cursor: default; +} + +.lab-header-button { + padding: 0.35rem 0.9rem; + border: 0.0625rem solid #fff; + border-radius: 0.25rem; + background: transparent; + color: #fff; + font: inherit; + white-space: nowrap; + cursor: pointer; + + &:focus-visible { + @include cps.focus-ring; + } +} + +// Not enough room for the chips: they fold behind Run settings. +@container lab-header (max-width: 72rem) { + .lab-chips { + display: none; + } + + .lab-run-settings { + display: block; + } +} + +// A phone: the name and sign-in state are left to screen readers, and the +// status bar, so the buttons stay in reach. +@container lab-header (max-width: 30rem) { + .lab-title, + .lab-user { + position: absolute; + width: 0.0625rem; + height: 0.0625rem; + overflow: hidden; + clip: rect(0, 0, 0, 0); + } +} + +// The narrowest phones: a tighter header, so its button stays whole. On the +// viewport, as the header can't query its own width. +@media (max-width: 22rem) { + .lab-header { + gap: 0.5rem; + padding: 0 0.75rem; + } +} + +// One line, whatever the width: each part ends in an ellipsis when short of +// room, the ids first, with the full text in its tooltip. +.lab-status { + position: fixed; + right: 0; + bottom: 0; + left: 0; + z-index: 10; + display: flex; + align-items: center; + gap: 1.25rem; + height: 2.25rem; + padding: 0 1.25rem; + overflow: hidden; + background-color: #1f2937; + color: #e5e7eb; + font-size: 0.875rem; + font-variant-numeric: tabular-nums; + white-space: nowrap; + + > span { + flex-shrink: 0; + } + + > .lab-status__id { + flex-shrink: 1; + min-width: 4rem; + overflow: hidden; + text-overflow: ellipsis; + } +} + +html.lab-insets { + .lab-header { + right: var(--uwt-inspector-inset-right, 0px); + left: var(--uwt-inspector-inset-left, 0px); + } + + .lab-status { + right: var(--uwt-inspector-inset-right, 0px); + bottom: var(--uwt-inspector-inset-bottom, 0px); + left: var(--uwt-inspector-inset-left, 0px); + } +} + +// Measured by its width, which a docked pane narrows, so the nav folds +// beside a pane as on a small screen. +.lab-layout { + container: lab-layout / inline-size; + min-height: 100vh; + padding: 3.5rem 0 2.25rem; +} + +.lab-layout__grid { + display: grid; + grid-template-columns: 12rem minmax(0, 1fr); +} + +// As tall as the window between the header and the status bar, above a pane +// docked below, so Settings sits at its foot; scrolled on its own when the +// pages need more room. +.lab-nav { + position: sticky; + top: 3.5rem; + align-self: start; + display: flex; + flex-direction: column; + height: calc(100vh - 5.75rem - var(--uwt-inspector-inset-bottom, 0px)); + overflow-y: auto; + padding-top: 0.5rem; + + // Settings, apart from the pages. + .lab-nav__pinned { + margin-top: auto; + padding: 0.5rem 0; + border-top: 0.0625rem solid var(--cps-color-line-mid); + + a { + display: flex; + align-items: center; + gap: 0.5rem; + } + } +} + +.lab-nav__pages { + display: flex; + flex: 1 0 auto; + flex-direction: column; +} + +// The rows of the side nav, and of Settings' list of its sections. +:is(.lab-nav, .lab-settings__nav) { + ul { + display: flex; + flex-direction: column; + margin: 0; + padding: 0; + list-style: none; + } + + a { + display: block; + // The bar is reserved on every row, as the selected one gets it: its + // text then stays where it is, in line with the group headers. + padding: 0.625rem 1.25rem 0.625rem 1rem; + border-left: 0.25rem solid transparent; + color: var(--cps-color-text-dark); + text-decoration: none; + transition: + background-color 0.12s, + color 0.12s; + + // The tint is faint against the page, so the text changes colour too. + &:hover { + background-color: rgb(135 10 60 / 7%); + color: var(--cps-color-calm); + } + + &:active { + background-color: rgb(135 10 60 / 15%); + color: var(--cps-color-calm); + } + + &.active { + border-left-color: var(--cps-color-calm); + background-color: var(--cps-color-highlight-selected); + color: var(--cps-color-calm); + font-weight: 700; + + &:hover { + background-color: var(--cps-color-highlight-selected-dark); + } + } + + // Inside the link, as cps-ui-kit does for full-width items: outside, the + // ring would be cut off at the window's edge. + &:focus-visible { + @include cps.focus-ring(-0.125rem, -0.25rem, 0); + } + } +} + +@media (prefers-reduced-motion: reduce) { + :is(.lab-nav, .lab-settings__nav) a { + transition: none; + } +} + +// Forced colours drop the tints: the selected row keeps its bar, in the +// system's selection colour, and the bold text. +@media (forced-colors: active) { + :is(.lab-nav, .lab-settings__nav) a { + border-left-color: Canvas; + + &.active { + border-left-color: Highlight; + } + } +} + +.lab-nav__group { + margin: 0.25rem 1.25rem 0.375rem; + padding-top: 1rem; + border-top: 0.0625rem solid var(--cps-color-line-mid); + color: var(--cps-color-text-darkest); + font-size: 0.9375rem; + font-weight: 700; + letter-spacing: 0.06em; + text-transform: uppercase; + + // A divider only between groups. + &:first-child { + padding-top: 0.25rem; + border-top: 0; + } +} + +.lab-nav__toggle { + display: none; +} + +// Under 900px: one column, the nav folded behind a bar naming the page. +@container lab-layout (max-width: 56.25rem) { + .lab-layout__grid { + grid-template-columns: minmax(0, 1fr); + } + + .lab-nav { + z-index: 5; + height: auto; + overflow: visible; + padding: 0; + border-bottom: 0.0625rem solid var(--cps-color-line-mid); + background-color: #f4f6f9; + } + + .lab-nav__toggle { + display: flex; + gap: 0.5rem; + width: 100%; + padding: 0.625rem 1.25rem; + border: 0; + background: none; + color: var(--cps-color-text-dark); + font: inherit; + font-weight: 700; + text-align: left; + cursor: pointer; + + &:focus-visible { + @include cps.focus-ring(-0.125rem, -0.25rem, 0); + } + } + + .lab-nav__current { + color: var(--cps-text-secondary); + font-weight: 400; + } + + .lab-nav__pages { + display: none; + max-height: calc(100vh - 9rem); + overflow: auto; + padding-bottom: 0.5rem; + } + + .lab-nav__pages--open { + display: block; + } +} + +.lab-main { + // Measured, so a page can put its results beside its actions. + container: lab-main / inline-size; + padding: 1.5rem 2rem; +} + +@container lab-layout (max-width: 30rem) { + .lab-main { + padding: 1rem; + } +} + +.lab-table { + width: 100%; + border-collapse: collapse; + font-size: 0.875rem; + + th, + td { + padding: 0.4rem 0.6rem; + border-bottom: 0.0625rem solid var(--cps-color-line-light); + text-align: left; + vertical-align: top; + } + + th { + background-color: #fafbfc; + } + + code { + font-size: 0.8rem; + } +} + +// A page with results: its cards, and beside them, kept in view, what they +// did. Stacked below 60rem, results after the cards. +.lab-page__cards { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.lab-page--results { + max-width: 90rem; +} + +@container lab-main (min-width: 60rem) { + .lab-page--results { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(18rem, 26rem); + align-items: start; + gap: 1rem 1.5rem; + + > h1, + > p { + grid-column: 1 / -1; + } + + > lab-results { + position: sticky; + top: 4.5rem; + } + } +} + +.lab-results { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 1rem 1.25rem; + border: 0.0625rem solid var(--cps-color-line-mid); + border-radius: 0.5rem; + background-color: #fff; + + h2 { + margin: 0; + font-size: 1.1rem; + } + + h3 { + margin: 0.5rem 0 0; + font-size: 0.95rem; + } + + > p { + margin: 0; + color: var(--cps-text-secondary); + font-size: 0.9rem; + } + + .lab-readout { + max-height: 16rem; + } +} + +.lab-results__sent { + display: flex; + flex-direction: column; + gap: 0.25rem; + margin: 0; + padding: 0; + font-size: 0.85rem; + list-style: none; + + li { + display: grid; + grid-template-columns: 4rem minmax(0, 1fr) auto; + gap: 0.5rem; + } +} + +.lab-results__name { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.lab-results__state { + color: var(--cps-text-secondary); +} + +.lab-results__state--received { + color: var(--cps-color-success-darken2); +} + +.lab-card__header { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 0.5rem 1rem; +} + +// A table wider than a small screen scrolls on its own, not the page. +// A scrolling box takes the focus with an outline, not the mixin's ring: that +// is drawn inside the box, so it would scroll with the content. +.lab-scroll { + overflow-x: auto; +} + +.lab-scroll:focus-visible, +.lab-readout:focus-visible { + outline: 0.125rem solid var(--cps-color-calm); + outline-offset: 0.125rem; +} + +.lab-note { + color: var(--cps-text-secondary); + font-size: 0.9rem; +} + +// An action kept apart from the group before it. +.lab-apart { + margin-inline-start: 1rem; +} + +// Fields sized for a wide screen never run past a small one's edge. +.lab-page + :is( + .cps-textarea-container, + .cps-select, + .cps-input-container, + textarea, + input + ) { + max-width: 100%; +} + +// On a phone, the kit's toggle wraps its options, and a switch its label; +// specific enough to beat the kit's own one-row and max-content rules. +.lab-page cps-button-toggle .cps-btn-toggle .cps-btn-toggle-content { + flex-wrap: wrap; + + // Every option keeps its edge once wrapped; overlapping, they look as one. + // The class twice outranks the kit's rule that takes the edge away. + .cps-btn-toggle-option-wrapper { + margin-right: -0.0625rem; + + .cps-btn-toggle-content-option.cps-btn-toggle-content-option { + border-right: 0.0625rem solid var(--cps-color-text-lightest); + } + } +} + +.lab-page cps-switch { + min-width: 0; + max-width: 100%; + + // The switch keeps its size while its label wraps. + .cps-switch { + flex-shrink: 0; + } +} diff --git a/projects/watchtower-lab/src/telemetry.schema.ts b/projects/watchtower-lab/src/telemetry.schema.ts new file mode 100644 index 0000000..5447a07 --- /dev/null +++ b/projects/watchtower-lab/src/telemetry.schema.ts @@ -0,0 +1,49 @@ +// Every scenario, step, BI event and logger name the lab uses, declared as the +// README tells applications to, so the library's types check them. +declare module '@absaoss-cps/ngx-ui-watchtower' { + interface UwtScenarioNames { + 'lab-happy-path': true; + 'lab-failure': true; + 'lab-cancel': true; + 'lab-incomplete': true; + 'lab-timeout': true; + 'lab-backdated': true; + 'lab-aggregates': true; + 'lab-http': true; + 'lab-http-error': true; + 'lab-superseded': true; + 'lab-abandoned': true; + 'lab-privacy': true; + 'lab-burst': true; + 'lab-detailed': true; + 'lab-parent': true; + 'lab-child': true; + 'lab-settled': true; + 'lab-crowd': true; + 'lab-long': true; + 'lab-trace-outcome': true; + 'lab-trace-cancel': true; + 'lab-trace-teardown': true; + 'lab-correlated': true; + } + interface UwtScenarioSteps { + fetch: true; + validate: true; + render: true; + batch: true; + } + interface UwtBIEventNames { + lab_button_clicked: true; + lab_double_fired: true; + lab_feature_used: true; + lab_privacy_checked: true; + lab_burst: true; + } + interface UwtLoggerNames { + lab: true; + noisy: true; + privacy: true; + } +} + +export {}; diff --git a/projects/watchtower-lab/tsconfig.app.json b/projects/watchtower-lab/tsconfig.app.json new file mode 100644 index 0000000..bdf07b4 --- /dev/null +++ b/projects/watchtower-lab/tsconfig.app.json @@ -0,0 +1,10 @@ +/* The Watchtower Lab: a testing app for the library, built from its source. */ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "../../out-tsc/watchtower-lab", + "types": ["node"] + }, + "files": ["src/main.ts", "src/main.server.ts", "server/server.ts"], + "include": ["src/**/*.ts", "server/**/*.ts"] +} diff --git a/tsconfig.json b/tsconfig.json index 9e6331e..76ac1cd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,6 +9,9 @@ ], "@absaoss-cps/ngx-ui-watchtower/rum": [ "./projects/ngx-ui-watchtower/rum/src/public-api.ts" + ], + "@absaoss-cps/ngx-ui-watchtower/event-inspector": [ + "./projects/ngx-ui-watchtower/event-inspector/src/public-api.ts" ] }, "esModuleInterop": true, @@ -42,6 +45,11 @@ { "path": "./projects/ngx-ui-watchtower/tsconfig.lib.json" }, { "path": "./projects/ngx-ui-watchtower/tsconfig.spec.json" }, { "path": "./projects/ngx-ui-watchtower/rum/tsconfig.lib.json" }, - { "path": "./projects/ngx-ui-watchtower/rum/tsconfig.spec.json" } + { "path": "./projects/ngx-ui-watchtower/rum/tsconfig.spec.json" }, + { "path": "./projects/ngx-ui-watchtower/event-inspector/tsconfig.lib.json" }, + { "path": "./projects/ngx-ui-watchtower/event-inspector/tsconfig.spec.json" }, + { "path": "./projects/watchtower-lab/tsconfig.app.json" }, + { "path": "./projects/watchtower-lab-fragment/tsconfig.app.json" }, + { "path": "./playwright/tsconfig.json" } ] }