diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 54aae4e2..4456da30 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,14 +13,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 # Set up Docker Buildx for multi-arch support - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - # Build multi-arch image + uses: docker/setup-buildx-action@v4 + # Build multi-arch image - name: Build Docker image (multi-arch) run: | - docker buildx create --use docker buildx build --platform linux/amd64,linux/arm64 . # Docker development - name: Docker development @@ -29,25 +28,25 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 - - - name: Enable Corepack - run: corepack enable + uses: actions/checkout@v7 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v7 with: - node-version: 20 + node-version: 24 + + - name: Enable Corepack + run: corepack enable - name: Install dependencies - run: yarn install + run: yarn install --immutable # Lint and format and prettier - name: Lint run: yarn lint - name: Prettier - run: yarn format + run: yarn format:check # Build - name: Build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cbf6fafc..9149dfce 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,12 +10,15 @@ on: jobs: push-to-ghcr: runs-on: ubuntu-latest + permissions: + contents: read + packages: write steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Login to GHCR - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -23,11 +26,11 @@ jobs: # Set up Docker Buildx for multi-arch support - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 # Build and push multi-arch Docker image - name: Build and push Staking brain docker monorepo - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v7 with: file: Dockerfile push: true @@ -37,17 +40,14 @@ jobs: gh-release: runs-on: ubuntu-latest needs: push-to-ghcr + permissions: + contents: write steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v7 - name: Create Release - id: create_release - uses: actions/create-release@v1 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ github.event.inputs.tag }} - release_name: Release v${{ github.event.inputs.tag }} - draft: false - prerelease: true + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RELEASE_TAG: ${{ inputs.tag }} + run: gh release create "$RELEASE_TAG" --title "Release v$RELEASE_TAG" --prerelease --generate-notes diff --git a/.gitignore b/.gitignore index adf2f4c7..a1cfe23d 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ packages/brain/uiBuild # Brain db packages/brain/brain-db.json +packages/brain/builder-config-imported # Keystores packages/brain/keystores diff --git a/.prettierrc b/.prettierrc index 8c5ed36f..159f4977 100644 --- a/.prettierrc +++ b/.prettierrc @@ -8,7 +8,7 @@ "jsxSingleQuote": false, "trailingComma": "none", "bracketSpacing": true, - "jsxBracketSameLine": false, + "bracketSameLine": false, "arrowParens": "always", "endOfLine": "auto" } diff --git a/Dockerfile b/Dockerfile index 778dc587..f520cc6c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,12 @@ -ARG DOCKER_IMAGE=node:20.17.0-alpine3.20 +ARG DOCKER_IMAGE=node:24.21.0-alpine # Build -FROM --platform=$TARGETPLATFORM ${DOCKER_IMAGE} AS build-stage +FROM ${DOCKER_IMAGE} AS build-stage WORKDIR /app COPY package.json yarn.lock tsconfig.json .yarnrc.yml ./ COPY packages packages -# Required to build with vite -RUN apk update && apk add --no-cache python3 py3-pip build-base # Enable corepack to use modern package manager RUN corepack enable @@ -16,10 +14,10 @@ RUN corepack enable RUN yarn install --immutable && \ yarn build && \ yarn clean:libraries && \ - yarn workspaces focus --all --production + yarn workspaces focus @stakingbrain/brain --production # Production -FROM --platform=$TARGETPLATFORM ${DOCKER_IMAGE} +FROM ${DOCKER_IMAGE} ENV NODE_ENV=production WORKDIR /app COPY ./packages/brain/tls ./tls @@ -36,4 +34,4 @@ COPY --from=build-stage /app/packages/ui/build ./uiBuild COPY --from=build-stage /app/packages/brain/dist /app/packages/brain/dist COPY --from=build-stage /app/packages/brain/package.json /app/packages/brain/package.json -CMD [ "node", "packages/brain/dist/index" ] +CMD [ "node", "packages/brain/dist/index.js" ] diff --git a/Dockerfile.brain.dev b/Dockerfile.brain.dev index 6052ba73..ebc21483 100644 --- a/Dockerfile.brain.dev +++ b/Dockerfile.brain.dev @@ -1,5 +1,5 @@ -ARG DOCKER_IMAGE=node:20.17.0-alpine3.20 -FROM --platform=$TARGETPLATFORM ${DOCKER_IMAGE} as build-stage +ARG DOCKER_IMAGE=node:24.21.0-alpine +FROM ${DOCKER_IMAGE} AS build-stage ENV NODE_ENV=development WORKDIR /app @@ -13,6 +13,6 @@ RUN apk add --no-cache --virtual .build-deps \ curl \ && rm -rf /var/cache/apk/* -RUN corepack enable && yarn set version berry +RUN corepack enable -ENTRYPOINT ["/bin/bash", "-c", "yarn && yarn build && yarn run dev"] \ No newline at end of file +ENTRYPOINT ["/bin/bash", "-c", "yarn && yarn build && yarn run dev"] diff --git a/README.md b/README.md index 2ff404b6..c2c64f9d 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,9 @@ Staking Brain is a critical logical component for Staking in DAppNode. It provides a user interface and a Launchpad API that allow manual and automatic keystore management. Designed to support not only solo stakers, but also DVT/LSD technologies, Staking Brain streamlines the staking process for all users. -Within the Dappnode environment, Staking Brain is incorporated into the Web3Signer packages (gnosis, mainnet, prater, lukso, holesky, hoodi). It ensures that user configurations for validators are reliably maintained. Please note that Staking Brain does not store keystores itself, but ensures their storage in the web3signer. It also maintains consistency between the validator service and web3signer service, as the validator must recognize all the pubkeys of validators whose keystores have been imported into the signer. +Within the Dappnode environment, Staking Brain is incorporated into the Web3Signer packages (gnosis, mainnet, lukso, hoodi). It ensures that user configurations for validators are reliably maintained. Please note that Staking Brain does not store keystores itself, but ensures their storage in the web3signer. It also maintains consistency between the validator service and web3signer service, as the validator must recognize all the pubkeys of validators whose keystores have been imported into the signer. + +Supported `NETWORK` values are `mainnet`, `gnosis`, `lukso`, and `hoodi`. Retired testnets are rejected at startup. Update the network and corresponding client environment variables when moving an older deployment to a supported network; network names are not automatically remapped. Each Web3Signer package includes four services: @@ -26,7 +28,9 @@ The new functionalities that the StakingBrain brings are: Follow these steps to develop with Staking Brain: - 1. Connect to your DAppNode (which should be running an instance of web3signer and validator in a network, e.g., Prater). +Use Node.js 24 LTS (at least 24.11) and Yarn 4.18.1, pinned in `package.json`. Enable Corepack with `corepack enable`. + + 1. Connect to your DAppNode (which should be running an instance of web3signer and validator in a network, e.g., Hoodi). 2. Clone the repository: @@ -37,9 +41,9 @@ git clone https://github.com/dappnode/StakingBrain 3. Set your current DAppNode staker config in `packages/brain/.env` : ``` -NETWORK="prater" -_DAPPNODE_GLOBAL_EXECUTION_CLIENT_PRATER="goerli-erigon.dnp.dappnode.eth" -_DAPPNODE_GLOBAL_CONSENSUS_CLIENT_PRATER="prysm-prater.dnp.dappnode.eth" +NETWORK="hoodi" +_DAPPNODE_GLOBAL_EXECUTION_CLIENT_HOODI="hoodi-geth.dnp.dappnode.eth" +_DAPPNODE_GLOBAL_CONSENSUS_CLIENT_HOODI="lighthouse-hoodi.dnp.dappnode.eth" ``` ### A) Local development @@ -52,10 +56,10 @@ yarn build yarn dev ``` -5.Access the KeyManager UI (e.g. Prater) +5.Access the KeyManager UI (e.g. Hoodi) ``` -http://localhost/?network=prater&signerUrl=http://web3signer.web3signer-prater.dappnode:9000/ +http://localhost:3000/ ``` ### B) Docker development @@ -76,7 +80,7 @@ docker logs -f brain (To watch status) docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' brain ``` -6. Access the KeyManager UI (e.g. Prater) +6. Access the KeyManager UI (e.g. Hoodi) ``` http:// @@ -84,7 +88,7 @@ http:// ### Development Recommendations - 1. Connect to your DAppNode using SSH and stop the brain container of your Prater web3signer: + 1. Connect to your DAppNode using SSH and stop the brain container of your Hoodi web3signer: ``` docker ps | grep brain diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index d3abf22e..ea8b9d14 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -1,4 +1,3 @@ -version: "3.9" services: brain: build: diff --git a/docs/gloas-builder-config.md b/docs/gloas-builder-config.md new file mode 100644 index 00000000..608185f0 --- /dev/null +++ b/docs/gloas-builder-config.md @@ -0,0 +1,204 @@ +# Gloas / ePBS validator configuration + +Brain manages configuration. The validator client signs slot-specific proposer +preferences and builder authorizations and supplies the resolved configuration +in proposal requests. The beacon node solicits and selects bids. Web3Signer +continues to provide signing; Brain does not implement those protocol duties. + +## Specifications checked + +Checked upstream branch heads on 2026-10-19 before implementation, pinned to: + +- Keymanager API `master`, [`e5aab3e3ac4b116a2b193000996e4645c8e1bd5f`](https://github.com/ethereum/keymanager-APIs/tree/e5aab3e3ac4b116a2b193000996e4645c8e1bd5f): + [`apis/builder_config.yaml`](https://github.com/ethereum/keymanager-APIs/blob/e5aab3e3ac4b116a2b193000996e4645c8e1bd5f/apis/builder_config.yaml), + [`types/builder_entry.yaml`](https://github.com/ethereum/keymanager-APIs/blob/e5aab3e3ac4b116a2b193000996e4645c8e1bd5f/types/builder_entry.yaml), + `types/uint.yaml`, `types/public_key.yaml`, `types/eth_address.yaml`. +- Beacon API `master`, [`01e99a6530b22941f619bde525cbf8bb61991d42`](https://github.com/ethereum/beacon-APIs/blob/01e99a6530b22941f619bde525cbf8bb61991d42/validator-flow.md): + validator flow (signed proposer preferences and `produceBlockV4`). +- Builder specs `main`, [`5461ba26626e2ac010eff8c4b8ecf2e288a46fc7`](https://github.com/ethereum/builder-specs/blob/5461ba26626e2ac010eff8c4b8ecf2e288a46fc7/specs/gloas/validator.md): + Gloas honest validator, authorization identity, payment caps and fee recipient. + +These are development specifications. The Keymanager README at this revision +lists no released client version supporting builder configuration. Deployment +requires a validator client implementing these routes and semantics, a compatible +Gloas beacon node, and signing support for the new messages in the validator / +Web3Signer stack. Brain does not infer support from the selected client brand. + +## Existing architecture and additions + +The shared `ValidatorApi`, per-key lowdb records, bulk settings RPC and dialogs, +fee-recipient enforcement and periodic reload already existed. Builder support +extends those paths. No alternate database or signing/bid-selection service is +introduced. Existing fee-recipient status reporting also now compares the actual +pubkey and address rather than always reporting success. + +`ValidatorApi` exposes `getBuilderConfig`, `setBuilderConfig` and +`deleteBuilderConfig` on `/eth/v1/validator/{pubkey}/builder_config`. POST sends +exactly the supplied overrides; DELETE has no body. Uint64s remain decimal +strings throughout validation, JSON persistence, RPC, editing and HTTP requests. +Values outside 0–18446744073709551615, numeric JSON values, invalid hex, malformed +public keys, oversized lists and duplicate URL/auth identities are rejected. +Builder request URLs are limited to absolute ASCII HTTP(S) URLs; use punycode for +internationalized hostnames. Userinfo and ports are supported. Omitted auth data +is compared using the specified canonical hostname bytes, including compressed +IPv6; it is left omitted in storage and requests for the client to resolve. + +## Persistence and inheritance + +Each validator may have a `builderConfig` in the existing database: + +Database writes retain owner-only read/write permissions (`0600`), including +after the JSON adapter atomically replaces the file. + +| Stored value | Meaning on application | +| -------------------- | -------------------------------------------------------------------------------------------------- | +| Property absent | Unmanaged: no builder endpoint calls, including for older database records and newly imported keys | +| `{}` | POST no overrides; inherit all validator defaults | +| `{ "builders": [] }` | POST an explicitly empty direct-builder list | +| Sparse object | POST exactly these overrides, replacing the prior configuration in full | +| `null` | DELETE the key's builder configuration and follow validator defaults | + +Reset intent remains stored as `null`: a failed reset, validator restart, or a +later client-side configuration change must not resurrect the old override. +Periodic reload replays POSTs and DELETEs, including successful ones, after key +imports and fee-recipient application. Each key fails independently and is +retried on the next reload. A bulk edit persists the validated batch before +application; partial application failures are reported with per-key status and a +message that settings are saved and will be retried. Application status is +transient and becomes pending after Brain restarts until application is tried. +Reloads, settings updates, imports and deletions share a queue per database, so an +in-flight reload finishes before a newer edit is saved and applied. Slow cron +runs skip overlapping ticks rather than accumulating more reloads. + +GET returns **resolved** client values. It never supplies editor defaults, +updates the database, or generates subsequent POST bodies. Changes to inherited +validator defaults therefore remain effective. Fee and index updates preserve +builder overrides; builder updates preserve fee recipients, index and tags. + +## One-time MEV-Boost settings import + +Set a comma-separated list of preset IDs on the `brain` service and restart it: + +```dotenv +BUILDER_IDS=aestus,flashbots,titan +``` + +Brain resolves the IDs using its current `NETWORK`. Hoodi IDs are `aestus`, +`flashbots`, `nflaig`, `titan`, `ultrasound`, and `pk910`. Spaces and duplicates +are ignored. Empty lists do nothing; unknown IDs reject the whole import without +changing settings or stopping normal startup. + +The list is saved once for existing validators without builder settings. Existing +choices and resets are preserved. After saving, Brain creates an empty +`builder-config-imported` marker beside `brain-db.json` in the data volume. +Keep it with the database in backups. The environment variable can remain set: +later restarts, edits, and new validators are unaffected. An empty database also +completes the import. Normal reconciliation applies the saved choices to the client. + +## UI and RPC + +Select one or more validators and choose **Builder settings**. A single selection +loads its saved overrides. The basic editor shows network-specific ePBS relay +presets with one selectable row per relay. Selected rows show a tinted background, +border, and check indicator. Clicking the relay name opens its endpoint; clicking +anywhere else in the row toggles selection. Row buttons support keyboard selection. +Presets live in Brain's +`modules/config/builderRelayPresets.ts` and are served by the read-only +`getBuilderRelayPresets()` RPC for the active network. The initial Hoodi catalog +contains Aestus, Flashbots, nflaig, Titan, Ultra Sound, and pk910 from +[EthStaker's ePBS list](https://ethstaker.org/mev-relay-list), reviewed 2026-10-22. +These are plain ePBS URLs, not legacy MEV-Boost URLs with public keys. Mainnet has +an independent empty catalog until ePBS endpoints are announced; add them to its +entry for a future release. Networks never fall back to Hoodi presets. Nothing +is automatically enabled. Selecting a preset saves its URL as an ordinary builder +entry, so future catalog updates do not rewrite saved settings. + +**Advanced settings** starts collapsed and exposes global bid preferences, +custom endpoints, and all per-builder settings without nested Advanced sections. +Each builder card collapses under its endpoint header. New custom cards open +automatically, and validation errors reveal the affected card. +Relay cards link to their endpoints and show network-specific OFAC filtering +metadata. Hoodi ePBS policies are currently unverified and displayed as +**Unknown**; mainnet MEV-Boost classifications are not reused for Hoodi. +Existing custom endpoints and +overrides are retained when toggling other presets. Saved URLs are matched to +selected rows, including equivalent trailing slashes; deselecting a relay removes +all entries at that endpoint, including any authorization variants. Custom +endpoints are called out above the advanced section. Validation errors expand +the editor. Custom endpoints remain editable if the preset lookup fails. + +Bulk editing starts with client defaults and explicitly replaces all selected validators' +overrides. Default switches omit numeric fields. Brain is the management point +for direct builders: select presets, add custom endpoints, or leave the list empty. +Every normal UI save sends an explicit `builders` list, so client-level builder +lists cannot silently become effective. Older omitted lists remain unchanged +until a user saves; the editor explains that saving replaces the inherited list +and starts with no direct builders selected. Resolved values never seed the list. +The backend still preserves omitted lists for API compatibility, and **Restore +client defaults** retains DELETE semantics (including restoring any client-level +builder list), explained by its tooltip and success message. +Add or remove builder cards and public-key fields without +editing JSON. The single Advanced settings section starts collapsed (validation +errors open it) and exposes per-builder limits and authorization. Minimum bid and boost explicitly +inherit the global preferences above; their helper text previews those form values. +Numbers remain strings internally, including zero and the full uint64 range. +Restore client defaults saves DELETE intent. A compact table compares saved +choices with effective client values, with per-builder details collapsed. Entries +are matched by exact URL and effective authorization bytes, independently of GET +ordering. These values never fill inherited form fields, and the comparison +excludes unsaved edits. Unavailable reads and saved-only/client-only entries are +shown explicitly. + +- `min_bid`: payment floor in Gwei; top-level value covers P2P bids and entries + without their own floor. +- `builder_boost_factor`: percentage preference, `100` for profit maximization; + zero prefers local building subject to upstream error/fallback semantics. + Entries can supply their own floor and boost. The UI displays the maximum uint64 + as **MAX**, preserving the exact string internally. Other percentages weight bid + comparison (50 = half, 150 = 1.5 times); they do not change the actual payment. +- An empty direct-builder list leaves P2P bids and local building available. +- `max_execution_payment`: per-builder cap in Gwei on trusted execution payment + counted when selecting a bid: `value + min(execution_payment, cap)`. Zero counts + none; it does not prove no additional execution payment can arrive. +- `builder_pubkeys`: empty or omitted accepts any builder; otherwise restricts + accepted bid signatures to the given 48-byte keys. These are builder signing + identities obtained from the builder, distinct from validator keys. A filter + applies to its URL entry, leaving other entries and public P2P bids unaffected. +- `auth_data`: opaque 1–4096 bytes, stored as hex and turned into signed + authorization **inside the validator client**. Omission derives a hostname + identity there. Authorization values can be inspected in the explicit builder + dialog; HTTP failure bodies are not echoed into logs or errors because they + may contain these values. + +`updateValidators([{ pubkey, builderConfig }])` saves a replacement; `null` +resets. Existing `feeRecipient` edits can still be sent alone. The read RPC +`getBuilderConfigs([pubkey, ...])` reports saved intent, resolved client values, +last application status, and an independent read status/message per key. +`getValidators` includes saved settings and last application status without +requiring builder endpoint availability. Refresh validator status in the dialog to +inspect application attempts after a periodic reload. +Client status reads run at most eight requests at once with a ten-second deadline +for the entire selection. Timed-out and unstarted reads report an error for their +key; saved choices remain available. Reads do not disable saving, resetting or +closing the editor, and stale read responses are discarded after a save. + +UI RPC requests allow up to 8 MiB, enough for configurations that fit the 6 MiB +database limit. The UI rejects larger requests before sending them. Pending RPC +calls reject on disconnect or after two minutes, with a reminder to refresh status +before retrying because an already received mutation may still finish on Brain. + +HTTP 405/501 are identified as unsupported; 404 explicitly means the endpoint +may be unavailable or the validator key may be missing. Authentication failures, +server failures, malformed responses and request timeouts are exposed. Ordinary +key and fee-recipient operations do not depend on builder API support. + +## Smooth + +The existing configured fee recipient remains authoritative. Builder +configuration cannot specify an alternate payout address; unknown fields such as +`fee_recipient` are rejected. Reload continues to enforce the configured fee +recipient even when all builder operations fail. In Gloas, protocol payments use +`bid.fee_recipient`; this can differ from the execution payload fee recipient. +The compatible validator client must use the configured destination for its +signed proposer preferences and expected bid recipient. Payment detection and +reward accounting remain with Smooth's existing components. diff --git a/eslint.config.mjs b/eslint.config.mjs index 332e2e08..1e53a914 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,43 +1,18 @@ -import { FlatCompat } from "@eslint/eslintrc"; -import { parse } from "@typescript-eslint/parser"; -import path from "path"; -import { fileURLToPath } from "url"; import js from "@eslint/js"; +import tseslint from "typescript-eslint"; +import prettier from "eslint-plugin-prettier/recommended"; -// mimic CommonJS variables -- not needed if using CommonJS -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -const compat = new FlatCompat({ - baseDirectory: __dirname, - resolvePluginsRelativeTo: __dirname, // optional - recommendedConfig: js.configs.recommended, // optional unless using "eslint:recommended" - allConfig: js.configs.all // optional unless using "eslint:all" -}); - -export default [ +export default tseslint.config( + { ignores: ["**/node_modules/**", "packages/*/dist/**", "packages/*/build/**"] }, + js.configs.recommended, + tseslint.configs.recommended, + prettier, { - // Basic settings - languageOptions: { - parser: parse, - parserOptions: { - project: "./tsconfig.json", // Adjust path if necessary - tsconfigRootDir: __dirname, - sourceType: "module" - } - }, - ignores: ["node_modules", "packages/*/dist", "packages/*/node_modules", "packages/*/build"] - }, - - // Optionally, add compatibility with old configs - ...compat.config({ - plugins: ["@typescript-eslint", "prettier"], - extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:prettier/recommended"], + files: ["**/*.{ts,tsx}"], rules: { - "prettier/prettier": "error", - "no-unused-expressions": "off", // Disable standard no-unused-expressions rule - "@typescript-eslint/no-unused-expressions": "off", // Disable TypeScript-specific rule + "no-unused-expressions": "off", + "@typescript-eslint/no-unused-expressions": "off", "@typescript-eslint/no-unused-vars": ["error", { varsIgnorePattern: "^_", argsIgnorePattern: "^_" }] } - }) -]; + } +); diff --git a/package.json b/package.json index 7c5a5245..bf9ae995 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "type": "module", "private": true, "engines": { - "node": ">=20.11.0" + "node": ">=24.11.0" }, "workspaces": [ "packages/*" @@ -13,34 +13,38 @@ "author": "DappNode Association", "license": "MIT", "devDependencies": { - "@types/chai": "^4.3.19", - "@types/mocha": "^10.0.7", - "@types/node": "^22.5.3", - "@typescript-eslint/eslint-plugin": "^8.4.0", - "@typescript-eslint/parser": "^8.4.0", - "chai": "^5.1.1", - "eslint": "^9.9.1", - "eslint-config-prettier": "^9.1.0", - "eslint-config-react-app": "^7.0.1", - "eslint-plugin-import": "^2.30.0", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-prettier": "^5.2.1", - "mocha": "^10.7.3", - "prettier": "^3.3.3", - "rimraf": "^4.1.1", - "tsx": "^4.19.0", - "typescript": "^5.5.4" + "@eslint/js": "^10.0.1", + "@types/chai": "^5.2.3", + "@types/mocha": "^10.0.10", + "@types/node": "^24.19.2", + "chai": "^6.3.0", + "dotenv": "^18.0.8", + "eslint": "^10.13.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.7", + "mocha": "^12.0.3", + "prettier": "^3.9.10", + "rimraf": "^6.1.3", + "tsx": "^4.23.15", + "typescript": "~6.0.3", + "typescript-eslint": "^8.71.2" }, "scripts": { "build": "yarn workspaces foreach --all -pt run build", "test": "yarn workspaces foreach --all -pt run test", "dev": "yarn workspaces foreach --all -pi -j unlimited run dev", - "lint": "eslint 'packages/**/src/**/*.{js,ts,tsx}' 'packages/**/test/**/*.{js,ts,tsx}' --ignore-pattern 'packages/common/src/validation/schemas/**/*.{js,ts,tsx}'", - "lint:fix": "eslint 'packages/**/src/**/*.{js,ts,tsx}' 'packages/**/test/**/*.{js,ts,tsx}' --fix --ignore-pattern 'packages/common/src/validation/schemas/**/*.{js,ts,tsx}'", + "lint": "eslint 'packages/**/src/**/*.{js,ts,tsx}' 'packages/**/test/**/*.{js,ts,tsx}'", + "lint:fix": "eslint 'packages/**/src/**/*.{js,ts,tsx}' 'packages/**/test/**/*.{js,ts,tsx}' --fix", "format": "prettier --write 'packages/**/src/**/*.{js,ts,tsx,json,md}' 'packages/**/test/**/*.{js,ts,tsx,json,md}'", "clean": "yarn run clean:build && yarn run clean:libraries", - "clean:libraries": "rimraf packages/**/node_modules && rimraf node_modules", - "clean:build": "rimraf packages/**/dist && rimraf packages/**/build" + "clean:libraries": "rimraf --glob 'packages/*/node_modules' node_modules", + "clean:build": "rimraf --glob 'packages/*/dist' 'packages/*/build'", + "format:check": "prettier --check 'packages/**/src/**/*.{js,ts,tsx,json,md}' 'packages/**/test/**/*.{js,ts,tsx,json,md}'" }, - "packageManager": "yarn@4.4.1" + "packageManager": "yarn@4.18.1", + "dependenciesMeta": { + "esbuild": { + "built": true + } + } } diff --git a/packages/brain/.mocharc.yaml b/packages/brain/.mocharc.yaml index 41e7c635..443f3cbd 100644 --- a/packages/brain/.mocharc.yaml +++ b/packages/brain/.mocharc.yaml @@ -4,5 +4,4 @@ extension: [ts] require: - dotenv/config node-option: - - experimental-specifier-resolution=node - - import=tsx/esm + - import=tsx diff --git a/packages/brain/package.json b/packages/brain/package.json index 83445c32..a135c294 100644 --- a/packages/brain/package.json +++ b/packages/brain/package.json @@ -8,8 +8,6 @@ "scripts": { "test": "yarn run test:unit", "test:unit": "mocha --exit --recursive 'test/**/*.unit.test.ts'", - "test:int": "mocha --exit --recursive 'test/**/*.int.test.ts'", - "test:all": "yarn run test:unit && yarn run test:int", "dev": "yarn run link:ui && tsx --watch src/index.ts", "link:ui": "ln -s ../ui/build/ ./uiBuild", "build": "yarn run clean:dist && yarn run clean:ln && tsc -p tsconfig.json", @@ -20,25 +18,21 @@ }, "dependencies": { "@stakingbrain/common": "workspace:^0.1.0", - "ajv": "^8.12.0", - "cors": "^2.8.5", - "dotenv": "^16.0.3", - "express": "^5.0.0", - "lodash-es": "^4.17.21", + "cors": "^2.8.6", + "dotenv": "^18.0.8", + "express": "^5.3.0", "lowdb": "^7.0.1", - "postgres": "^3.4.4", - "socket.io": "^4.7.5" + "postgres": "^3.4.9", + "socket.io": "^4.8.4" }, "devDependencies": { - "@types/cors": "^2.8.13", - "@types/express": "^5.0.0", - "@types/lodash-es": "^4.17.12", - "@types/lowdb": "^1.0.15", - "@types/mocha": "^10.0.7", - "@types/sinon": "^10.0.13", - "mocha": "^10.7.3", - "rimraf": "^4.1.1", - "sinon": "^15.0.1", - "typescript": "^5.5.4" + "@types/cors": "^2.8.19", + "@types/express": "^5.0.6", + "@types/mocha": "^10.0.10", + "@types/sinon": "^22.0.0", + "mocha": "^12.0.3", + "rimraf": "^6.1.3", + "sinon": "^22.1.0", + "typescript": "~6.0.3" } } diff --git a/packages/brain/src/index.ts b/packages/brain/src/index.ts index 4e995e4a..f2f1a9b0 100644 --- a/packages/brain/src/index.ts +++ b/packages/brain/src/index.ts @@ -8,6 +8,7 @@ import process from "node:process"; import { params } from "./params.js"; import { getCrons } from "./modules/cron/index.js"; import { brainConfig } from "./modules/config/index.js"; +import { importBuilderIds } from "./modules/config/importBuilderIds.js"; logger.info(`Starting brain...`); @@ -19,20 +20,33 @@ const __dirname = process.cwd(); // Load staker config const config = brainConfig(); -logger.debug(`Brain config:\n`); -for (const [key, value] of Object.entries(config)) logger.debug(`${key}: ${JSON.stringify(value)}`); +logger.debug(`Chain config: ${JSON.stringify(config.chain)}`); // Create API instances. Must preceed db initialization const { prometheusApi, signerApi, blockExplorerApi, validatorApi, beaconchainApi, dappmanagerApi, postgresClient } = getApiClients(config); // Create DB instance -const brainDb = new BrainDataBase( - mode === "production" ? path.resolve("data", params.brainDbName) : params.brainDbName -); +const brainDbPath = mode === "production" ? path.resolve("data", params.brainDbName) : params.brainDbName; +const brainDb = new BrainDataBase(brainDbPath); await brainDb.initialize(signerApi, validatorApi); -logger.debug(brainDb.data); +logger.debug(`Loaded ${Object.keys(brainDb.getData()).length} validators`); + +// Import builder choices once before cron/API writes can begin. +try { + const imported = importBuilderIds({ + builderIds: process.env.BUILDER_IDS, + network: config.chain.network, + brainDb, + markerPath: path.join(path.dirname(brainDbPath), "builder-config-imported") + }); + if (imported) + logger.info(`Builder settings imported: ${imported.imported} validators; ${imported.skipped} preserved`); +} catch (error) { + // A failed optional handoff must not interrupt ordinary key/fee reconciliation. + logger.error("Unable to import BUILDER_IDS; import will retry at next startup", error); +} // CRON const { reloadValidatorsCronTask } = getCrons({ diff --git a/packages/brain/src/modules/apiClients/beaconchain/index.ts b/packages/brain/src/modules/apiClients/beaconchain/index.ts index a5409d6e..2ea4881f 100644 --- a/packages/brain/src/modules/apiClients/beaconchain/index.ts +++ b/packages/brain/src/modules/apiClients/beaconchain/index.ts @@ -137,15 +137,18 @@ export class BeaconchainApi extends StandardApi { */ public async getStateValidator({ state, - pubkey + pubkey, + signal }: { state: BlockId; pubkey: string; + signal?: AbortSignal; }): Promise { try { return await this.request({ method: "GET", - endpoint: path.join(this.beaconchainEndpoint, "states", state, "validators", pubkey) + endpoint: path.join(this.beaconchainEndpoint, "states", state, "validators", pubkey), + signal }); } catch (e) { throw new BeaconchainApiError(`Error getting (GET) validator from beaconchain. ${e.message}`); diff --git a/packages/brain/src/modules/apiClients/error.ts b/packages/brain/src/modules/apiClients/error.ts index 968cee85..fed2abb5 100644 --- a/packages/brain/src/modules/apiClients/error.ts +++ b/packages/brain/src/modules/apiClients/error.ts @@ -1,3 +1,12 @@ import { createErrorFactory } from "../../createErrorFactory.js"; export const ApiError = createErrorFactory("ApiError"); + +export class HttpApiError extends ApiError { + constructor( + public readonly statusCode: number, + message: string + ) { + super(message); + } +} diff --git a/packages/brain/src/modules/apiClients/prometheus/index.ts b/packages/brain/src/modules/apiClients/prometheus/index.ts index c0fb502b..75e94fb8 100644 --- a/packages/brain/src/modules/apiClients/prometheus/index.ts +++ b/packages/brain/src/modules/apiClients/prometheus/index.ts @@ -132,7 +132,6 @@ export class PrometheusApi extends StandardApi { query: string; startTimestamp: number; endTimestamp: number; - // eslint-disable-next-line @typescript-eslint/no-explicit-any }): Promise { // Construct the request body const requestBody = new URLSearchParams({ diff --git a/packages/brain/src/modules/apiClients/signer/index.ts b/packages/brain/src/modules/apiClients/signer/index.ts index 20f984d3..8ed5d405 100644 --- a/packages/brain/src/modules/apiClients/signer/index.ts +++ b/packages/brain/src/modules/apiClients/signer/index.ts @@ -154,12 +154,13 @@ export class Web3SignerApi extends StandardApi { * List all remote validating pubkeys known to this validator client binary * @see https://ethereum.github.io/keymanager-APIs/#/Remote%20Key%20Manager/ListRemoteKeys */ - public async listRemoteKeys(): Promise { + public async listRemoteKeys(signal?: AbortSignal): Promise { try { return await this.request({ method: "GET", endpoint: this.localKeymanagerEndpoint, - headers: this.originHeader + headers: this.originHeader, + signal }); } catch (e) { throw new SignerApiError(`Error getting (GET) keystores from remote signer. ${e.message}`); diff --git a/packages/brain/src/modules/apiClients/standard.ts b/packages/brain/src/modules/apiClients/standard.ts index 754db684..6842fccf 100644 --- a/packages/brain/src/modules/apiClients/standard.ts +++ b/packages/brain/src/modules/apiClients/standard.ts @@ -1,9 +1,10 @@ import https from "node:https"; import http from "node:http"; -import { ApiError } from "./error.js"; +import { ApiError, HttpApiError } from "./error.js"; import logger from "../logger/index.js"; import type { ApiParams, AllowedMethods, ErrnoException } from "./types.js"; import type { Network } from "@stakingbrain/common"; +import { RPC_TIMEOUT_MS } from "@stakingbrain/common"; export class StandardApi { private useTls = false; @@ -53,7 +54,8 @@ export class StandardApi { endpoint, body, headers = {}, // Default to an empty object - timeout + timeout = RPC_TIMEOUT_MS, + signal }: { method: AllowedMethods; endpoint: string; @@ -61,8 +63,10 @@ export class StandardApi { body?: any; headers?: Record; timeout?: number; + signal?: AbortSignal; // eslint-disable-next-line @typescript-eslint/no-explicit-any }): Promise { + signal?.throwIfAborted(); let req: http.ClientRequest; this.requestOptions.method = method; this.requestOptions.path = endpoint; @@ -89,17 +93,19 @@ export class StandardApi { if (this.useTls) { this.requestOptions.rejectUnauthorized = false; - req = https.request(this.requestOptions); + req = https.request({ ...this.requestOptions, signal }); } else { - req = http.request(this.requestOptions); + req = http.request({ ...this.requestOptions, signal }); } - if (timeout) { - req.setTimeout(timeout, () => { - const error = new ApiError(`Request to ${endpoint} timed out`); - req.destroy(error); - }); - } + const deadline = + timeout > 0 + ? setTimeout(() => { + const error = new ApiError(`Request to ${endpoint} timed out`); + req.destroy(error); + }, timeout) + : undefined; + deadline?.unref(); if (body) { req.setHeader("Content-Length", Buffer.byteLength(body)); @@ -159,7 +165,10 @@ export class StandardApi { } else errorMessage = Buffer.concat(data).toString(); reject( - new ApiError(`Request to ${endpoint} failed with status code ${res.statusCode}: ${errorMessage}. `) + new HttpApiError( + res.statusCode || 0, + `Request to ${endpoint} failed with status code ${res.statusCode}: ${errorMessage}. ` + ) ); } }); @@ -168,7 +177,7 @@ export class StandardApi { }); }); } - ); + ).finally(() => clearTimeout(deadline)); } } diff --git a/packages/brain/src/modules/apiClients/validator/index.ts b/packages/brain/src/modules/apiClients/validator/index.ts index 02263609..92705633 100644 --- a/packages/brain/src/modules/apiClients/validator/index.ts +++ b/packages/brain/src/modules/apiClients/validator/index.ts @@ -12,8 +12,82 @@ import { StandardApi } from "../standard.js"; import path from "path"; import { prefix0xPubkey } from "../prefix0xPubkey.js"; import { ValidatorApiError } from "./error.js"; +import { BuilderConfig, BuilderConfigStatus, isValidBlsPubkey, validateBuilderConfig } from "@stakingbrain/common"; +import { HttpApiError } from "../error.js"; export class ValidatorApi extends StandardApi { + // Same per-validator status/message reporting as the other Keymanager operations. + // Transient: persisted desired configuration is always reapplied after a restart. + private builderConfigStatuses = new Map(); + + public getBuilderConfigStatus(publicKey: string): BuilderConfigStatus | undefined { + return this.builderConfigStatuses.get(prefix0xPubkey(publicKey)); + } + + public async getBuilderConfig(publicKey: string, signal?: AbortSignal): Promise<{ data: BuilderConfig }> { + try { + const response = await this.request({ + method: "GET", + endpoint: this.builderConfigEndpoint(publicKey), + timeout: 10000, + signal + }); + validateBuilderConfig(response?.data); + return response; + } catch (e) { + throw this.builderConfigError(e, "reading", publicKey); + } + } + + /** POST replaces overrides in full. Never feed resolved GET values back into this method. */ + public async setBuilderConfig(config: BuilderConfig, publicKey: string): Promise { + validateBuilderConfig(config); + await this.applyBuilderConfig("POST", publicKey, config); + } + + public async deleteBuilderConfig(publicKey: string): Promise { + await this.applyBuilderConfig("DELETE", publicKey); + } + + private builderConfigEndpoint(publicKey: string): string { + const pubkey = prefix0xPubkey(publicKey); + if (!isValidBlsPubkey(pubkey)) throw new ValidatorApiError("Invalid builder configuration validator public key"); + return `${this.validatorEndpoint}/${pubkey}/builder_config`; + } + + private async applyBuilderConfig( + method: "POST" | "DELETE", + publicKey: string, + config?: BuilderConfig + ): Promise { + const pubkey = prefix0xPubkey(publicKey); + try { + await this.request({ + method, + endpoint: this.builderConfigEndpoint(pubkey), + body: config === undefined ? undefined : JSON.stringify(config), + timeout: 10000 + }); + this.builderConfigStatuses.set(pubkey, { status: "applied" }); + } catch (e) { + const error = this.builderConfigError(e, method === "POST" ? "applying" : "resetting", pubkey); + this.builderConfigStatuses.set(pubkey, { + status: e instanceof HttpApiError && [405, 501].includes(e.statusCode) ? "unsupported" : "error", + message: error.message + }); + throw error; + } + } + + private builderConfigError(error: Error, action: string, pubkey: string): ValidatorApiError { + // Do not expose response bodies: a client may echo stored authorization bytes. + const reason = + error instanceof HttpApiError + ? `HTTP ${error.statusCode}${[405, 501].includes(error.statusCode) ? " (builder configuration is unsupported)" : error.statusCode === 404 ? " (endpoint unavailable or validator key not found)" : ""}` + : error.message; + return new ValidatorApiError(`Error ${action} builder configuration for ${pubkey}: ${reason}`); + } + /** * Remote Key Manager endpoint * @see https://ethereum.github.io/keymanager-APIs/#/Remote%20Key%20Manager @@ -30,11 +104,12 @@ export class ValidatorApi extends StandardApi { * List the validator public key to eth address mapping for fee recipient feature on a specific public key. * @see https://ethereum.github.io/keymanager-APIs/#/Fee%20Recipient/listFeeRecipient */ - public async getFeeRecipient(publicKey: string): Promise { + public async getFeeRecipient(publicKey: string, signal?: AbortSignal): Promise { try { return (await this.request({ method: "GET", - endpoint: path.join(this.validatorEndpoint, prefix0xPubkey(publicKey), "feerecipient") + endpoint: path.join(this.validatorEndpoint, prefix0xPubkey(publicKey), "feerecipient"), + signal })) as ValidatorGetFeeResponse; } catch (e) { throw new ValidatorApiError(`Error getting fee recipient for pubkey ${publicKey} from validator. ${e.message}`); @@ -78,11 +153,12 @@ export class ValidatorApi extends StandardApi { * List the validator public key to eth address mapping for fee recipient feature on a specific public key. * https://ethereum.github.io/keymanager-APIs/#/Fee%20Recipient/listFeeRecipient */ - public async getRemoteKeys(): Promise { + public async getRemoteKeys(signal?: AbortSignal): Promise { try { return (await this.request({ method: "GET", - endpoint: this.remoteKeymanagerEndpoint + endpoint: this.remoteKeymanagerEndpoint, + signal })) as ValidatorGetRemoteKeysResponse; } catch (e) { throw new ValidatorApiError(`Error getting remote keys from validator. ${e.message}`); diff --git a/packages/brain/src/modules/apiClients/validator/types.ts b/packages/brain/src/modules/apiClients/validator/types.ts index a9225f69..dfac3345 100644 --- a/packages/brain/src/modules/apiClients/validator/types.ts +++ b/packages/brain/src/modules/apiClients/validator/types.ts @@ -2,7 +2,7 @@ import { BeaconchainPoolVoluntaryExitsPostRequest } from "../beaconchain/types.j export interface ValidatorGetFeeResponse { data: { - pubkey: string; + pubkey?: string; ethaddress: string; }; } diff --git a/packages/brain/src/modules/apiServers/brain/config.ts b/packages/brain/src/modules/apiServers/brain/config.ts index 37170606..7b7b045f 100644 --- a/packages/brain/src/modules/apiServers/brain/config.ts +++ b/packages/brain/src/modules/apiServers/brain/config.ts @@ -1,5 +1 @@ -export const allowedOrigins = [ - "http://ui.lido-csm-hoodi.dappnode", - "http://ui.lido-csm-holesky.dappnode", - "http://ui.lido-csm-mainnet.dappnode" -]; +export const allowedOrigins = ["http://ui.lido-csm-hoodi.dappnode", "http://ui.lido-csm-mainnet.dappnode"]; diff --git a/packages/brain/src/modules/apiServers/brain/routes/validators/route.ts b/packages/brain/src/modules/apiServers/brain/routes/validators/route.ts index 10be1650..42e11291 100644 --- a/packages/brain/src/modules/apiServers/brain/routes/validators/route.ts +++ b/packages/brain/src/modules/apiServers/brain/routes/validators/route.ts @@ -23,7 +23,7 @@ export const createBrainValidatorsRouter = ({ brainDb }: { brainDb: BrainDataBas const tagList = tagValidatorsMap.get(details.tag) || []; if (format === "index") { - if (!details.index) { + if (details.index === undefined) { logger.warn( `Validator ${pubkey} does not have an index, a possible cause is that the deposit has not been processed yet` ); diff --git a/packages/brain/src/modules/apiServers/launchpad/config.ts b/packages/brain/src/modules/apiServers/launchpad/config.ts index 71050972..5bdd3824 100644 --- a/packages/brain/src/modules/apiServers/launchpad/config.ts +++ b/packages/brain/src/modules/apiServers/launchpad/config.ts @@ -4,6 +4,5 @@ export const allowedOrigins = [ "http://stader-testnet.dappnode", "http://stader.dappnode", "http://ui.lido-csm-hoodi.dappnode", - "http://ui.lido-csm-holesky.dappnode", "http://ui.lido-csm-mainnet.dappnode" ]; diff --git a/packages/brain/src/modules/apiServers/launchpad/routes/feeRecipients/route.ts b/packages/brain/src/modules/apiServers/launchpad/routes/feeRecipients/route.ts index ac818d74..1f40d0c1 100644 --- a/packages/brain/src/modules/apiServers/launchpad/routes/feeRecipients/route.ts +++ b/packages/brain/src/modules/apiServers/launchpad/routes/feeRecipients/route.ts @@ -167,7 +167,7 @@ export const createFeeRecipientsRouter = ({ reloadValidatorsCronTask, brainDb, validatorApi, - customValidatorUpdateRequest: validatorsToUpdate, + customValidatorUpdateRequest: validatorsToUpdate.map(({ pubkey, feeRecipient }) => ({ pubkey, feeRecipient })), requestFrom: "api" }); diff --git a/packages/brain/src/modules/apiServers/launchpad/startLaunchpadApi.ts b/packages/brain/src/modules/apiServers/launchpad/startLaunchpadApi.ts index 28b23a0b..83cf3070 100644 --- a/packages/brain/src/modules/apiServers/launchpad/startLaunchpadApi.ts +++ b/packages/brain/src/modules/apiServers/launchpad/startLaunchpadApi.ts @@ -7,7 +7,7 @@ import { allowedOrigins } from "./config.js"; import { createKeystoresRouter, createFeeRecipientsRouter } from "./routes/index.js"; import { CronJob } from "../../cron/cron.js"; import { BrainDataBase } from "../../db/index.js"; -import { Network } from "@stakingbrain/common"; +import { Network, RPC_MAX_PAYLOAD_BYTES } from "@stakingbrain/common"; import { BeaconchainApi } from "../../apiClients/beaconchain/index.js"; import { Web3SignerApi } from "../../apiClients/signer/index.js"; import { ValidatorApi } from "../../apiClients/validator/index.js"; @@ -32,7 +32,7 @@ export function startLaunchpadApi({ allowedOriginsFromEnv: string | string[] | null; }): http.Server { const app = express(); - app.use(express.json()); + app.use(express.json({ limit: RPC_MAX_PAYLOAD_BYTES })); app.use(cors({ origin: allowedOriginsFromEnv ?? allowedOrigins })); app.use(createKeystoresRouter({ reloadValidatorsCronTask, brainDb, network, validatorApi, signerApi, signerUrl })); diff --git a/packages/brain/src/modules/apiServers/ui/calls/deleteValidators.ts b/packages/brain/src/modules/apiServers/ui/calls/deleteValidators.ts index e6789dca..2aade46f 100644 --- a/packages/brain/src/modules/apiServers/ui/calls/deleteValidators.ts +++ b/packages/brain/src/modules/apiServers/ui/calls/deleteValidators.ts @@ -1,15 +1,14 @@ +import { withValidatorOperationLock } from "../../../db/withValidatorOperationLock.js"; import { Web3SignerApi, ValidatorApi } from "../../../apiClients/index.js"; import { Web3signerDeleteRequest, Web3signerDeleteResponse } from "../../../apiClients/types.js"; import { CronJob } from "../../../cron/cron.js"; import { BrainDataBase } from "../../../db/index.js"; import logger from "../../../logger/index.js"; +import { isValidBlsPubkey, prefix0xPubkey } from "@stakingbrain/common"; /** * Delete keystores: - * 1. Write on db - * 2. Delete keystores on web3signer API - * 3. Delete pubkeys on validator API - * 4. Delete feeRecipient on Validator API + * Stop signing first. Only remove client settings for confirmed deletions. * @param deleteRequest * @returns */ @@ -26,34 +25,47 @@ export async function deleteValidators({ brainDb: BrainDataBase; deleteRequest: Web3signerDeleteRequest; }): Promise { - try { - // IMPORTANT: stop the cron. This removes the scheduled cron task from the task queue - // and prevents the cron from running while we are deleting validators - reloadValidatorsCronTask.stop(); + return withValidatorOperationLock(brainDb, async () => { + try { + if ( + !deleteRequest || + !Array.isArray(deleteRequest.pubkeys) || + deleteRequest.pubkeys.length === 0 || + Array.from(deleteRequest.pubkeys).some( + (key) => typeof key !== "string" || !isValidBlsPubkey(prefix0xPubkey(key)) + ) + ) + throw new Error("Select valid validator public keys to delete"); + deleteRequest = { pubkeys: deleteRequest.pubkeys.map(prefix0xPubkey) }; + // Pause future ticks while this operation holds the shared validator lock. + reloadValidatorsCronTask.stop(); - // Delete feeRecipient on Validator API - for (const pubkey of deleteRequest.pubkeys) - await validatorApi - .deleteFeeRecipient(pubkey) - .then(() => logger.debug(`Deleted fee recipient in validator API`)) - .catch((err) => logger.error(`Error deleting validator feeRecipient`, err)); - // Delete pubkeys on validator API - await validatorApi - .deleteRemoteKeys(deleteRequest) - .then(() => logger.debug(`Deleted pubkeys in validator API`)) - .catch((err) => logger.error(`Error deleting validator pubkeys`, err)); + // Delete keystores on web3signer API + const web3signerDeleteResponse = await signerApi.deleteRemoteKeys(deleteRequest); - // Delete keystores on web3signer API - const web3signerDeleteResponse = await signerApi.deleteRemoteKeys(deleteRequest); + const deletedPubkeys = deleteRequest.pubkeys.filter((_, index) => + ["deleted", "not_found", "not_active"].includes(web3signerDeleteResponse.data[index]?.status) + ); + // Persist before client cleanup so reload can finish an interrupted deletion. + brainDb.deleteValidators(deletedPubkeys); + if (deletedPubkeys.length > 0) { + const clientResponse = await validatorApi + .deleteRemoteKeys({ pubkeys: deletedPubkeys }) + .catch((err) => logger.error(`Error deleting validator pubkeys`, err)); + for (const [index, pubkey] of deletedPubkeys.entries()) { + if (!clientResponse || !["deleted", "not_found"].includes(clientResponse.data[index]?.status)) continue; + await validatorApi + .deleteFeeRecipient(pubkey) + .catch((err) => logger.error(`Error deleting validator feeRecipient`, err)); + } + } - // Write on db - brainDb.deleteValidators(deleteRequest.pubkeys); - - // IMPORTANT: start the cron - reloadValidatorsCronTask.start(); - return web3signerDeleteResponse; - } catch (e) { - reloadValidatorsCronTask.restart(); - throw e; - } + // IMPORTANT: start the cron + reloadValidatorsCronTask.start(); + return web3signerDeleteResponse; + } catch (e) { + reloadValidatorsCronTask.restart(); + throw e; + } + }); } diff --git a/packages/brain/src/modules/apiServers/ui/calls/getBuilderConfigs.ts b/packages/brain/src/modules/apiServers/ui/calls/getBuilderConfigs.ts new file mode 100644 index 00000000..1fe50854 --- /dev/null +++ b/packages/brain/src/modules/apiServers/ui/calls/getBuilderConfigs.ts @@ -0,0 +1,61 @@ +import { isValidBlsPubkey, prefix0xPubkey } from "@stakingbrain/common"; +import { BrainDataBase } from "../../../db/index.js"; +import { ValidatorApi } from "../../../apiClients/validator/index.js"; +import { ValidatorBuilderConfigResponse } from "./types.js"; + +const READ_CONCURRENCY = 8; +const READ_DEADLINE_MS = 10000; + +export async function getBuilderConfigs({ + pubkeys, + brainDb, + validatorApi +}: { + pubkeys: string[]; + brainDb: BrainDataBase; + validatorApi: ValidatorApi; +}): Promise { + if ( + !Array.isArray(pubkeys) || + pubkeys.some((key) => typeof key !== "string" || !isValidBlsPubkey(prefix0xPubkey(key))) + ) + throw new Error("pubkeys must be an array of validator BLS public keys"); + const dbData = brainDb.getData(); + const results: ValidatorBuilderConfigResponse[] = pubkeys.map((key) => { + const pubkey = prefix0xPubkey(key); + if (!dbData[pubkey]) throw new Error(`Validator ${pubkey} is not in the database`); + const builderConfig = dbData[pubkey].builderConfig; + return { + pubkey, + builderConfig, + application: + validatorApi.getBuilderConfigStatus(pubkey) ?? + (builderConfig === undefined ? undefined : { status: "pending" }), + status: "success" + }; + }); + const controller = new AbortController(); + const deadline = setTimeout(() => controller.abort(), READ_DEADLINE_MS); + let next = 0; + try { + await Promise.all( + Array.from({ length: Math.min(READ_CONCURRENCY, results.length) }, async () => { + while (next < results.length) { + const result = results[next++]; + try { + if (controller.signal.aborted) throw new Error("Client status check timed out; refresh to retry"); + result.resolved = (await validatorApi.getBuilderConfig(result.pubkey, controller.signal)).data; + } catch (error) { + result.status = "error"; + result.message = controller.signal.aborted + ? "Client status check timed out; refresh to retry" + : error.message; + } + } + }) + ); + } finally { + clearTimeout(deadline); + } + return results; +} diff --git a/packages/brain/src/modules/apiServers/ui/calls/getValidators.ts b/packages/brain/src/modules/apiServers/ui/calls/getValidators.ts index 4dc16b01..840fff89 100644 --- a/packages/brain/src/modules/apiServers/ui/calls/getValidators.ts +++ b/packages/brain/src/modules/apiServers/ui/calls/getValidators.ts @@ -1,15 +1,19 @@ import { isValidWithdrawableBlsAddress, isValidNonWithdrawableBlsAddress } from "@stakingbrain/common"; -import { CustomValidatorGetResponse, WithdrawalCredentialsFormat } from "./types.js"; +import { CustomValidatorGetResponse } from "./types.js"; import { BrainDataBase } from "../../../db/index.js"; import { ValidatorApi, Web3SignerApi, BeaconchainApi } from "../../../apiClients/index.js"; import logger from "../../../logger/index.js"; -/** - * Get all validators from db - * If running in development mode (NODE_ENV === "development") it will returns booleans for - * validatorImported and validatorFeeRecipientCorrect checks from the validator API - * @returns - */ +type BeaconDetails = Pick; +type ListProgress = { + beaconNext: number; + feeNext: number; + beacon: Map; +}; +const listProgress = new WeakMap(); +const BEACON_CACHE_MS = 60_000; + +/** Read a stable snapshot; independent queues rotate unfinished work across bounded refreshes. */ export async function getValidators({ brainDb, validatorApi, @@ -21,68 +25,106 @@ export async function getValidators({ signerApi: Web3SignerApi; beaconchainApi: BeaconchainApi; }): Promise { - const data = brainDb.data; - if (!data) return []; - - const validatorPubkeys = ( - await validatorApi.getRemoteKeys().catch((e) => { - logger.error(e); - return { data: [] }; - }) - ).data.map((validator) => validator.pubkey); - - const signerPubkeys = ( - await signerApi.listRemoteKeys().catch((e) => { - logger.error(e); - return { data: [] }; - }) - ).data.map((key) => key.validating_pubkey); - - const validatorsFeeRecipients = await Promise.all( - validatorPubkeys.map((pubkey) => validatorApi.getFeeRecipient(pubkey)) - ).catch((e) => { - logger.error(e); - return []; - }); - - const validators: CustomValidatorGetResponse[] = []; - for (const [pubkey, { tag, feeRecipient }] of Object.entries(data)) { - let format: WithdrawalCredentialsFormat, - withdrawalAddress = "", - index = ""; - try { - const validatorStateResponse = await beaconchainApi.getStateValidator({ - state: "head", - pubkey - }); - - withdrawalAddress = validatorStateResponse.data.validator.withdrawal_credentials; - index = validatorStateResponse.data.index; - - format = isValidWithdrawableBlsAddress(withdrawalAddress) - ? "ecdsa" - : isValidNonWithdrawableBlsAddress(withdrawalAddress) - ? "bls" - : "unknown"; - } catch (e) { - logger.error(e); - format = "error"; - } + const entries = Object.entries(brainDb.getData()); + const progress = listProgress.get(brainDb) ?? { beaconNext: 0, feeNext: 0, beacon: new Map() }; + listProgress.set(brainDb, progress); + const currentKeys = new Set(entries.map(([pubkey]) => pubkey)); + for (const [pubkey, cached] of progress.beacon) { + if (!currentKeys.has(pubkey) || cached.expires <= Date.now()) progress.beacon.delete(pubkey); + } + if (entries.length === 0) return []; + const results: CustomValidatorGetResponse[] = entries.map(([pubkey, details]) => ({ + pubkey, + index: details.index?.toString() ?? "", + tag: details.tag, + feeRecipient: details.feeRecipient, + builderConfig: details.builderConfig, + builderConfigStatus: + validatorApi.getBuilderConfigStatus(pubkey) ?? + (details.builderConfig === undefined ? undefined : { status: "pending" }), + withdrawalCredentials: { address: "", format: "error" }, + ...progress.beacon.get(pubkey)?.details + // Membership and fee checks stay undefined until a successful read establishes their value. + })); + const controller = new AbortController(); + const deadline = setTimeout(() => controller.abort(), 10000); - validators.push({ - pubkey, - index, - tag, - feeRecipient, - withdrawalCredentials: { - address: withdrawalAddress, - format - }, - validatorImported: validatorPubkeys.includes(pubkey), - signerImported: signerPubkeys.includes(pubkey), - validatorFeeRecipientCorrect: validatorsFeeRecipients.some((feeRecipient) => feeRecipient === feeRecipient) - }); + async function readQueue( + cursor: "beaconNext" | "feeNext", + read: (result: CustomValidatorGetResponse) => Promise + ) { + const start = progress[cursor] % results.length; + let next = 0; + await Promise.all( + Array.from({ length: Math.min(8, results.length) }, async () => { + while (next < results.length && !controller.signal.aborted) { + const index = (start + next++) % results.length; + progress[cursor] = (index + 1) % results.length; + try { + await read(results[index]); + } catch (error) { + logger.error(error); + } + } + }) + ); } - return validators; + try { + await Promise.all([ + (async () => { + try { + const remoteKeys = await validatorApi.getRemoteKeys(controller.signal); + const keys = new Set(remoteKeys.data.map(({ pubkey }) => pubkey.toLowerCase())); + for (const result of results) result.validatorImported = keys.has(result.pubkey.toLowerCase()); + await readQueue("feeNext", async (result) => { + if (!result.validatorImported) return; + const response = await validatorApi.getFeeRecipient(result.pubkey, controller.signal); + result.validatorFeeRecipientCorrect = + response.data.ethaddress.toLowerCase() === result.feeRecipient.toLowerCase(); + }); + } catch (error) { + logger.error(error); + } + })(), + (async () => { + try { + const signerKeys = await signerApi.listRemoteKeys(controller.signal); + const keys = new Set(signerKeys.data.map(({ validating_pubkey }) => validating_pubkey.toLowerCase())); + for (const result of results) result.signerImported = keys.has(result.pubkey.toLowerCase()); + } catch (error) { + logger.error(error); + } + })(), + readQueue("beaconNext", async (result) => { + const cached = progress.beacon.get(result.pubkey); + if (cached) { + Object.assign(result, cached.details); + return; + } + const { data } = await beaconchainApi.getStateValidator({ + state: "head", + pubkey: result.pubkey, + signal: controller.signal + }); + const address = data.validator.withdrawal_credentials; + const details: BeaconDetails = { + index: data.index, + withdrawalCredentials: { + address, + format: isValidWithdrawableBlsAddress(address) + ? "ecdsa" + : isValidNonWithdrawableBlsAddress(address) + ? "bls" + : "unknown" + } + }; + Object.assign(result, details); + progress.beacon.set(result.pubkey, { details, expires: Date.now() + BEACON_CACHE_MS }); + }) + ]); + return results; + } finally { + clearTimeout(deadline); + } } diff --git a/packages/brain/src/modules/apiServers/ui/calls/importValidators.ts b/packages/brain/src/modules/apiServers/ui/calls/importValidators.ts index 0a38d65b..6b920a74 100644 --- a/packages/brain/src/modules/apiServers/ui/calls/importValidators.ts +++ b/packages/brain/src/modules/apiServers/ui/calls/importValidators.ts @@ -1,5 +1,9 @@ +import { withValidatorOperationLock } from "../../../db/withValidatorOperationLock.js"; import { isFeeRecipientEditable, + isValidBlsPubkey, + isValidEcdsaPubkey, + tags, NonEditableFeeRecipientTag, Tag, shortenPubkey, @@ -7,8 +11,6 @@ import { Network, ROCKET_POOL_FEE_RECIPIENT, STADER_POOL_FEE_RECIPIENT_MAINNET, - STADER_POOL_FEE_RECIPIENT_PRATER, - LIDO_FEE_RECIPIENT_HOLESKY, LIDO_FEE_RECIPIENT_MAINNET, LIDO_FEE_RECIPIENT_HOODI } from "@stakingbrain/common"; @@ -16,11 +18,12 @@ import { CustomImportRequest } from "./types.js"; import { CronJob } from "../../../cron/cron.js"; import { BrainDataBase } from "../../../db/index.js"; import { Web3signerPostResponse } from "../../../apiClients/types.js"; -import { PubkeyDetails } from "../../../db/types.js"; +import { StakingBrainDb } from "../../../db/types.js"; import logger from "../../../logger/index.js"; import { Web3SignerApi, ValidatorApi } from "../../../apiClients/index.js"; type ValidatorImportRequest = { + requestIndex: number; keystore: string; password: string; tag: Tag; @@ -28,15 +31,7 @@ type ValidatorImportRequest = { pubkey: string; }; -/** - * Import keystores: - * 1. Import keystores + passwords on web3signer API - * 2. Import pubkeys on validator API - * 3. Import feeRecipient on Validator API - * 4. Write on db must go last because if signerApi fails does not make sense to write on db since cron will not delete them at some point - * @param postRequest - * @returns Web3signerPostResponse - */ +/** Persist import intent before contacting the signer; reconcile uncertain outcomes without deleting keys. */ export async function importValidators({ postRequest, reloadValidatorsCronTask, @@ -54,137 +49,159 @@ export async function importValidators({ signerUrl: string; brainDb: BrainDataBase; }): Promise { - try { - // IMPORTANT: stop the cron. This removes the scheduled cron task from the task queue - // and prevents the cron from running while we are importing validators + return withValidatorOperationLock(brainDb, async () => { reloadValidatorsCronTask.stop(); - - const validators: ValidatorImportRequest[] = []; - const validatorsToPost: ValidatorImportRequest[] = []; - const wrongFeeRecipientResponse: { - status: "error"; - message: string; - }[] = []; - - for (const validator of postRequest.validatorsImportRequest) { - const keystore = validator.keystore.toString(); - const pubkey = JSON.parse(keystore).pubkey; - - try { - const feeRecipient = - !["gnosis", "lukso"].includes(network) && !isFeeRecipientEditable(validator.tag, postRequest.importFrom) - ? getNonEditableFeeRecipient(validator.tag as NonEditableFeeRecipientTag, network, validator.feeRecipient) - : validator.feeRecipient; - - logger.info(`Setting ${feeRecipient} as fee recipient for ${pubkey}`); - - validators.push({ - keystore, - password: validator.password, - tag: validator.tag, - feeRecipient: feeRecipient, - pubkey - }); - } catch (e) { - wrongFeeRecipientResponse.push({ - status: "error", - message: `Could not obtain fee recipient for pubkey ${shortenPubkey( + try { + if ( + !postRequest || + !Array.isArray(postRequest.validatorsImportRequest) || + postRequest.validatorsImportRequest.length === 0 + ) + throw new Error("Select at least one validator to import"); + if (!["ui", "api"].includes(postRequest.importFrom)) throw new Error("Invalid import origin"); + const existingData = brainDb.getData(); + const existing = new Map(Object.keys(existingData).map((pubkey) => [pubkey.toLowerCase(), pubkey])); + const validators: ValidatorImportRequest[] = []; + const validatorsToPost: ValidatorImportRequest[] = []; + const results: Web3signerPostResponse = { data: [] }; + const seen = new Set(); + for (const [requestIndex, validator] of postRequest.validatorsImportRequest.entries()) { + try { + if (!validator || (typeof validator.keystore !== "string" && !Buffer.isBuffer(validator.keystore))) + throw new Error("Keystore must contain JSON text"); + const keystore = validator.keystore.toString(); + let parsed: { pubkey?: string } | null; + try { + parsed = JSON.parse(keystore); + } catch { + throw new Error("Invalid keystore JSON"); + } + if (typeof parsed?.pubkey !== "string" || !isValidBlsPubkey(prefix0xPubkey(parsed.pubkey))) + throw new Error("Keystore must contain a valid BLS public key"); + const pubkey = prefix0xPubkey(parsed.pubkey).toLowerCase(); + if (seen.has(pubkey)) throw new Error(`Duplicate public key in import request: ${shortenPubkey(pubkey)}`); + if (!tags.includes(validator.tag)) throw new Error("Invalid validator tag"); + if (typeof validator.password !== "string") throw new Error("Keystore password must be a string"); + const feeRecipient = + !["gnosis", "lukso"].includes(network) && !isFeeRecipientEditable(validator.tag, postRequest.importFrom) + ? getNonEditableFeeRecipient(validator.tag as NonEditableFeeRecipientTag, network, validator.feeRecipient) + : validator.feeRecipient; + if (typeof feeRecipient !== "string" || !isValidEcdsaPubkey(feeRecipient)) + throw new Error(`Invalid fee recipient for ${shortenPubkey(pubkey)}`); + seen.add(pubkey); + logger.info(`Setting ${feeRecipient} as fee recipient for ${pubkey}`); + validators.push({ + requestIndex, + keystore, + password: validator.password, + tag: validator.tag, + feeRecipient: feeRecipient, pubkey - )}. ${e} You can force a specific fee recipient by selecting tag "solo", but for some protocols this might cause you to lose rewards if you set a wrong address.` - }); - - logger.error(`Error obtaining fee recipient for pubkey ${shortenPubkey(pubkey)}: ${e}`); + }); + } catch (e) { + results.data[requestIndex] = { + status: "error", + message: e.message + }; + } } - } - // Import keystores and passwords on web3signer API - const web3signerPostResponse = await signerApi.importRemoteKeys({ - keystores: validators.map((validator) => validator.keystore), - passwords: validators.map((validator) => validator.password), - slashing_protection: postRequest.slashing_protection ? postRequest.slashing_protection.toString() : undefined - }); - - logger.debug(`Imported keystores into web3signer API: ${JSON.stringify(web3signerPostResponse.data)}`); - - // Signer API import keystore may fail for some keystores, but not all - // @see https://github.com/ConsenSys/web3signer/issues/713 - // Remove the pubkeys to avoid adding them to the db - const pubkeysToPostIterator = validators.map((validator) => validator.pubkey).entries(); - - //Iterate over pubkeysToPost with index and pubkey - for (const [index, pubkey] of pubkeysToPostIterator) { - const postStatus = web3signerPostResponse.data[index].status; - - if (postStatus === "error") { - web3signerPostResponse.data[index].message += - ". Check that the keystore file format is valid and the password is correct."; - logger.error( - `Error importing keystore for pubkey ${shortenPubkey(pubkey)}: ${web3signerPostResponse.data[index].message}` + if (validators.length === 0) return results; + const toDbRecords = (entries: ValidatorImportRequest[]): StakingBrainDb => + Object.fromEntries( + entries.map(({ pubkey, tag, feeRecipient }) => [ + pubkey, + { + tag, + feeRecipient, + automaticImport: postRequest.importFrom !== "ui", + importPending: true + } + ]) ); - } else if (postStatus === "duplicate") { - logger.warn(`Duplicate keystore for pubkey ${shortenPubkey(pubkey)}`); - } else if (postStatus === "imported") { - validatorsToPost.push(validators[index]); + // Check all database constraints, including size, before changing signer storage. + brainDb.validateNewValidators(toDbRecords(validators)); + // Journal new keys before the signer can change state. Keep uncertain outcomes across restarts. + brainDb.addValidators({ validators: toDbRecords(validators) }); + + // Import keystores and passwords on web3signer API + const web3signerPostResponse = await signerApi.importRemoteKeys({ + keystores: validators.map((validator) => validator.keystore), + passwords: validators.map((validator) => validator.password), + slashing_protection: postRequest.slashing_protection ? postRequest.slashing_protection.toString() : undefined + }); + + logger.debug(`Imported keystores into web3signer API: ${JSON.stringify(web3signerPostResponse.data)}`); + + // Signer API import keystore may fail for some keystores, but not all + // @see https://github.com/ConsenSys/web3signer/issues/713 + // Remove explicit new-key failures, but retain intent when a result is missing. + const rejectedNewKeys: string[] = []; + const confirmedKeys: string[] = []; + for (const [index, validator] of validators.entries()) { + const { pubkey, requestIndex } = validator; + const response = web3signerPostResponse.data[index] ?? { + status: "error", + message: "Import outcome unknown: signer did not return a result. Import intent retained for recovery." + }; + results.data[requestIndex] = response; + const postStatus = response.status; + if (postStatus === "error") { + // A missing result is uncertain, unlike an explicit per-key rejection. + if (web3signerPostResponse.data[index]?.status === "error") { + if (!existing.has(pubkey)) rejectedNewKeys.push(pubkey); + response.message = + (response.message ?? "") + ". Check that the keystore file format is valid and the password is correct."; + } + logger.error(`Error importing keystore for pubkey ${shortenPubkey(pubkey)}: ${response.message}`); + } else if (postStatus === "duplicate" || postStatus === "imported") { + if (postStatus === "duplicate") logger.warn(`Duplicate keystore for pubkey ${shortenPubkey(pubkey)}`); + const storedKey = existing.get(pubkey); + confirmedKeys.push(storedKey ?? pubkey); + if (!storedKey || existingData[storedKey].importPending) + validatorsToPost.push({ + ...validator, + pubkey: storedKey ?? pubkey, + feeRecipient: storedKey ? existingData[storedKey].feeRecipient : validator.feeRecipient + }); + } } - } - - // Add info about the wrong fee recipients to the response - web3signerPostResponse.data.push(...wrongFeeRecipientResponse); - - if (validatorsToPost.length === 0) { - reloadValidatorsCronTask.start(); - return web3signerPostResponse; - } - - // Import pubkeys on validator API - await validatorApi - .postRemoteKeys({ - remote_keys: validatorsToPost.map((validator) => ({ - pubkey: validator.pubkey, - url: signerUrl - })) - }) - .catch((err) => logger.error(`Error setting validator pubkeys`, err)); - logger.debug(`Added pubkeys to validator API`); + brainDb.deleteValidators(rejectedNewKeys); + if (confirmedKeys.length > 0) + brainDb.updateValidators({ + validators: Object.fromEntries(confirmedKeys.map((pubkey) => [pubkey, { importPending: undefined }])) + }); - // Import feeRecipient on Validator API - for (const validator of validatorsToPost) { - console.log("pubkey to set feeRecipient", validator.pubkey); + if (validatorsToPost.length === 0) return results; + // Import pubkeys on validator API await validatorApi - .setFeeRecipient(validator.feeRecipient, validator.pubkey) - .catch((err) => logger.error(`Error setting validator feeRecipient for pubkey ${validator.pubkey} :`, err)); - } - - logger.debug(`Added fee recipients to validator API`); - - const validatorsToDb = { - validators: validatorsToPost.reduce( - (acc, validator) => { - acc[prefix0xPubkey(validator.pubkey)] = { - tag: validator.tag, - feeRecipient: validator.feeRecipient, - automaticImport: postRequest.importFrom !== "ui" - }; - return acc; - }, - {} as { [pubkey: string]: PubkeyDetails } - ) - }; - - // Write on db - brainDb.addValidators(validatorsToDb); + .postRemoteKeys({ + remote_keys: validatorsToPost.map((validator) => ({ + pubkey: validator.pubkey, + url: signerUrl + })) + }) + .catch((err) => logger.error(`Error setting validator pubkeys`, err)); + + logger.debug(`Added pubkeys to validator API`); + + // Import feeRecipient on Validator API + for (const validator of validatorsToPost) { + await validatorApi + .setFeeRecipient(validator.feeRecipient, validator.pubkey) + .catch((err) => logger.error(`Error setting validator feeRecipient for pubkey ${validator.pubkey} :`, err)); + } - logger.debug(`Written on db: ${validatorsToPost.map((v) => v.pubkey).join(", ")}`); + logger.debug(`Added fee recipients to validator API`); - // IMPORTANT: start the cron - reloadValidatorsCronTask.start(); - return web3signerPostResponse; - } catch (e) { - reloadValidatorsCronTask.restart(); - throw e; - } + logger.debug(`Written on db: ${validatorsToPost.map((v) => v.pubkey).join(", ")}`); + return results; + } finally { + reloadValidatorsCronTask.start(); + } + }); } function getNonEditableFeeRecipient( @@ -201,8 +218,7 @@ function getNonEditableFeeRecipient( case "lido": if (network === "mainnet") return LIDO_FEE_RECIPIENT_MAINNET; - else if (network === "holesky") return LIDO_FEE_RECIPIENT_HOLESKY; - else if (network === "hoodi") return suggestedFeeRecipient ?? LIDO_FEE_RECIPIENT_HOODI; + else if (network === "hoodi") return suggestedFeeRecipient || LIDO_FEE_RECIPIENT_HOODI; else throw Error(`Fee recipient not found for tag: ${tag} and network: ${network}`); // Stader FR cannot be known in advance @@ -211,7 +227,6 @@ function getNonEditableFeeRecipient( // Set fee recipient to socializing pool adddress if it is not defined else { if (network === "mainnet") return STADER_POOL_FEE_RECIPIENT_MAINNET; - else if (network === "prater") return STADER_POOL_FEE_RECIPIENT_PRATER; else throw Error(`Fee recipient not found for tag: ${tag} and network: ${network}`); } default: diff --git a/packages/brain/src/modules/apiServers/ui/calls/index.ts b/packages/brain/src/modules/apiServers/ui/calls/index.ts index f74dd24d..af1528c0 100644 --- a/packages/brain/src/modules/apiServers/ui/calls/index.ts +++ b/packages/brain/src/modules/apiServers/ui/calls/index.ts @@ -5,6 +5,7 @@ import { exitValidators, getExitValidators } from "./exitValidators.js"; import { importValidators } from "./importValidators.js"; import { updateValidators } from "./updateValidators.js"; import { getValidators } from "./getValidators.js"; +import { getBuilderConfigs } from "./getBuilderConfigs.js"; import { signerGetStatus } from "./signerGetStatus.js"; import { getStakerConfig } from "./getStakerConfig.js"; import { BrainDataBase } from "../../../db/index.js"; @@ -20,6 +21,7 @@ import { Web3SignerApi } from "../../../apiClients/index.js"; import { BrainConfig } from "../../../config/types.js"; +import { getBuilderRelayPresets } from "../../../config/builderRelayPresets.js"; export const createRpcMethods = ({ postgresClient, @@ -44,6 +46,8 @@ export const createRpcMethods = ({ const { minGenesisTime, secondsPerSlot, network, isMevBoostSet, executionClient, consensusClient } = brainConfig.chain; return { + getBuilderRelayPresets: async () => getBuilderRelayPresets(network), + getBuilderConfigs: async (pubkeys: string[]) => await getBuilderConfigs({ pubkeys, brainDb, validatorApi }), beaconchaFetchAllValidatorsInfo: async (pubkeys: string[]) => await beaconchaFetchAllValidatorsInfo({ blockExplorerApi, pubkeys }), beaconchaFetchValidatorsInfo: async (pubkeys: string[]) => diff --git a/packages/brain/src/modules/apiServers/ui/calls/types.ts b/packages/brain/src/modules/apiServers/ui/calls/types.ts index 3c966c3f..0d8c3447 100644 --- a/packages/brain/src/modules/apiServers/ui/calls/types.ts +++ b/packages/brain/src/modules/apiServers/ui/calls/types.ts @@ -1,4 +1,4 @@ -import { StakerConfig, Tag } from "@stakingbrain/common"; +import { BuilderConfig, BuilderConfigStatus, BuilderRelayPresets, StakerConfig, Tag } from "@stakingbrain/common"; import type { BeaconchaGetResponse, Web3signerDeleteRequest, @@ -13,6 +13,7 @@ import { NumberOfDaysToQuery } from "../../../validatorsDataIngest/types.js"; // Define the type for RPC methods export interface RpcMethods { + getBuilderRelayPresets: () => Promise; beaconchaFetchAllValidatorsInfo: (pubkeys: string[]) => Promise; beaconchaFetchValidatorsInfo: (pubkeys: string[]) => Promise; deleteValidators: (deleteRequest: Web3signerDeleteRequest) => Promise; @@ -24,6 +25,7 @@ export interface RpcMethods { requestFrom?: ActionRequestOrigin ) => Promise; getValidators: () => Promise; + getBuilderConfigs: (pubkeys: string[]) => Promise; signerGetStatus: () => Promise; getStakerConfig: () => Promise; fetchValidatorsPerformanceData: ( @@ -37,18 +39,28 @@ export type ActionRequestOrigin = "ui" | "api"; export interface CustomImportRequest { validatorsImportRequest: CustomValidatorImportRequest[]; importFrom: ActionRequestOrigin; - slashing_protection?: File | string; + slashing_protection?: string; } export interface CustomValidatorImportRequest { tag: Tag; feeRecipient: string; - keystore: File | string; + keystore: string; password: string; } export interface CustomValidatorUpdateRequest { pubkey: string; - feeRecipient: string; + feeRecipient?: string; + builderConfig?: BuilderConfig | null; +} + +export interface ValidatorBuilderConfigResponse { + pubkey: string; + builderConfig?: BuilderConfig | null; + resolved?: BuilderConfig; + application?: BuilderConfigStatus; + status: "success" | "error"; + message?: string; } export type WithdrawalCredentialsFormat = "ecdsa" | "bls" | "unknown" | "error"; @@ -64,4 +76,6 @@ export interface CustomValidatorGetResponse { signerImported?: boolean; validatorImported?: boolean; validatorFeeRecipientCorrect?: boolean; + builderConfig?: BuilderConfig | null; + builderConfigStatus?: BuilderConfigStatus; } diff --git a/packages/brain/src/modules/apiServers/ui/calls/updateValidators.ts b/packages/brain/src/modules/apiServers/ui/calls/updateValidators.ts index 7bb0faf2..ac01b427 100644 --- a/packages/brain/src/modules/apiServers/ui/calls/updateValidators.ts +++ b/packages/brain/src/modules/apiServers/ui/calls/updateValidators.ts @@ -1,17 +1,14 @@ +import { withValidatorOperationLock } from "../../../db/withValidatorOperationLock.js"; import { prefix0xPubkey, isFeeRecipientEditable, ActionRequestOrigin } from "@stakingbrain/common"; import logger from "../../../../modules/logger/index.js"; import { CustomValidatorUpdateRequest } from "./types.js"; -import { PubkeyDetails } from "../../../../modules/db/types.js"; +import { StakingBrainDbUpdate } from "../../../../modules/db/types.js"; import { CronJob } from "../../../cron/cron.js"; import { BrainDataBase } from "../../../db/index.js"; import { ValidatorApi } from "../../../apiClients/validator/index.js"; +import { applyValidatorBuilderConfig } from "../../../cron/reloadValidators/postValidatorsBuilderConfigsFromDb.js"; -/** - * Updates validators on DB: - * 1. Write on db MUST goes first because if signerApi fails, cron will try to delete them - * 2. Import feeRecipient on Validator API - * @param param0 - */ +/** Persist desired settings first. Periodic reload retries client failures. */ export async function updateValidators({ reloadValidatorsCronTask, brainDb, @@ -25,47 +22,52 @@ export async function updateValidators({ customValidatorUpdateRequest: CustomValidatorUpdateRequest[]; requestFrom?: ActionRequestOrigin; }): Promise { - try { - // IMPORTANT: stop the cron. This removes the scheduled cron task from the task queue - // and prevents the cron from running while we are importing validators + return withValidatorOperationLock(brainDb, async () => { reloadValidatorsCronTask.stop(); + try { + if (!Array.isArray(customValidatorUpdateRequest) || customValidatorUpdateRequest.length === 0) + throw new Error("Select at least one validator to update"); + const dbData = brainDb.getData(); + const updates: StakingBrainDbUpdate = {}; + for (const validator of customValidatorUpdateRequest) { + if (!validator || typeof validator.pubkey !== "string") throw new Error("Validator public key is required"); + const pubkey = prefix0xPubkey(validator.pubkey); + if (!dbData[pubkey]) throw new Error(`Validator ${pubkey} is not in the database`); + if (updates[pubkey]) throw new Error(`Duplicate validator ${pubkey}`); + if (Object.keys(validator).some((field) => !["pubkey", "feeRecipient", "builderConfig"].includes(field))) + throw new Error("Unsupported validator setting"); + const update: StakingBrainDbUpdate[string] = {}; + if ("feeRecipient" in validator && isFeeRecipientEditable(dbData[pubkey].tag, requestFrom)) + update.feeRecipient = validator.feeRecipient; + if ("builderConfig" in validator) update.builderConfig = validator.builderConfig; + if (Object.keys(update).length > 0) updates[pubkey] = update; + } + if (Object.keys(updates).length === 0) throw new Error("The fee recipient can't be updated for these validators"); - const dbData = brainDb.getData(); - - // Only update validators with editable fee recipient - const editableValidators: CustomValidatorUpdateRequest[] = customValidatorUpdateRequest.filter( - (validator) => - dbData[prefix0xPubkey(validator.pubkey)] && - isFeeRecipientEditable(dbData[prefix0xPubkey(validator.pubkey)].tag, requestFrom) - ); - - if (editableValidators.length === 0) { - throw new Error("The fee recipient can't be updated for these validators"); + // Validate the entire batch before changing any persisted settings. + brainDb.updateValidators({ validators: updates }); + const failures: string[] = []; + for (const [pubkey, update] of Object.entries(updates)) { + if (update.feeRecipient !== undefined) + await validatorApi.setFeeRecipient(update.feeRecipient, pubkey).catch((err) => { + logger.error("Error setting validator feeRecipient", err); + failures.push(`Fee recipient application failed for ${pubkey}`); + }); + if ("builderConfig" in update) { + try { + await applyValidatorBuilderConfig(validatorApi, pubkey, update.builderConfig); + } catch (error) { + logger.error(error); + failures.push(error.message); + } + } + } + if (failures.length > 0) + throw new Error( + `Settings saved; client configuration application failed and will be retried on reload. ${failures.join("; ")}` + ); + } finally { + reloadValidatorsCronTask.start(); } - - brainDb.updateValidators({ - validators: editableValidators.reduce( - (acc, validator) => { - acc[validator.pubkey] = { - feeRecipient: validator.feeRecipient - }; - return acc; - }, - {} as { [pubkey: string]: Omit } - ) - }); - - // Import feeRecipient on Validator API - for (const validator of editableValidators) - await validatorApi - .setFeeRecipient(validator.feeRecipient, validator.pubkey) - .then(() => logger.debug(`Added feeRecipient to validator API`)) - .catch((err) => logger.error(`Error setting validator feeRecipient`, err)); - - // IMPORTANT: start the cron - reloadValidatorsCronTask.start(); - } catch (e) { - reloadValidatorsCronTask.restart(); - throw e; - } + }); } diff --git a/packages/brain/src/modules/apiServers/ui/config.ts b/packages/brain/src/modules/apiServers/ui/config.ts index 8aee4d89..f018f4cc 100644 --- a/packages/brain/src/modules/apiServers/ui/config.ts +++ b/packages/brain/src/modules/apiServers/ui/config.ts @@ -1,3 +1,18 @@ import { BRAIN_UI_DOMAIN, Network } from "@stakingbrain/common"; +import { RPC_MAX_PAYLOAD_BYTES } from "@stakingbrain/common"; +import type { ServerOptions } from "socket.io"; export const allowedOrigins = (network: Network) => ["http://my.dappnode", `http://${BRAIN_UI_DOMAIN(network)}`]; + +export function uiSocketOptions(origins: string | string[]): Partial { + const allowed = Array.isArray(origins) ? origins : [origins]; + return { + serveClient: false, + maxHttpBufferSize: RPC_MAX_PAYLOAD_BYTES, + cors: { origin: origins }, + // CORS alone does not protect WebSocket connections. + allowRequest: (request, callback) => { + callback(null, !request.headers.origin || allowed.includes("*") || allowed.includes(request.headers.origin)); + } + }; +} diff --git a/packages/brain/src/modules/apiServers/ui/rpcHandler.ts b/packages/brain/src/modules/apiServers/ui/rpcHandler.ts new file mode 100644 index 00000000..c98e53a0 --- /dev/null +++ b/packages/brain/src/modules/apiServers/ui/rpcHandler.ts @@ -0,0 +1,42 @@ +import type { RpcMethods } from "./calls/types.js"; +import logger from "../../logger/index.js"; + +type RpcResponse = { + jsonrpc: "2.0"; + id: string | number | null; + result?: unknown; + error?: { code: number; message: string }; +}; + +export function createRpcHandler(rpcMethods: RpcMethods) { + return async (request: unknown, acknowledgement: unknown): Promise => { + // A missing acknowledgement must not turn validation failures into unhandled rejections. + if (typeof acknowledgement !== "function") return; + const callback = acknowledgement as (response: RpcResponse) => void; + const record = + request && typeof request === "object" && !Array.isArray(request) ? (request as Record) : {}; + const id = typeof record.id === "string" || typeof record.id === "number" ? record.id : null; + if ( + record.jsonrpc !== "2.0" || + typeof record.method !== "string" || + (record.id !== undefined && record.id !== null && typeof record.id !== "string" && typeof record.id !== "number") + ) { + callback({ jsonrpc: "2.0", id, error: { code: -32600, message: "Invalid Request" } }); + return; + } + const method = record.method; + if (!Object.hasOwn(rpcMethods, method)) { + callback({ jsonrpc: "2.0", id, error: { code: -32601, message: "Method not found" } }); + return; + } + logger.debug(`Received rpc call: ${method}`); + try { + const result = await rpcMethods[method as keyof RpcMethods](record.params as never); + callback({ jsonrpc: "2.0", result, id }); + } catch (error) { + logger.error(error); + const message = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error"; + callback({ jsonrpc: "2.0", id, error: { code: -32000, message } }); + } + }; +} diff --git a/packages/brain/src/modules/apiServers/ui/startUiServer.ts b/packages/brain/src/modules/apiServers/ui/startUiServer.ts index dfc1843d..35a3b5d1 100644 --- a/packages/brain/src/modules/apiServers/ui/startUiServer.ts +++ b/packages/brain/src/modules/apiServers/ui/startUiServer.ts @@ -7,7 +7,7 @@ import logger from "../../logger/index.js"; import http from "http"; import fs from "fs"; import { params } from "../../../params.js"; -import { RpcMethods } from "./calls/types.js"; +import { createRpcHandler } from "./rpcHandler.js"; import { createRpcMethods } from "./calls/index.js"; import { CronJob } from "../../cron/cron.js"; import { BrainDataBase } from "../../db/index.js"; @@ -19,27 +19,7 @@ import { ValidatorApi, Web3SignerApi } from "../../apiClients/index.js"; -import { allowedOrigins } from "./config.js"; - -// Define the type for the RPC request -interface RpcRequest { - jsonrpc: string; - method: keyof RpcMethods; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - params?: any; - id: string | number | null; -} - -function toRpcErrorMessage(error: unknown): string { - if (error == null) return "Unknown error"; - if (typeof error === "string") return error; - if (error instanceof Error) return error.message || error.name || "Unknown error"; - try { - return JSON.stringify(error); - } catch { - return String(error); - } -} +import { allowedOrigins, uiSocketOptions } from "./config.js"; export function startUiServer({ brainDb, @@ -82,48 +62,14 @@ export function startUiServer({ const app = express(); const server = http.createServer(app); + const uiOrigins = allowedOriginsFromEnv ?? allowedOrigins(network); // Socket io - const io = new Server(server, { - serveClient: false - }); + const io = new Server(server, uiSocketOptions(uiOrigins)); io.on("connection", (socket) => { logger.debug("A user connected"); - socket.on("rpc", async (request: RpcRequest, callback) => { - const { jsonrpc, method, params, id } = request; - logger.debug(`Received rpc call: ${method}`); - - if (jsonrpc !== "2.0") { - callback({ - jsonrpc: "2.0", - error: { code: -32600, message: "Invalid Request" }, - id - }); - return; - } - - if (!(method in rpcMethods)) { - callback({ - jsonrpc: "2.0", - error: { code: -32601, message: "Method not found" }, - id - }); - return; - } - - try { - const result = await rpcMethods[method](params); - callback({ jsonrpc: "2.0", result, id }); - } catch (error) { - logger.error(error); - callback({ - jsonrpc: "2.0", - error: { code: -32000, message: toRpcErrorMessage(error) }, - id - }); - } - }); + socket.on("rpc", createRpcHandler(rpcMethods)); socket.on("disconnect", () => { logger.debug("A user disconnected"); diff --git a/packages/brain/src/modules/config/builderRelayPresets.ts b/packages/brain/src/modules/config/builderRelayPresets.ts new file mode 100644 index 00000000..18bc7ce6 --- /dev/null +++ b/packages/brain/src/modules/config/builderRelayPresets.ts @@ -0,0 +1,27 @@ +import { BuilderRelayPreset, BuilderRelayPresets, Network } from "@stakingbrain/common"; + +// ePBS endpoints from https://ethstaker.org/mev-relay-list, reviewed 2026-10-22. +// Legacy MEV-Boost URLs (with relay public keys) are not ePBS builder endpoints. +const relayPresets: Record = { + [Network.Hoodi]: [ + // EthStaker does not specify filtering policies for these Hoodi ePBS endpoints. + { id: "aestus", name: "Aestus", url: "https://hoodi.aestus.live", ofacFiltering: "unknown" }, + { id: "flashbots", name: "Flashbots", url: "https://boost-relay-hoodi.flashbots.net", ofacFiltering: "unknown" }, + { id: "nflaig", name: "nflaig", url: "https://builder-hoodi.nflaig.dev", ofacFiltering: "unknown" }, + { id: "titan", name: "Titan", url: "https://hoodi.titanrelay.xyz", ofacFiltering: "unknown" }, + { id: "ultrasound", name: "Ultra Sound", url: "https://relay-hoodi.ultrasound.money", ofacFiltering: "unknown" }, + { id: "pk910", name: "pk910", url: "https://builder-hoodi.pk910.de", ofacFiltering: "unknown" } + ], + // Add mainnet ePBS endpoints here when announced; never fall back to testnet relays. + [Network.Mainnet]: [], + [Network.Gnosis]: [], + [Network.Lukso]: [] +}; + +export function getBuilderRelayPresets(network: Network): BuilderRelayPresets { + return { + network, + sourceUrl: "https://ethstaker.org/mev-relay-list", + relays: relayPresets[network].map((relay) => ({ ...relay })) + }; +} diff --git a/packages/brain/src/modules/config/importBuilderIds.ts b/packages/brain/src/modules/config/importBuilderIds.ts new file mode 100644 index 00000000..0e6dc818 --- /dev/null +++ b/packages/brain/src/modules/config/importBuilderIds.ts @@ -0,0 +1,50 @@ +import fs from "node:fs"; +import { Network } from "@stakingbrain/common"; +import { BrainDataBase } from "../db/index.js"; +import { getBuilderRelayPresets } from "./builderRelayPresets.js"; + +/** Import preset choices once, before cron jobs and APIs start. */ +export function importBuilderIds({ + builderIds, + network, + brainDb, + markerPath +}: { + builderIds: string | undefined; + network: Network; + brainDb: BrainDataBase; + markerPath: string; +}): { imported: number; skipped: number } | undefined { + if (!builderIds || fs.existsSync(markerPath)) return; + const ids = [ + ...new Set( + builderIds + .split(",") + .map((id) => id.trim()) + .filter(Boolean) + ) + ]; + if (ids.length === 0) return; + const { relays } = getBuilderRelayPresets(network); + const builderConfig = { + builders: ids.map((id) => { + const relay = relays.find((relay) => relay.id === id); + if (!relay) throw new Error(`BUILDER_IDS contains an unknown builder ID for ${network}`); + return { url: relay.url }; + }) + }; + + const validators = Object.entries(brainDb.getData()); + const updates = Object.fromEntries( + validators + .filter(([, details]) => details.builderConfig === undefined) + .map(([pubkey]) => [pubkey, { builderConfig }]) + ); + const imported = Object.keys(updates).length; + const skipped = validators.length - imported; + if (imported > 0) brainDb.updateValidators({ validators: updates }); + + // Mark completion only after saving. Retries preserve existing settings and edits. + fs.writeFileSync(markerPath, "", { mode: 0o600, flag: "wx" }); + return { imported, skipped }; +} diff --git a/packages/brain/src/modules/config/index.ts b/packages/brain/src/modules/config/index.ts index aa64b818..14bb72c2 100644 --- a/packages/brain/src/modules/config/index.ts +++ b/packages/brain/src/modules/config/index.ts @@ -66,15 +66,11 @@ const getPostgresUrl = (network: Network): string => { switch (network) { case Network.Hoodi: return "postgres://postgres:password@postgres.web3signer-hoodi.dappnode:5432/web3signer"; - case Network.Holesky: - return "postgres://postgres:password@postgres.web3signer-holesky.dappnode:5432/web3signer"; case Network.Mainnet: return "postgres://postgres:mainnet@postgres.web3signer.dappnode:5432/web3signer-mainnet"; case Network.Gnosis: return "postgres://postgres:gnosis@postgres.web3signer-gnosis.dappnode:5432/web3signer-gnosis"; case Network.Lukso: return "postgres://postgres:password@postgres.web3signer-lukso.dappnode:5432/web3signer"; - case Network.Prater: - return "postgres://postgres:password@postgres.web3signer-prater.dappnode:5432/web3signer"; } }; diff --git a/packages/brain/src/modules/config/loadEnvs.ts b/packages/brain/src/modules/config/loadEnvs.ts index 0ee16435..0af8cfaf 100644 --- a/packages/brain/src/modules/config/loadEnvs.ts +++ b/packages/brain/src/modules/config/loadEnvs.ts @@ -35,10 +35,8 @@ function getNetwork(): Network { if (!network) throw Error("NETWORK env is required"); if (network === Network.Mainnet) return Network.Mainnet; - if (network === Network.Prater) return Network.Prater; if (network === Network.Gnosis) return Network.Gnosis; if (network === Network.Lukso) return Network.Lukso; - if (network === Network.Holesky) return Network.Holesky; if (network === Network.Hoodi) return Network.Hoodi; throw Error(`NETWORK env must be one of ${Object.values(Network).join(", ")}`); diff --git a/packages/brain/src/modules/config/networks/holesky.ts b/packages/brain/src/modules/config/networks/holesky.ts deleted file mode 100644 index 58b2b90e..00000000 --- a/packages/brain/src/modules/config/networks/holesky.ts +++ /dev/null @@ -1,8 +0,0 @@ -export const holeskyBrainConfig = () => { - return { - blockExplorerUrl: "https://holesky.beaconcha.in", - minGenesisTime: 1695902100, // Sep-28-2023 11:55:00 +UTC - secondsPerSlot: 12, - slotsPerEpoch: 32 - }; -}; diff --git a/packages/brain/src/modules/config/networks/index.ts b/packages/brain/src/modules/config/networks/index.ts index 399f4cad..b344fb98 100644 --- a/packages/brain/src/modules/config/networks/index.ts +++ b/packages/brain/src/modules/config/networks/index.ts @@ -1,25 +1,19 @@ import { gnosisBrainConfig } from "./gnosis.js"; -import { holeskyBrainConfig } from "./holesky.js"; import { hoodiBrainConfig } from "./hoodi.js"; import { luksoBrainConfig } from "./lukso.js"; import { mainnetBrainConfig } from "./mainnet.js"; -import { praterBrainConfig } from "./prater.js"; import { Network } from "@stakingbrain/common"; export const networkConfig = (network: Network) => { switch (network) { case Network.Hoodi: return hoodiBrainConfig(); - case Network.Holesky: - return holeskyBrainConfig(); case Network.Mainnet: return mainnetBrainConfig(); case Network.Gnosis: return gnosisBrainConfig(); case Network.Lukso: return luksoBrainConfig(); - case Network.Prater: - return praterBrainConfig(); default: throw Error(`Network ${network} is not supported`); } diff --git a/packages/brain/src/modules/config/networks/prater.ts b/packages/brain/src/modules/config/networks/prater.ts deleted file mode 100644 index c96d45dc..00000000 --- a/packages/brain/src/modules/config/networks/prater.ts +++ /dev/null @@ -1,8 +0,0 @@ -export const praterBrainConfig = () => { - return { - blockExplorerUrl: "https://prater.beaconcha.in", - minGenesisTime: 1614588812, // Mar-01-2021 08:53:32 AM +UTC - secondsPerSlot: 12, - slotsPerEpoch: 32 - }; -}; diff --git a/packages/brain/src/modules/cron/cron.ts b/packages/brain/src/modules/cron/cron.ts index 1b604041..2b6af3df 100644 --- a/packages/brain/src/modules/cron/cron.ts +++ b/packages/brain/src/modules/cron/cron.ts @@ -3,6 +3,7 @@ import logger from "../logger/index.js"; export class CronJob { private task: NodeJS.Timeout | null = null; + private running = false; private interval: number; constructor( @@ -17,7 +18,16 @@ export class CronJob { if (this.task === null) { logger.info(`Starting cron job with interval ${this.interval}ms`); this.task = setInterval(async () => { - await this.jobFunction(); + // A slow reload must not queue another copy on every interval. + if (this.running) return; + this.running = true; + try { + await this.jobFunction(); + } catch (error) { + logger.error("Error running cron job", error); + } finally { + this.running = false; + } }, this.interval); } else logger.warn("Task is already running. Use restart to restart the job."); } diff --git a/packages/brain/src/modules/cron/reloadValidators/deleteDbPubkeysNotInSigner.ts b/packages/brain/src/modules/cron/reloadValidators/deleteDbPubkeysNotInSigner.ts index f7c10547..cbb6d25e 100644 --- a/packages/brain/src/modules/cron/reloadValidators/deleteDbPubkeysNotInSigner.ts +++ b/packages/brain/src/modules/cron/reloadValidators/deleteDbPubkeysNotInSigner.ts @@ -14,7 +14,11 @@ export async function deleteDbPubkeysNotInSigner({ dbPubkeys: string[]; signerPubkeys: string[]; }): Promise { - const dbPubkeysToRemove = dbPubkeys.filter((pubkey) => !signerPubkeys.includes(pubkey)); + const signerKeys = new Set(signerPubkeys.map((pubkey) => pubkey.toLowerCase())); + const data = brainDb.getData(); + const dbPubkeysToRemove = dbPubkeys.filter( + (pubkey) => !signerKeys.has(pubkey.toLowerCase()) && !data[pubkey]?.importPending + ); if (dbPubkeysToRemove.length > 0) { logger.debug(`${logPrefix}Found ${dbPubkeysToRemove.length} validators to remove from DB`); diff --git a/packages/brain/src/modules/cron/reloadValidators/deleteSignerPubkeysNotInDb.ts b/packages/brain/src/modules/cron/reloadValidators/deleteSignerPubkeysNotInDb.ts index 5c68bc79..3659d964 100644 --- a/packages/brain/src/modules/cron/reloadValidators/deleteSignerPubkeysNotInDb.ts +++ b/packages/brain/src/modules/cron/reloadValidators/deleteSignerPubkeysNotInDb.ts @@ -14,7 +14,8 @@ export async function deleteSignerPubkeysNotInDb({ signerPubkeys: string[]; dbPubkeys: string[]; }): Promise { - const signerPubkeysToRemove = signerPubkeys.filter((pubkey) => !dbPubkeys.includes(pubkey)); + const dbKeys = new Set(dbPubkeys.map((pubkey) => pubkey.toLowerCase())); + const signerPubkeysToRemove = signerPubkeys.filter((pubkey) => !dbKeys.has(pubkey.toLowerCase())); if (signerPubkeysToRemove.length > 0) { logger.debug(`${logPrefix}Found ${signerPubkeysToRemove.length} validators to remove from signer`); diff --git a/packages/brain/src/modules/cron/reloadValidators/index.ts b/packages/brain/src/modules/cron/reloadValidators/index.ts index 8519a9a7..37072175 100644 --- a/packages/brain/src/modules/cron/reloadValidators/index.ts +++ b/packages/brain/src/modules/cron/reloadValidators/index.ts @@ -1,3 +1,4 @@ +import { withValidatorOperationLock } from "../../db/withValidatorOperationLock.js"; import { Web3SignerApi, ValidatorApi } from "../../apiClients/index.js"; import { BrainDataBase } from "../../db/index.js"; import logger from "../../logger/index.js"; @@ -7,6 +8,7 @@ import { deleteValidatorPubkeysNotInDB } from "./deleteValidatorPubkeysNotInDb.j import { logPrefix } from "./logPrefix.js"; import { postValidatorPubkeysFromDb } from "./postValidatorPubkeysFromDb.js"; import { postValidatorsFeeRecipientsFromDb } from "./postValidatorsFeeRecipientsFromDb.js"; +import { postValidatorsBuilderConfigsFromDb } from "./postValidatorsBuilderConfigsFromDb.js"; /** * Reload db data based on truth sources: validator and signer APIs: @@ -24,47 +26,67 @@ export async function reloadValidators( validatorApi: ValidatorApi, brainDb: BrainDataBase ): Promise { - try { - logger.debug(`${logPrefix}Reloading data...`); + return withValidatorOperationLock(brainDb, async () => { + try { + logger.debug(`${logPrefix}Reloading data...`); - // 0. Check signer API upcheck endpoint - await signerApi.upcheck(); + // 0. Check signer API upcheck endpoint + await signerApi.upcheck(); - // 1. GET data - const dbPubkeys = Object.keys(brainDb.getData()); - const signerPubkeys = (await signerApi.listRemoteKeys()).data.map((keystore) => keystore.validating_pubkey); + // 1. GET data + const dbPubkeys = Object.keys(brainDb.getData()); + const signerPubkeys = (await signerApi.listRemoteKeys()).data.map((keystore) => keystore.validating_pubkey); - // 2. DELETE from signer API pubkeys that are not in DB - await deleteSignerPubkeysNotInDb({ signerApi, signerPubkeys, dbPubkeys }); + // 2. DELETE from signer API pubkeys that are not in DB + await deleteSignerPubkeysNotInDb({ signerApi, signerPubkeys, dbPubkeys }); - // 3. DELETE from DB pubkeys that are not in signer API - await deleteDbPubkeysNotInSigner({ brainDb, dbPubkeys, signerPubkeys }); + // 3. DELETE from DB pubkeys that are not in signer API + await deleteDbPubkeysNotInSigner({ brainDb, dbPubkeys, signerPubkeys }); - const validatorPubkeys = (await validatorApi.getRemoteKeys()).data.map((keystore) => keystore.pubkey); + const signerKeys = new Set(signerPubkeys.map((pubkey) => pubkey.toLowerCase())); + const data = brainDb.getData(); + const confirmedImports = dbPubkeys.filter( + (pubkey) => data[pubkey].importPending && signerKeys.has(pubkey.toLowerCase()) + ); + if (confirmedImports.length > 0) + brainDb.updateValidators({ + validators: Object.fromEntries(confirmedImports.map((pubkey) => [pubkey, { importPending: undefined }])) + }); + // Preserve pending records, but do not activate keys the signer has not confirmed. + const activeData = Object.fromEntries( + Object.entries(brainDb.getData()).filter(([pubkey]) => signerKeys.has(pubkey.toLowerCase())) + ); - // 4. POST to validator API pubkeys that are in DB and not in validator API - await postValidatorPubkeysFromDb({ - validatorApi, - signerUrl, - brainDbPubkeysToAdd: dbPubkeys.filter((pubkey) => !validatorPubkeys.includes(pubkey)), - validatorPubkeys - }); + const validatorPubkeys = (await validatorApi.getRemoteKeys()).data.map((keystore) => keystore.pubkey); + const validatorKeys = new Set(validatorPubkeys.map((pubkey) => pubkey.toLowerCase())); + const dbKeys = new Set(dbPubkeys.map((pubkey) => pubkey.toLowerCase())); - // 5. DELETE to validator API pubkeys that are in validator API and not in DB - await deleteValidatorPubkeysNotInDB({ - validatorApi, - validatorPubkeys, - validatorPubkeysToRemove: validatorPubkeys.filter((pubkey) => !dbPubkeys.includes(pubkey)) - }); + // 4. POST to validator API pubkeys that are in DB and not in validator API + await postValidatorPubkeysFromDb({ + validatorApi, + signerUrl, + brainDbPubkeysToAdd: Object.keys(activeData).filter((pubkey) => !validatorKeys.has(pubkey.toLowerCase())), + validatorPubkeys + }); - // 6. POST to validator API fee recipients that are in DB in validator API - await postValidatorsFeeRecipientsFromDb({ - validatorApi, - dbData: brainDb.getData() - }); + // 5. DELETE to validator API pubkeys that are in validator API and not in DB + await deleteValidatorPubkeysNotInDB({ + validatorApi, + validatorPubkeys, + validatorPubkeysToRemove: validatorPubkeys.filter((pubkey) => !dbKeys.has(pubkey.toLowerCase())) + }); - logger.debug(`${logPrefix}Finished reloading data`); - } catch (e) { - logger.error(`${logPrefix}Error reloading data`, e); - } + // 6. POST to validator API fee recipients that are in DB in validator API + await postValidatorsFeeRecipientsFromDb({ + validatorApi, + dbData: activeData + }); + + // Independently retry managed configuration after key imports and fee destinations. + await postValidatorsBuilderConfigsFromDb({ validatorApi, dbData: activeData }); + logger.debug(`${logPrefix}Finished reloading data`); + } catch (e) { + logger.error(`${logPrefix}Error reloading data`, e); + } + }); } diff --git a/packages/brain/src/modules/cron/reloadValidators/postValidatorsBuilderConfigsFromDb.ts b/packages/brain/src/modules/cron/reloadValidators/postValidatorsBuilderConfigsFromDb.ts new file mode 100644 index 00000000..8f51fe1c --- /dev/null +++ b/packages/brain/src/modules/cron/reloadValidators/postValidatorsBuilderConfigsFromDb.ts @@ -0,0 +1,29 @@ +import { BuilderConfig } from "@stakingbrain/common"; +import { ValidatorApi } from "../../apiClients/index.js"; +import { StakingBrainDb } from "../../db/types.js"; +import logger from "../../logger/index.js"; +import { logPrefix } from "./logPrefix.js"; + +export async function applyValidatorBuilderConfig( + validatorApi: ValidatorApi, + pubkey: string, + config: BuilderConfig | null | undefined +): Promise { + if (config === null) await validatorApi.deleteBuilderConfig(pubkey); + else if (config !== undefined) await validatorApi.setBuilderConfig(config, pubkey); +} + +/** Replay original overrides, including reset intent. Never copy resolved GET values. */ +export async function postValidatorsBuilderConfigsFromDb({ + validatorApi, + dbData +}: { + validatorApi: ValidatorApi; + dbData: StakingBrainDb; +}): Promise { + for (const [pubkey, { builderConfig }] of Object.entries(dbData)) { + await applyValidatorBuilderConfig(validatorApi, pubkey, builderConfig).catch((error) => + logger.error(`${logPrefix}Error applying builder configuration for ${pubkey}`, error) + ); + } +} diff --git a/packages/brain/src/modules/cron/sendProofsOfValidation/getProofsOfValidation.ts b/packages/brain/src/modules/cron/sendProofsOfValidation/getProofsOfValidation.ts index 84eb20b3..5c24e24f 100644 --- a/packages/brain/src/modules/cron/sendProofsOfValidation/getProofsOfValidation.ts +++ b/packages/brain/src/modules/cron/sendProofsOfValidation/getProofsOfValidation.ts @@ -1,4 +1,3 @@ -import { isEmpty } from "lodash-es"; import { Web3SignerApi } from "../../apiClients/index.js"; import { DappnodeSignatureVerifierPostRequest, @@ -31,7 +30,7 @@ export async function getProofsOfValidation( const dbPubkeysDetailsFiltered = Object.fromEntries( Object.entries(dbPubkeysDetails).filter(([, details]) => shareDataWithDappnode || details.tag === "stader") ); - if (isEmpty(dbPubkeysDetailsFiltered)) return []; + if (Object.keys(dbPubkeysDetailsFiltered).length === 0) return []; // For each pubkey, get the proof of validation from the signer const proofsOfValidations = await Promise.all( Object.keys(dbPubkeysDetailsFiltered).map(async (pubkey) => { diff --git a/packages/brain/src/modules/cron/trackValidatorsPerformance/setActiveValidatorsLoadedInBrain.ts b/packages/brain/src/modules/cron/trackValidatorsPerformance/setActiveValidatorsLoadedInBrain.ts index 42317dfc..05c70e56 100644 --- a/packages/brain/src/modules/cron/trackValidatorsPerformance/setActiveValidatorsLoadedInBrain.ts +++ b/packages/brain/src/modules/cron/trackValidatorsPerformance/setActiveValidatorsLoadedInBrain.ts @@ -1,4 +1,3 @@ -import { isEmpty } from "lodash-es"; import { BeaconchainApi } from "../../apiClients/index.js"; import { ValidatorStatus } from "../../apiClients/types.js"; import { BrainDataBase } from "../../db/index.js"; @@ -51,14 +50,14 @@ async function getValidatorIndexesAndSaveInDb({ brainDb: BrainDataBase; }): Promise { const brainDbData = brainDb.getData(); - if (isEmpty(brainDbData)) return []; + if (Object.keys(brainDbData).length === 0) return []; // Get validator indexes from brain db const validatorIndexes: string[] = []; const validatorPubkeysWithNoIndex: string[] = []; // Iterate over brain db data and push the indexes to the array for (const [pubkey, details] of Object.entries(brainDbData)) { - if (details.index) validatorIndexes.push(details.index.toString()); + if (details.index !== undefined) validatorIndexes.push(details.index.toString()); else validatorPubkeysWithNoIndex.push(pubkey); } @@ -79,7 +78,7 @@ async function getValidatorIndexesAndSaveInDb({ const { pubkey } = validatorData.validator; const index = validatorData.index; validatorIndexes.push(index); - brainDb.updateValidators({ validators: { [pubkey]: { ...brainDbData[pubkey], index: parseInt(index) } } }); + brainDb.updateValidators({ validators: { [pubkey]: { index: parseInt(index) } } }); } } diff --git a/packages/brain/src/modules/db/index.ts b/packages/brain/src/modules/db/index.ts index 87ee6c22..f90bbad4 100644 --- a/packages/brain/src/modules/db/index.ts +++ b/packages/brain/src/modules/db/index.ts @@ -1,4 +1,4 @@ -import { StakingBrainDb, StakingBrainDbUpdate, PubkeyDetails } from "./types.js"; +import { StakingBrainDb, StakingBrainDbUpdate } from "./types.js"; import { LowSync } from "lowdb"; import { JSONFileSync } from "lowdb/node"; import fs from "fs"; @@ -6,8 +6,7 @@ import logger from "../logger/index.js"; import { Web3SignerApi, ValidatorApi } from "../apiClients/index.js"; import { params } from "../../params.js"; import { isValidTag } from "./utils.js"; -import { shortenPubkey, isValidBlsPubkey, isValidEcdsaPubkey } from "@stakingbrain/common"; -import { isEmpty } from "lodash-es"; +import { shortenPubkey, isValidBlsPubkey, isValidEcdsaPubkey, validateBuilderConfig } from "@stakingbrain/common"; import { BrainDbError } from "./error.js"; // TODO: @@ -34,10 +33,17 @@ export class BrainDataBase extends LowSync { this.dbName = dbName; } + public override write(): void { + super.write(); + // The JSON adapter replaces the file atomically; restore the private mode on + // the replacement too, since records may contain builder authorization data. + this.setOwnerWriteRead(); + } + /** * Returns the database content. * - If the database is empty, it will return an empty object - * - If the database is corrupted, it will erase it and create a new empty database with the correct permissions + * - Refuses unreadable or invalid data so reload cannot mistake corruption for deleted validators * * @returns an object in format StakingBrainDb (it could be an empty object {}) */ @@ -47,21 +53,21 @@ export class BrainDataBase extends LowSync { } /** - * Initializes the database: IMPORTANT! this method must not throw an error since it will be called from index.ts + * Initializes the database. Failure must stop startup before destructive reconciliation runs. * - If the database file doesn't exist, it will attempt to perform a migration - * - If the migration fails, it will create a new empty database + * - If migration fails, startup fails with the original database preserved * - If the database file exists, it will validate it */ public async initialize(signerApi: Web3SignerApi, validatorApi: ValidatorApi): Promise { try { // Important! .read() method must be called before accessing brainDb.data otherwise it will be empty object - this.read(); + this.validateDb(); // If db.json doesn't exist, db.data will be an empty object - if (isEmpty(this.data)) await this.databaseMigration(signerApi, validatorApi); + if (Object.keys(this.data ?? {}).length === 0) await this.databaseMigration(signerApi, validatorApi); else this.setOwnerWriteRead(); } catch (e) { logger.error(`unable to initialize the db ${this.dbName}`, e); - this.validateDb(); + throw e; } } @@ -73,21 +79,32 @@ export class BrainDataBase extends LowSync { } /** - * Adds 1 or more public keys and their details to the database + * Check new records before an external signer import changes persistent state. */ + public validateNewValidators(validators: StakingBrainDb): void { + this.validateDb(); + const existing = new Set(Object.keys(this.data).map((pubkey) => pubkey.toLowerCase())); + const additions = Object.fromEntries( + Object.entries(validators).filter(([pubkey]) => !existing.has(pubkey.toLowerCase())) + ); + this.ensureDbMaxSize(additions); + this.validateAddValidators(additions); + } + + /** Adds new validator records while preserving the settings of existing keys. */ public addValidators({ validators }: { validators: StakingBrainDb }): void { try { this.validateDb(); // Remove pubkeys that already exist + const existing = new Set(Object.keys(this.data).map((pubkey) => pubkey.toLowerCase())); if (this.data) for (const pubkey of Object.keys(validators)) - if (this.data[pubkey]) { + if (existing.has(pubkey.toLowerCase())) { logger.warn(`Pubkey ${pubkey} already in the database`); delete validators[pubkey]; } - this.ensureDbMaxSize(validators); - this.validateAddValidators(validators); + this.validateNewValidators(validators); this.data = { ...this.data, ...validators }; this.write(); } catch (e) { @@ -96,12 +113,13 @@ export class BrainDataBase extends LowSync { } /** - * Updates 1 or more validators in db. The fields available to update are feeRecipient and index + * Update supplied validator settings without erasing omitted settings. */ public updateValidators({ validators }: { validators: StakingBrainDbUpdate }): void { try { this.validateDb(); this.validateUpdateValidators(validators); + const updatedData = { ...this.data }; if (this.data) for (const pubkey of Object.keys(validators)) { if (!this.data[pubkey]) { @@ -109,11 +127,11 @@ export class BrainDataBase extends LowSync { logger.warn(`Pubkey ${pubkey} not found in the database`); delete validators[pubkey]; } else { - this.data[pubkey].feeRecipient = validators[pubkey].feeRecipient; - this.data[pubkey].index = validators[pubkey].index; + updatedData[pubkey] = { ...this.data[pubkey], ...validators[pubkey] }; } } - + this.ensureDbMaxSize(updatedData, true); + this.data = updatedData; this.write(); } catch (e) { throw new BrainDbError(`Unable to update pubkeys ${Object.keys(validators).join(", ")}. ${e.message}`); @@ -128,12 +146,18 @@ export class BrainDataBase extends LowSync { try { this.validateDb(); if (!this.data) return; + const storedKeys = new Map(Object.keys(this.data).map((key) => [key.toLowerCase(), key])); + let changed = false; for (const pubkey of pubkeys) { - if (!this.data[pubkey]) { + const storedKey = storedKeys.get(pubkey.toLowerCase()); + if (!storedKey) { logger.warn(`Pubkey ${pubkey} not found in the database`); - } else delete this.data[pubkey]; - this.write(); + } else { + delete this.data[storedKey]; + changed = true; + } } + if (changed) this.write(); } catch (e) { throw new BrainDbError(`Unable to delete pubkeys ${Object.keys(pubkeys).join(", ")}. ${e.message}`); } @@ -141,22 +165,6 @@ export class BrainDataBase extends LowSync { // PRIVATE METHODS // - /** - * Cleans the database: - * - Writes an empty object to the database - * - On error deletes the database file and creates a new one - */ - private pruneDatabase(): void { - try { - this.data = {}; - this.write(); - } catch (e) { - logger.error(`Unable to prune database. Creating a new one...`, e); - if (fs.existsSync(this.dbName)) fs.unlinkSync(this.dbName); - this.createJsonFileAndPermissions(); - } - } - /** * Set write permissions to the database file */ @@ -174,19 +182,26 @@ export class BrainDataBase extends LowSync { /** * Validates the database it is in the correct format: * - Creates JSON file if it doesn't exist - * - Deletes the database if it is corrupted and creates a new one + * - Throws on corrupted data without replacing it */ private validateDb(): void { try { - this.read(); - if (isEmpty(this.data)) { + if (!fs.existsSync(this.dbName)) { + if (Object.keys(this.data ?? {}).length > 0) + throw new BrainDbError("Database file disappeared while validators were loaded"); logger.warn(`Database file ${this.dbName} not found. Creating it...`); this.createJsonFileAndPermissions(); } + // LowSync.read() ignores falsy JSON values, so inspect the adapter result directly. + const persisted = this.adapter.read(); + if (!persisted || typeof persisted !== "object" || Array.isArray(persisted)) + throw new BrainDbError("Expected a validator database object"); + this.validateAddValidators(persisted); + this.data = persisted; } catch (e) { - logger.error(`The database is corrupted. Cleaning database`, e); - this.pruneDatabase(); - this.read(); + throw new BrainDbError( + `Unable to read database ${this.dbName}; refusing to reconcile validator keys. ${e.message}` + ); } } @@ -202,10 +217,10 @@ export class BrainDataBase extends LowSync { * - 1 pubkey: 4KB * Average pubkey size to be added: 213 bytes */ - private ensureDbMaxSize(validators: StakingBrainDb): void { + private ensureDbMaxSize(validators: StakingBrainDb, replace = false): void { const MAX_DB_SIZE = 6 * 1024 * 1024; - const dbSize = fs.statSync(this.dbName).size; - const pubkeysSize = Buffer.byteLength(JSON.stringify(validators)); + const dbSize = replace ? 0 : fs.statSync(this.dbName).size; + const pubkeysSize = Buffer.byteLength(JSON.stringify(validators, null, 2)); if (dbSize + pubkeysSize > MAX_DB_SIZE) throw new BrainDbError( `The database is too big. Max size is ${MAX_DB_SIZE} bytes. Current size is ${dbSize} bytes. Data to be added is ${pubkeysSize} bytes. ` @@ -228,8 +243,8 @@ export class BrainDataBase extends LowSync { while (retries < 10) { try { logger.info(`Database file ${this.dbName} not found. Attemping to perform migration...`); - // Create json file - this.createJsonFileAndPermissions(); + this.validateDb(); + if (Object.keys(this.data).length > 0) return; // Fetch public keys from signer API const pubkeys = (await signerApi.listRemoteKeys()).data.map((keystore) => keystore.validating_pubkey); if (pubkeys.length === 0) { @@ -237,39 +252,18 @@ export class BrainDataBase extends LowSync { return; } else logger.info(`Found ${pubkeys.length} public keys to migrate`); - let feeRecipient = ""; - await validatorApi - .getFeeRecipient(pubkeys[0]) - .then((response) => { - feeRecipient = response.data.ethaddress; - }) - .catch((e) => { - logger.error(`Unable to fetch fee recipient for ${pubkeys[0]}. Setting default ${params.burnAddress}}`, e); - // TODO: consider setting MEV fee recipient. - feeRecipient = params.burnAddress; - }); - - logger.info(`The fee recipient to be used in the migration is ${feeRecipient}`); - - this.addValidators({ - validators: pubkeys.reduce( - (acc, pubkey) => { - acc[pubkey] = { - tag: params.defaultTag, - feeRecipient, - automaticImport: false - }; - return acc; - }, - {} as { [pubkey: string]: PubkeyDetails } - ) - }); + const validators: StakingBrainDb = {}; + for (const pubkey of pubkeys) { + const feeRecipient = (await validatorApi.getFeeRecipient(pubkey)).data.ethaddress; + validators[pubkey] = { tag: params.defaultTag, feeRecipient, automaticImport: false }; + } + this.addValidators({ validators }); logger.info(`Database migration completed`); return; } catch (e) { - if (retries < 30) { - retries++; + retries++; + if (retries < 10) { logger.error(`Unable to perform database migration. Retrying in 6 seconds...`, e); await new Promise((resolve) => { logger.info(`Retrying database migration for ${(retries + 1).toString()} time...`); @@ -308,15 +302,16 @@ export class BrainDataBase extends LowSync { } // FeeRecipient - if (!pubkeyDetails.feeRecipient) { - errors.push(`\n pubkey ${pubkeySubstr}: feeRecipient address is missing`); - } else { + if ("feeRecipient" in pubkeyDetails) { if (typeof pubkeyDetails.feeRecipient !== "string") errors.push(`\n pubkey ${pubkeySubstr}: feeRecipient address is invalid, must be in string format`); - if (!isValidEcdsaPubkey(pubkeyDetails.feeRecipient)) + if (typeof pubkeyDetails.feeRecipient !== "string" || !isValidEcdsaPubkey(pubkeyDetails.feeRecipient)) errors.push(`\n pubkey ${pubkeySubstr}: fee recipient is invalid`); } + if ("builderConfig" in pubkeyDetails && pubkeyDetails.builderConfig !== null) + validateBuilderConfig(pubkeyDetails.builderConfig); }); + if (errors.length > 0) throw new BrainDbError(errors.join("\n")); } private validateAddValidators(validators: StakingBrainDb): void { @@ -355,6 +350,10 @@ export class BrainDataBase extends LowSync { } // AutomaticImport + if (pubkeyDetails.importPending !== undefined && typeof pubkeyDetails.importPending !== "boolean") + errors.push(`\n pubkey ${pubkeySubstr}: importPending must be a boolean`); + if ("builderConfig" in pubkeyDetails && pubkeyDetails.builderConfig !== null) + validateBuilderConfig(pubkeyDetails.builderConfig); if (typeof pubkeyDetails.automaticImport === "undefined") { errors.push(`\n pubkey ${pubkeySubstr}: automaticImport is missing`); } else { diff --git a/packages/brain/src/modules/db/types.ts b/packages/brain/src/modules/db/types.ts index 2a400efe..4a885288 100644 --- a/packages/brain/src/modules/db/types.ts +++ b/packages/brain/src/modules/db/types.ts @@ -1,4 +1,4 @@ -import { Tag } from "@stakingbrain/common"; +import { BuilderConfig, Tag } from "@stakingbrain/common"; /** * DbSlot represents the line in the database for a given public key: @@ -13,7 +13,7 @@ export interface StakingBrainDb { } export interface StakingBrainDbUpdate { - [pubkey: string]: Omit; + [pubkey: string]: Partial>; } export interface CustomValidators extends PubkeyDetails { @@ -24,7 +24,11 @@ export interface PubkeyDetails { tag: Tag; feeRecipient: string; automaticImport: boolean; + // Durable import intent: absence from the signer is not proof that an in-flight import failed. + importPending?: boolean; index?: number; // index of the validator. Only available if the validator is active. + // Absent: unmanaged. Object: original overrides. Null: persistently reset to VC defaults. + builderConfig?: BuilderConfig | null; } export const nonEditableFeeRecipientTags = ["rocketpool", "stader", "stakewise", "lido"] as const; diff --git a/packages/brain/src/modules/db/withValidatorOperationLock.ts b/packages/brain/src/modules/db/withValidatorOperationLock.ts new file mode 100644 index 00000000..b181fdf0 --- /dev/null +++ b/packages/brain/src/modules/db/withValidatorOperationLock.ts @@ -0,0 +1,20 @@ +import type { BrainDataBase } from "./index.js"; + +const pendingOperations = new WeakMap>(); + +/** Keep database snapshots and their client writes ordered, including across awaits. */ +export async function withValidatorOperationLock(brainDb: BrainDataBase, operation: () => Promise): Promise { + const previous = pendingOperations.get(brainDb) ?? Promise.resolve(); + let release!: () => void; + const current = new Promise((resolve) => { + release = resolve; + }); + pendingOperations.set(brainDb, current); + await previous; + try { + return await operation(); + } finally { + release(); + if (pendingOperations.get(brainDb) === current) pendingOperations.delete(brainDb); + } +} diff --git a/packages/brain/test/integration/modules/cron/trackValidatorsPerformance/makeAllCronPerformanceCalls.int.test.ts b/packages/brain/test/integration/modules/cron/trackValidatorsPerformance/makeAllCronPerformanceCalls.int.test.ts deleted file mode 100644 index 2f18e322..00000000 --- a/packages/brain/test/integration/modules/cron/trackValidatorsPerformance/makeAllCronPerformanceCalls.int.test.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { BeaconchainApi } from "../../../../../src/modules/apiClients/beaconchain/index.js"; - -import { expect } from "chai"; -import { Network } from "@stakingbrain/common"; -import { ApiParams, ValidatorStatus } from "../../../../../src/modules/apiClients/types.js"; -import { BeaconchainApiError } from "../../../../../src/modules/apiClients/beaconchain/error.js"; - -describe.skip("Beaconchain API Tests", function () { - let api: BeaconchainApi; - const validatorIndexes = ["1802459", "1802425"]; - before(async function () { - const apiParams: ApiParams = { - baseUrl: "http://beacon-chain.holesky.dncore.dappnode:3500" // replace with actual API URL - }; - - api = new BeaconchainApi(apiParams, Network.Holesky); // Network matters for number of slots in epoch - }); - - // first call done in cron, to check what epoch to process - it("should retrieve finality checkpoints for the head state", async function () { - const checkpointsResponse = await api.getStateFinalityCheckpoints({ stateId: "head" }); - - expect(checkpointsResponse).to.have.property("data"); - expect(checkpointsResponse.data).to.have.property("finalized"); - expect(checkpointsResponse.data.finalized).to.have.property("epoch"); - - // Check that finalized.epoch exists and is a number (converted from string) - const epoch = parseInt(checkpointsResponse.data.finalized.epoch, 10); - expect(epoch).to.be.a("number"); - expect(epoch).to.not.be.NaN; // Ensure it's a valid number - }); - - it("should retrieve ids of validators in the finalized state that are active_ongoing", async function () { - this.timeout(10000); // Set timeout to 10 seconds (10000 ms). This takes longer - const response = await api.postStateValidators({ - body: { - ids: validatorIndexes, - statuses: [ValidatorStatus.ACTIVE_ONGOING] - }, - stateId: "finalized" - }); - const indexToCheck1 = "1802459"; // First index to check - const indexToCheck2 = "1802425"; // Second index to check - - // Check if the data array contains the indices you are looking for - const indicesInResponse = response.data.map((validator) => validator.index); - - expect(indicesInResponse).to.include(indexToCheck1); - expect(indicesInResponse).to.include(indexToCheck2); - }); - - it("should be able to check if node is syncing", async function () { - const { el_offline, is_syncing } = (await api.getSyncingStatus()).data; - - // Check that both el_offline and is_syncing are booleans - expect(el_offline).to.be.a("boolean"); - expect(is_syncing).to.be.a("boolean"); - }); - - it("should retrieve attestation rewards & block proposal duties for a specific epoch", async function () { - this.timeout(10000); // Set timeout to 10 seconds (10000 ms). This takes longer - - const epoch = await api.getStateFinalityCheckpoints({ stateId: "head" }); - const duties = await api.getProposerDuties({ epoch: epoch.data.finalized.epoch }); - - // Assume we want to check the first duty slot - const slotToCheck = duties.data[0]?.slot; - - if (slotToCheck) { - try { - // Attempt to retrieve the block header - const blockHeaderResponse = await api.getBlockHeader({ blockId: slotToCheck }); - expect(blockHeaderResponse.data.header.message.proposer_index).to.be.equal(duties.data[0].validator_index); - } catch (error) { - // Check if the error is a BeaconchainApiError - expect(error).to.be.instanceOf(BeaconchainApiError); // Ensure it's the correct error type - - // Assert that the error message includes "404" - expect(error.message).to.include("404"); - } - } else { - throw new Error("No slot available for duties data"); - } - - // Retrieve attestation rewards - const rewardsResponse = await api.getAttestationsRewards({ - epoch: epoch.data.finalized.epoch, - pubkeysOrIndexes: [] - }); - expect(rewardsResponse.data).to.have.property("total_rewards"); - }); -}); diff --git a/packages/brain/test/unit/modules/ cron/trackValidatorsPerformance/getActiveValidatorsLoadedInBrain.unit.test.ts b/packages/brain/test/unit/modules/ cron/trackValidatorsPerformance/getActiveValidatorsLoadedInBrain.unit.test.ts index a6718431..605a7cce 100644 --- a/packages/brain/test/unit/modules/ cron/trackValidatorsPerformance/getActiveValidatorsLoadedInBrain.unit.test.ts +++ b/packages/brain/test/unit/modules/ cron/trackValidatorsPerformance/getActiveValidatorsLoadedInBrain.unit.test.ts @@ -139,7 +139,7 @@ describe("Cron - trackValidatorsPerformance - getActiveValidatorsLoadedInBrain", it("should return the active validators loaded in the brain and not update validator index one and update validator index two in db", async () => { const beaconchainApi = new BeaconchainApiMock( { baseUrl: "http://localhost:3000", apiPath: "", authToken: "" }, - Network.Holesky + Network.Hoodi ); const brainDb = new BrainDataBaseMock("test.json"); const validatorsDataPerEpochMap: ValidatorsDataPerEpochMap = new Map(); diff --git a/packages/brain/test/unit/modules/apiClients/cron.unit.test.ts b/packages/brain/test/unit/modules/apiClients/cron.unit.test.ts deleted file mode 100644 index 67fd4310..00000000 --- a/packages/brain/test/unit/modules/apiClients/cron.unit.test.ts +++ /dev/null @@ -1,314 +0,0 @@ -import { expect } from "chai"; -import { before } from "mocha"; -import { ValidatorApi, Web3SignerApi } from "../../../../src/modules/apiClients/index.js"; -import { execSync } from "node:child_process"; -import { BrainDataBase } from "../../../../src/modules/db/index.js"; -import fs from "fs"; -import path from "path"; -import { reloadValidators } from "../../../../src/modules/cron/reloadValidators/index.js"; -import { Network } from "@stakingbrain/common"; -import { PubkeyDetails } from "../../../../src/modules/db/types.js"; - -describe.skip("Cron: Prater", () => { - const defaultFeeRecipient = "0x0000000000000000000000000000000000000000"; - - //The order of this array is important for the tests - const pubkeys = [ - "0xa2cc280ce811bb680cba309103e23dc3c9902f2a08541c6737e8adfe8198e796023b959fc8aadfad39499b56ec3dd184", - "0x86d25af52627204ab822a20ac70da6767952841edbcb0b83c84a395205313661de5f7f76efa475a46f45fa89d95c1dd7", - "0x821a80380122281580ba8a56cd21956933d43c62fdc8f5b4ec31b2c620e8534e80b6b816c9a2cc8d25568dc4ebcfd47a", - "0x8f2b698583d69c7a78b4482871282602adb7fb47a1aab66c63feb48e7b9245dad77b82346e0201328d66a8b4d483b716", - "0xa1735a0dd72205dae313c36d7d17f5b06685944c8886ddac530e5aedbe1fca0c8003e7e274ec1b4ddd08b884f5b9a830" - ]; - - const keystoresPath = path.resolve(process.cwd(), "keystores"); - - const keystorePass = "stakingbrain"; - - const stakerSpecs = { - network: "prater" as Network, - consensusClients: [ - /*{ - name: "Prysm", - containerName: - "DAppNodePackage-validator.prysm-prater.dnp.dappnode.eth", - token: - "0xd59b8238ecaeb255d62c85c6ca8aee185870bd7a27e43f85fd2658267036d94a", - },*/ - { - name: "Lighthouse", - containerName: "DAppNodePackage-validator.lighthouse-prater.dnp.dappnode.eth", - token: "api-token-0x0200e6ce18e26fd38caca7ae1bfb9e2bba7efb20ed2746ad17f2f6dda44603152d" - } - /*{ - name: "Nimbus", - containerName: - "DAppNodePackage-beacon-validator.nimbus-prater.dnp.dappnode.eth", - token: - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.MxwOozSH-TLbW_XKepjyYDHm2IT8Ki0tD3AHuajfNMg", - }, - { - name: "Teku", - containerName: "DAppNodePackage-validator.teku-prater.dnp.dappnode.eth", - token: "cd4892ca35d2f5d3e2301a65fc7aa660", - - },*/ - ] - }; - - for (const consensusClient of stakerSpecs.consensusClients) { - describe(`Consensus client: ${consensusClient.name}`, () => { - let validatorApi: ValidatorApi; - let signerApi: Web3SignerApi; - let brainDb: BrainDataBase; - let signerUrl: string; - - const testDbName = "testDb.json"; - - const signerContainerName = "DAppNodePackage-web3signer.web3signer-prater.dnp.dappnode.eth"; - - const host = "web3signer.web3signer-prater.dappnode"; - - before(() => { - const consensusIp = execSync( - `docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' ${consensusClient.containerName}` - ) - .toString() - .trim(); - validatorApi = new ValidatorApi( - { - baseUrl: `http://${consensusIp}:3500`, - authToken: consensusClient.token - }, - stakerSpecs.network - ); - - const signerIp = execSync( - `docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' ${signerContainerName}` - ) - .toString() - .trim(); - signerApi = new Web3SignerApi( - { - baseUrl: `http://${signerIp}:9000`, - host - }, - stakerSpecs.network - ); - signerUrl = `http://${signerIp}:9000`; - - if (fs.existsSync(testDbName)) fs.unlinkSync(testDbName); - brainDb = new BrainDataBase(testDbName); - }); - - beforeEach(async function () { - this.timeout(40000); - - console.log("Cleaning DB, validator and signer"); - - //Clean DB - fs.writeFileSync(testDbName, JSON.stringify({})); - - //Clean validator - await validatorApi.deleteRemoteKeys({ pubkeys }); - - //Clean signer - await signerApi.deleteRemoteKeys({ pubkeys }); - }); - - it("Should post fee recipient in DB to validator", async () => { - await addSampleValidatorsToAllSources(1); - - const pubkeyToTest = pubkeys[0]; - - const feeRecipient = "0x1111111111111111111111111111111111111111"; - - brainDb.updateValidators({ - validators: { - [pubkeyToTest]: { - feeRecipient - } - } - }); - - //Check that fee recipient has changed in validator - await reloadValidators(signerApi, signerUrl, validatorApi, brainDb); - - const validatorFeeRecipient = await validatorApi.getFeeRecipient(pubkeyToTest); - - expect(validatorFeeRecipient.data.ethaddress).to.be.equal(feeRecipient); - expect(validatorFeeRecipient.data.ethaddress).to.be.equal(feeRecipient); - }).timeout(15000); - - it("Should remove 1 keystore from signer to match pubkeys in DB", async () => { - addSampleValidatorsToDB(1); - await addSampleKeystoresToSigner(2); - - await reloadValidators(signerApi, signerUrl, validatorApi, brainDb); - - const signerPubkeys = await signerApi.listRemoteKeys(); - const dbPubkeys = Object.keys(brainDb.getData()); - - expect(signerPubkeys.data.length).to.be.equal(1); - expect(dbPubkeys.length).to.be.equal(1); - - expect(signerPubkeys.data[0].validating_pubkey).to.be.equal(dbPubkeys[0]); - }).timeout(15000); - - it("Should remove 1 keystore from DB to match keystores in signer", async () => { - addSampleValidatorsToDB(2); - await addSampleKeystoresToSigner(1); - - await reloadValidators(signerApi, signerUrl, validatorApi, brainDb); - - const signerPubkeys = await signerApi.listRemoteKeys(); - const dbPubkeys = Object.keys(brainDb.getData()); - - expect(signerPubkeys.data.length).to.be.equal(1); - expect(dbPubkeys.length).to.be.equal(1); - - expect(signerPubkeys.data[0].validating_pubkey).to.be.equal(dbPubkeys[0]); - }).timeout(15000); - - it("Should remove all the pubkeys in DB and keystores in signer to match each other", async () => { - addSampleValidatorsToDB(2); - await addSampleKeystoresToSigner(2); - - brainDb.deleteValidators([pubkeys[0]]); - await signerApi.deleteRemoteKeys({ pubkeys: [pubkeys[1]] }); - - await reloadValidators(signerApi, signerUrl, validatorApi, brainDb); - - const signerPubkeys = await signerApi.listRemoteKeys(); - const dbPubkeys = Object.keys(brainDb.getData()); - - expect(signerPubkeys.data.length).to.be.equal(0); - expect(dbPubkeys.length).to.be.equal(0); - }).timeout(15000); - - it("Should keep all the keystores in the signer and the pubkeys in the DB", async () => { - addSampleValidatorsToDB(2); - await addSampleKeystoresToSigner(2); - - await reloadValidators(signerApi, signerUrl, validatorApi, brainDb); - - const signerPubkeys = await signerApi.listRemoteKeys(); - const dbPubkeys = Object.keys(brainDb.getData()); - - expect(signerPubkeys.data.length).to.be.equal(2); - expect(dbPubkeys.length).to.be.equal(2); - - //Expect the same pubkeys in both sources (could not be in the same order) - expect(signerPubkeys.data[0].validating_pubkey).to.be.oneOf(dbPubkeys); - expect(signerPubkeys.data[1].validating_pubkey).to.be.oneOf(dbPubkeys); - }).timeout(15000); - - it("Should delete all pubkeys from validator with empty DB", async () => { - await addSamplePubkeysToValidator(1); - - console.log("Added pubkeys to validator"); - - await reloadValidators(signerApi, signerUrl, validatorApi, brainDb); - - console.log("Validators reloaded"); - - const validatorPubkeys = await validatorApi.getRemoteKeys(); - - console.log("Got validator pubkeys"); - - expect(validatorPubkeys.data.length).to.be.equal(0); - }).timeout(50000); - - it("Should add the pubkeys in the DB to the validator", async () => { - addSampleValidatorsToDB(2); - await addSampleKeystoresToSigner(2); - - const pubkeysToTest = pubkeys.slice(0, 2); - - await reloadValidators(signerApi, signerUrl, validatorApi, brainDb); - - const validatorPubkeys = await validatorApi.getRemoteKeys(); - - expect(validatorPubkeys.data.length).to.be.equal(2); - - //Expect the same pubkeys in both sources (could not be in the same order) - expect(validatorPubkeys.data[0].pubkey).to.be.oneOf(pubkeysToTest); - expect(validatorPubkeys.data[1].pubkey).to.be.oneOf(pubkeysToTest); - }).timeout(15000); - - // AUXILIARY FUNCTIONS // - - async function addSampleValidatorsToAllSources(nValidators = 5) { - if (nValidators > pubkeys.length) nValidators = pubkeys.length; - - if (nValidators < 1) nValidators = 1; - - await addSampleKeystoresToSigner(nValidators); - - addSampleValidatorsToDB(nValidators); - - await addSamplePubkeysToValidator(nValidators); - } - - async function addSampleKeystoresToSigner(nKeystores = 5) { - if (nKeystores > pubkeys.length) nKeystores = pubkeys.length; - - if (nKeystores < 1) nKeystores = 1; - - const keystoresPaths = fs.readdirSync(keystoresPath).filter((file) => file.endsWith(".json")); - - //Reduce keystoresPaths to nValidators - keystoresPaths.splice(nKeystores); - - const keystores = keystoresPaths.map((file) => fs.readFileSync(path.join(keystoresPath, file)).toString()); - - const passwords = Array(keystores.length).fill(keystorePass); - - await signerApi.importRemoteKeys({ - keystores, - passwords - }); - } - - function addSampleValidatorsToDB(nValidators = 5) { - if (nValidators > pubkeys.length) nValidators = pubkeys.length; - - if (nValidators < 1) nValidators = 1; - - const pubkeysToTest = pubkeys.slice(0, nValidators); - - brainDb.addValidators({ - validators: pubkeysToTest.reduce( - (acc, pubkey) => { - acc[pubkey] = { - tag: "solo", - feeRecipient: defaultFeeRecipient, - automaticImport: true - }; - return acc; - }, - {} as { [pubkey: string]: PubkeyDetails } - ) - }); - } - - async function addSamplePubkeysToValidator(nPubkeys = 5) { - if (nPubkeys > pubkeys.length) nPubkeys = pubkeys.length; - - if (nPubkeys < 1) nPubkeys = 1; - - const pubkeysToTest = pubkeys.slice(0, nPubkeys); - - //Add pubkeys to validator - const signerUrl = signerApi.getBaseUrl(); - - await validatorApi.postRemoteKeys({ - remote_keys: pubkeysToTest.map((pubkey) => ({ - pubkey, - url: signerUrl - })) - }); - } - }); - } -}); diff --git a/packages/brain/test/unit/modules/apiClients/dappmanagerApi.unit.test.ts b/packages/brain/test/unit/modules/apiClients/dappmanagerApi.unit.test.ts index 884e3d7f..74b0ec24 100644 --- a/packages/brain/test/unit/modules/apiClients/dappmanagerApi.unit.test.ts +++ b/packages/brain/test/unit/modules/apiClients/dappmanagerApi.unit.test.ts @@ -3,7 +3,7 @@ import { DappmanagerApi } from "../../../../src/modules/apiClients/index.js"; import { NotificationType } from "../../../../src/modules/apiClients/dappmanager/types.js"; describe.skip("Dappmanager API", () => { - const dappmanagerApi = new DappmanagerApi({ baseUrl: "http://my.dappnode" }, Network.Holesky); + const dappmanagerApi = new DappmanagerApi({ baseUrl: "http://my.dappnode" }, Network.Hoodi); it("should send a notification to the dappmanager", async () => { const title = "Test title"; diff --git a/packages/brain/test/unit/modules/apiClients/fetchValidatorIndex.unit.test.ts b/packages/brain/test/unit/modules/apiClients/fetchValidatorIndex.unit.test.ts deleted file mode 100644 index 38baeae2..00000000 --- a/packages/brain/test/unit/modules/apiClients/fetchValidatorIndex.unit.test.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { expect } from "chai"; -import { Network } from "@stakingbrain/common"; -import { BlockExplorerApi } from "../../../../src/modules/apiClients"; -import { ApiParams } from "../../../../src/modules/apiClients/types.js"; - -describe.skip("Test for fetching validator indexes in every available network", () => { - const networks: Network[] = [Network.Mainnet, Network.Gnosis, Network.Lukso, Network.Holesky]; - - networks.forEach((network) => { - it(`should return data corresponding to every validator PK for ${network}`, async () => { - const apiParams = beaconchaApiParamsMap.get(network); - if (!apiParams) { - throw new Error(`API parameters for ${network} are not defined`); - } - - const beaconchaApi = new BlockExplorerApi(apiParams, network); - const testParams = networkTestMap.get(network); - if (!testParams) { - throw new Error(`Test parameters for ${network} are not defined`); - } - - const allValidatorsInfo = await beaconchaApi.fetchAllValidatorsInfo({ - pubkeys: testParams.pubkeys - }); - - // Assuming allValidatorsInfo[0].data contains an array of validators - const validators = allValidatorsInfo[0].data; - - // This loop assumes that the API returns validators in the same order as requested. - // If this assumption is not valid, additional logic is needed to match returned validators to expected values. - testParams.pubkeys.forEach((pubkey, index) => { - const validator = validators.find((v) => v.pubkey === pubkey); - if (!validator) { - throw new Error(`Validator with pubkey ${pubkey} not found for ${network}`); - } - expect(validator.validatorindex).to.equal(testParams.indexes[index]); - }); - }); - }); -}); - -const networkTestMap = new Map([ - [ - "mainnet", - { - pubkeys: [ - "0x80000001677f23a227dfed6f61b132d114be83b8ad0aa5f3c5d1d77e6ee0bf5f73b0af750cc34e8f2dae73c21dc36f4a", - "0x800006d4b1026b6149168b342e6883d48ede9539202cc414448b1b796394440a5401e8d6620e65d7c77654bf1db199b1" - ], - indexes: [8499, 347967] - } - ], - [ - "gnosis", - { - pubkeys: [ - "0x8000385f61788781c3514322c14bf4ef51bfee5ed743872ac9ea37a3a11e1e3496f4f5e252ef33fdb5bf3684c13ed210", - "0x8000e5f66ffb58ef1f8bf8994172da85c77142cc7be24a63d53e572f5ba1149466c5aa0e6a383b2cddf83467cf8e688a" - ], - indexes: [57102, 81200] - } - ], - [ - "lukso", - { - pubkeys: [ - "0x8000460289c3435bb6af636314142aed624089edb56dc12fc91475176592067ae4216bfe95b651c3af7c1c491a519134", - "0x80012f0aaa40e9cccf47ea7255ec380976d32c9e92fec0701926d9457e9510d108ae8fd5bea82bf13b85cc82de2729ac" - ], - indexes: [24693, 4272] - } - ], - [ - "holesky", - { - pubkeys: [ - "0x800000b3884235f70b06fec68c19642fc9e81e34fbe7f1c0ae156b8b45860dfe5ac71037ae561c2a759ba83401488e18", - "0x800009f644592de8d2de0da0caca00f26fd6fb3d7f99f57101bbbfb45d4b166f8dbe5fd82b3611e6e90fe323de955bd2" - ], - indexes: [886680, 68945] - } - ] -]); - -// TODO: move below to common - -const beaconchaApiParamsMap = new Map([ - [ - "mainnet", - { - baseUrl: "https://beaconcha.in", - apiPath: "/api/v1/" - } - ], - [ - "gnosis", - { - baseUrl: "https://gnosischa.in", - apiPath: "/api/v1/" - } - ], - [ - "lukso", - { - baseUrl: "https://explorer.consensus.mainnet.lukso.network", - apiPath: "/api/v1/" - } - ], - [ - "holesky", - { - baseUrl: "https://holesky.beaconcha.in", - apiPath: "/api/v1/" - } - ] -]); diff --git a/packages/brain/test/unit/modules/apiClients/postgresClient.unit.test.ts b/packages/brain/test/unit/modules/apiClients/postgresClient.unit.test.ts deleted file mode 100644 index 4f9e9c0c..00000000 --- a/packages/brain/test/unit/modules/apiClients/postgresClient.unit.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { PostgresClient } from "../../../../src/modules/apiClients/index.js"; - -// This test must be executed with a real database connection - -describe.skip("Postgres client", function () { - this.timeout(10 * 1000); - // change the dbUrl on demmand - const dbUrl = "postgres://postgres:password@postgres.web3signer-holesky.dappnode:5432/web3signer"; - const postgresClient = new PostgresClient(dbUrl); - - it("should initialize the database", async () => { - await postgresClient.initialize(); - }); - - it("should get table size", async () => { - const tableSize = await postgresClient.getTableSize(); - console.log("Table size: ", tableSize); - }); - - it("should get validators data from the db", async () => { - const validatorIndexes = ["1802289", "1802258"]; - const data = await postgresClient.getEpochsDataMapForEpochRange({ - validatorIndexes, - startEpoch: 85232, - endEpoch: 85300 - }); - console.log("Validators data: ", data); - }); - - it("should delete the table and its enum types", async () => { - await postgresClient.deleteDatabaseTable(); - }); -}); diff --git a/packages/brain/test/unit/modules/apiClients/standard.unit.test.ts b/packages/brain/test/unit/modules/apiClients/standard.unit.test.ts new file mode 100644 index 00000000..85c55df3 --- /dev/null +++ b/packages/brain/test/unit/modules/apiClients/standard.unit.test.ts @@ -0,0 +1,51 @@ +import { expect } from "chai"; +import sinon from "sinon"; +import http from "node:http"; +import { EventEmitter } from "node:events"; +import { Network, RPC_TIMEOUT_MS } from "@stakingbrain/common"; +import { StandardApi } from "../../../../src/modules/apiClients/standard.js"; + +class ApiProbe extends StandardApi { + read(timeout?: number): Promise { + return this.request({ method: "GET", endpoint: "/test", timeout }); + } +} + +describe("HTTP request deadlines", () => { + let clock: sinon.SinonFakeTimers; + let request: http.ClientRequest; + let api: ApiProbe; + beforeEach(() => { + clock = sinon.useFakeTimers(); + request = new EventEmitter() as http.ClientRequest; + request.end = () => request; + request.destroy = sinon.stub().callsFake((error: Error) => { + request.emit("error", error); + return request; + }); + sinon.stub(http, "request").returns(request); + api = new ApiProbe({ baseUrl: "http://unused" }, Network.Mainnet); + }); + afterEach(() => sinon.restore()); + + it("settles stalled requests at the default deadline and releases timers", async () => { + const pending = api.read().catch((error: Error) => error); + await clock.tickAsync(RPC_TIMEOUT_MS); + expect(await pending) + .to.have.property("message") + .that.includes("timed out"); + expect(clock.countTimers()).to.equal(0); + }); + + it("clears the deadline after a successful response", async () => { + const pending = api.read(1000); + const response = new EventEmitter() as http.IncomingMessage; + response.statusCode = 200; + response.headers = { "content-type": "application/json" }; + request.emit("response", response); + response.emit("data", Buffer.from('{"ok":true}')); + response.emit("end"); + expect(await pending).to.deep.equal({ ok: true }); + expect(clock.countTimers()).to.equal(0); + }); +}); diff --git a/packages/brain/test/unit/modules/apiClients/validatorApi.unit.test.ts b/packages/brain/test/unit/modules/apiClients/validatorApi.unit.test.ts deleted file mode 100644 index c68c0c45..00000000 --- a/packages/brain/test/unit/modules/apiClients/validatorApi.unit.test.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { expect } from "chai"; -import { before } from "mocha"; -import { ValidatorApi } from "../../../../src/modules/apiClients/index.js"; -import { execSync } from "node:child_process"; -import { Network } from "@stakingbrain/common"; - -describe.skip("Validator API: Prater", () => { - const defaultFeeRecipient = "0x0000000000000000000000000000000000000000"; - const pubkeys = [ - "0xa2cc280ce811bb680cba309103e23dc3c9902f2a08541c6737e8adfe8198e796023b959fc8aadfad39499b56ec3dd184", - "0x86d25af52627204ab822a20ac70da6767952841edbcb0b83c84a395205313661de5f7f76efa475a46f45fa89d95c1dd7", - "0x821a80380122281580ba8a56cd21956933d43c62fdc8f5b4ec31b2c620e8534e80b6b816c9a2cc8d25568dc4ebcfd47a", - "0x8f2b698583d69c7a78b4482871282602adb7fb47a1aab66c63feb48e7b9245dad77b82346e0201328d66a8b4d483b716", - "0xa1735a0dd72205dae313c36d7d17f5b06685944c8886ddac530e5aedbe1fca0c8003e7e274ec1b4ddd08b884f5b9a830" - ]; - const stakerSpecs = { - network: "prater" as Network, - consensusClients: [ - { - name: "Prysm", - containerName: "DAppNodePackage-validator.prysm-prater.dnp.dappnode.eth", - token: "0xd59b8238ecaeb255d62c85c6ca8aee185870bd7a27e43f85fd2658267036d94a" - } - /**{ - name: "Lighthouse", - containerName: - "DAppNodePackage-validator.lighthouse-prater.dnp.dappnode.eth", - token: - "api-token-0x0200e6ce18e26fd38caca7ae1bfb9e2bba7efb20ed2746ad17f2f6dda44603152d", - }, - { - name: "Nimbus", - containerName: - "DAppNodePackage-beacon-validator.nimbus-prater.dnp.dappnode.eth", - token: - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.MxwOozSH-TLbW_XKepjyYDHm2IT8Ki0tD3AHuajfNMg", - }, - { - name: "Teku", - containerName: "DAppNodePackage-validator.teku-prater.dnp.dappnode.eth", - token: "cd4892ca35d2f5d3e2301a65fc7aa660", - },*/ - ] - }; - - for (const consensusClient of stakerSpecs.consensusClients) { - describe(`Consensus client: ${consensusClient.name}`, () => { - let validatorApi: ValidatorApi; - - before(() => { - const consensusIp = execSync( - `docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' ${consensusClient.containerName}` - ) - .toString() - .trim(); - validatorApi = new ValidatorApi( - { - baseUrl: `http://${consensusIp}:3500`, - authToken: consensusClient.token - }, - stakerSpecs.network - ); - }); - - it("Should post validators", async () => { - const response = await validatorApi.postRemoteKeys({ - remote_keys: [ - { - pubkey: - "0xa2cc280ce811bb680cba309103e23dc3c9902f2a08541c6737e8adfe8198e796023b959fc8aadfad39499b56ec3dd184", - url: "http://web3signer.web3signer-prater.dappnode:9000" - }, - { - pubkey: - "0x86d25af52627204ab822a20ac70da6767952841edbcb0b83c84a395205313661de5f7f76efa475a46f45fa89d95c1dd7", - url: "http://web3signer.web3signer-prater.dappnode:9000" - }, - { - pubkey: - "0x821a80380122281580ba8a56cd21956933d43c62fdc8f5b4ec31b2c620e8534e80b6b816c9a2cc8d25568dc4ebcfd47a", - url: "http://web3signer.web3signer-prater.dappnode:9000" - }, - { - pubkey: - "0x8f2b698583d69c7a78b4482871282602adb7fb47a1aab66c63feb48e7b9245dad77b82346e0201328d66a8b4d483b716", - url: "http://web3signer.web3signer-prater.dappnode:9000" - }, - { - pubkey: - "0xa1735a0dd72205dae313c36d7d17f5b06685944c8886ddac530e5aedbe1fca0c8003e7e274ec1b4ddd08b884f5b9a830", - url: "http://web3signer.web3signer-prater.dappnode:9000" - } - ] - }); - - // It may be a first time import or duplicate import - expect(response).to.be.ok; - }); - - it("Should get validators", async () => { - const response = await validatorApi.getRemoteKeys(); - const expectedResponse = [ - { - pubkey: - "0xa2cc280ce811bb680cba309103e23dc3c9902f2a08541c6737e8adfe8198e796023b959fc8aadfad39499b56ec3dd184", - url: "http://web3signer.web3signer-prater.dappnode:9000", - readonly: true - }, - { - pubkey: - "0x86d25af52627204ab822a20ac70da6767952841edbcb0b83c84a395205313661de5f7f76efa475a46f45fa89d95c1dd7", - url: "http://web3signer.web3signer-prater.dappnode:9000", - readonly: true - }, - { - pubkey: - "0x821a80380122281580ba8a56cd21956933d43c62fdc8f5b4ec31b2c620e8534e80b6b816c9a2cc8d25568dc4ebcfd47a", - url: "http://web3signer.web3signer-prater.dappnode:9000", - readonly: true - }, - { - pubkey: - "0x8f2b698583d69c7a78b4482871282602adb7fb47a1aab66c63feb48e7b9245dad77b82346e0201328d66a8b4d483b716", - url: "http://web3signer.web3signer-prater.dappnode:9000", - readonly: true - }, - { - pubkey: - "0xa1735a0dd72205dae313c36d7d17f5b06685944c8886ddac530e5aedbe1fca0c8003e7e274ec1b4ddd08b884f5b9a830", - url: "http://web3signer.web3signer-prater.dappnode:9000", - readonly: true - } - ]; - expect(response.data).to.eql(expectedResponse); - }); - - it("Should set fee recipients to all validators", async () => { - for (const pubkey of pubkeys) await validatorApi.setFeeRecipient(defaultFeeRecipient, pubkey); - }); - - it("Should get fee recipients from all validators", async () => { - for (const pubkey of pubkeys) { - const response = await validatorApi.getFeeRecipient(pubkey); - expect(response.data.ethaddress).to.eql(defaultFeeRecipient); - } - }); - - it("Should delete fee recipients to all validators", async () => { - for (const pubkey of pubkeys) await validatorApi.deleteFeeRecipient(pubkey); - }); - - it("Should delete validators", async () => { - const response = await validatorApi.deleteRemoteKeys({ - pubkeys - }); - expect(response).to.be.ok; - }); - }); - } -}); diff --git a/packages/brain/test/unit/modules/apiClients/web3signerApi.unit.test.ts b/packages/brain/test/unit/modules/apiClients/web3signerApi.unit.test.ts deleted file mode 100644 index 48142af1..00000000 --- a/packages/brain/test/unit/modules/apiClients/web3signerApi.unit.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { expect } from "chai"; -import { before } from "mocha"; -import path from "path"; -import fs from "fs"; -import { execSync } from "node:child_process"; -import { Web3SignerApi } from "../../../../src/modules/apiClients/index.js"; -import { Network } from "@stakingbrain/common"; - -describe.skip("Signer API: Prater", () => { - const keystoresPath = path.resolve(process.cwd(), "keystores"); - - const pubkeys = [ - "0xa2cc280ce811bb680cba309103e23dc3c9902f2a08541c6737e8adfe8198e796023b959fc8aadfad39499b56ec3dd184", - "0x86d25af52627204ab822a20ac70da6767952841edbcb0b83c84a395205313661de5f7f76efa475a46f45fa89d95c1dd7", - "0x821a80380122281580ba8a56cd21956933d43c62fdc8f5b4ec31b2c620e8534e80b6b816c9a2cc8d25568dc4ebcfd47a", - "0x8f2b698583d69c7a78b4482871282602adb7fb47a1aab66c63feb48e7b9245dad77b82346e0201328d66a8b4d483b716", - "0xa1735a0dd72205dae313c36d7d17f5b06685944c8886ddac530e5aedbe1fca0c8003e7e274ec1b4ddd08b884f5b9a830" - ]; - const signerContainerName = "DAppNodePackage-web3signer.web3signer-prater.dnp.dappnode.eth"; - const host = "web3signer.web3signer-prater.dappnode"; - let signerApi: Web3SignerApi; - - before(() => { - // Get consensus client IP - const signerIp = execSync( - `docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' ${signerContainerName}` - ) - .toString() - .trim(); - signerApi = new Web3SignerApi( - { - baseUrl: `http://${signerIp}:9000`, - host - }, - Network.Prater - ); - }); - - it("Should post validators", async () => { - const keystoresPaths = fs.readdirSync(keystoresPath).filter((file) => file.endsWith(".json")); - const keystores = keystoresPaths.map((file) => fs.readFileSync(path.join(keystoresPath, file)).toString()); - const passwords = Array(keystores.length).fill("stakingbrain"); - - const response = await signerApi.importRemoteKeys({ - keystores, - passwords - }); - - expect(response.data).to.be.an("array"); - }).timeout(10000); - - it("Should get validators", async () => { - const response = await signerApi.listRemoteKeys(); - expect(response.data.map((item) => item.validating_pubkey)).to.have.members(pubkeys); - }); - - it("Should delete validators", async () => { - const response = await signerApi.deleteRemoteKeys({ - pubkeys - }); - expect(response.data).to.be.ok; - - const validators = await signerApi.listRemoteKeys(); - expect(validators.data).to.be.empty; - }); -}); diff --git a/packages/brain/test/unit/modules/builderConfig/builderConfig.unit.test.ts b/packages/brain/test/unit/modules/builderConfig/builderConfig.unit.test.ts new file mode 100644 index 00000000..f7c7d20a --- /dev/null +++ b/packages/brain/test/unit/modules/builderConfig/builderConfig.unit.test.ts @@ -0,0 +1,539 @@ +import { expect } from "chai"; +import sinon from "sinon"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { BuilderConfig, Network, MEV_SP_ADDRESS_MAINNET, ConsensusClient, ExecutionClient } from "@stakingbrain/common"; +import { ValidatorApi, Web3SignerApi, BeaconchainApi } from "../../../../src/modules/apiClients/index.js"; +import { AllowedMethods } from "../../../../src/modules/apiClients/types.js"; +import { HttpApiError } from "../../../../src/modules/apiClients/error.js"; +import { BrainDataBase } from "../../../../src/modules/db/index.js"; +import { CronJob } from "../../../../src/modules/cron/cron.js"; +import { reloadValidators } from "../../../../src/modules/cron/reloadValidators/index.js"; +import { updateValidators } from "../../../../src/modules/apiServers/ui/calls/updateValidators.js"; +import { getBuilderConfigs } from "../../../../src/modules/apiServers/ui/calls/getBuilderConfigs.js"; +import { getValidators } from "../../../../src/modules/apiServers/ui/calls/getValidators.js"; +import { setActiveValidatorsLoadedInBrain } from "../../../../src/modules/cron/trackValidatorsPerformance/setActiveValidatorsLoadedInBrain.js"; +import { ValidatorStatus } from "../../../../src/modules/apiClients/types.js"; +import logger from "../../../../src/modules/logger/index.js"; + +function deferred(): { promise: Promise; resolve: () => void } { + let resolve!: () => void; + const promise = new Promise((done) => { + resolve = done; + }); + return { promise, resolve }; +} + +const keys = ["11", "22", "33"].map((byte) => "0x" + byte.repeat(48)); +const recipient = "0x" + "ab".repeat(20); +const config: BuilderConfig = { + min_bid: "9007199254740993", + builders: [{ url: "https://builder.example", max_execution_payment: "18446744073709551615" }] +}; + +type Request = { method: AllowedMethods; endpoint: string; body?: string }; +class RecordingValidator extends ValidatorApi { + calls: Request[] = []; + remoteKeys: string[] = []; + resolved: BuilderConfig = { min_bid: "100", builder_boost_factor: "100", builders: [] }; + failure?: Error; + failKey?: string; + constructor() { + super({ baseUrl: "http://validator:3500", authToken: "token" }, Network.Mainnet); + } + protected async request(request: Request): Promise { + this.calls.push(request); + if (request.endpoint.endsWith("builder_config")) { + if (this.failure && (!this.failKey || request.endpoint.includes(this.failKey))) throw this.failure; + return request.method === "GET" ? { data: this.resolved } : undefined; + } + if (request.endpoint.endsWith("remotekeys")) { + if (request.method === "GET") return { data: this.remoteKeys.map((pubkey) => ({ pubkey })) }; + if (request.method === "POST") { + const entries = JSON.parse(request.body!).remote_keys as { pubkey: string }[]; + this.remoteKeys.push(...entries.map(({ pubkey }) => pubkey)); + return { data: entries.map(() => ({ status: "imported", message: "" })) }; + } + } + return undefined; + } +} + +async function rejection(action: Promise): Promise { + try { + await action; + } catch (error) { + return error; + } + throw new Error("Expected rejection"); +} + +describe("Builder configuration management", () => { + let directory: string; + let db: BrainDataBase; + let api: RecordingValidator; + let cron: sinon.SinonStubbedInstance; + beforeEach(() => { + directory = fs.mkdtempSync(path.join(os.tmpdir(), "stakingbrain-builders-")); + db = new BrainDataBase(path.join(directory, "db.json")); + db.addValidators({ + validators: Object.fromEntries( + keys.map((key) => [key, { tag: "solo", feeRecipient: recipient, automaticImport: false, index: 42 }]) + ) + }); + api = new RecordingValidator(); + cron = sinon.createStubInstance(CronJob); + sinon.stub(logger, "error"); + }); + afterEach(() => { + sinon.restore(); + fs.rmSync(directory, { recursive: true, force: true }); + }); + + const update = ( + brainDb: BrainDataBase, + validatorApi: ValidatorApi, + reloadValidatorsCronTask: CronJob, + requests: Parameters[0]["customValidatorUpdateRequest"] + ) => updateValidators({ brainDb, validatorApi, reloadValidatorsCronTask, customValidatorUpdateRequest: requests }); + + it("serializes sparse replacement and uint64 strings and supports GET/DELETE", async () => { + await api.setBuilderConfig(config, keys[0].slice(2)); + expect(api.calls[0]).to.include({ + method: "POST", + endpoint: `/eth/v1/validator/${keys[0]}/builder_config`, + body: JSON.stringify(config) + }); + expect((await api.getBuilderConfig(keys[0])).data).to.deep.equal(api.resolved); + await api.setBuilderConfig({}, keys[0]); + expect(api.calls.at(-1)?.body).to.equal("{}"); + await api.deleteBuilderConfig(keys[0]); + expect(api.calls.at(-1)).to.include({ method: "DELETE", body: undefined }); + expect(api.getBuilderConfigStatus(keys[0])).to.deep.equal({ status: "applied" }); + const count = api.calls.length; + expect( + (await rejection(api.setBuilderConfig({ min_bid: 1 } as unknown as BuilderConfig, keys[0]))).message + ).to.include("uint64"); + expect((await rejection(api.setBuilderConfig({}, "../bad"))).message).to.include("public key"); + expect(api.calls.length).to.equal(count); + }); + + it("persists original overrides across process restart and partial fee/index updates", async () => { + await update(db, api, cron, [{ pubkey: keys[0].slice(2), builderConfig: config }]); + const reopened = new BrainDataBase(path.join(directory, "db.json")); + expect(reopened.getData()[keys[0]].builderConfig).to.deep.equal(config); + reopened.updateValidators({ validators: { [keys[0]]: { index: 77 } } }); + await update(reopened, api, cron, [{ pubkey: keys[0], feeRecipient: recipient }]); + expect(reopened.getData()[keys[0]]).to.include({ index: 77, feeRecipient: recipient }); + expect(reopened.getData()[keys[0]].builderConfig).to.deep.equal(config); + expect(reopened.getData()[keys[1]]).not.to.have.property("builderConfig"); + }); + + it("keeps empty list, no overrides, reset and unmanaged states distinct", async () => { + await update(db, api, cron, [ + { pubkey: keys[0], builderConfig: { builders: [] } }, + { pubkey: keys[1], builderConfig: {} } + ]); + expect(db.getData()[keys[0]].builderConfig).to.deep.equal({ builders: [] }); + expect(db.getData()[keys[1]].builderConfig).to.deep.equal({}); + expect(db.getData()[keys[2]]).not.to.have.property("builderConfig"); + await update(db, api, cron, [{ pubkey: keys[0], builderConfig: null }]); + expect(new BrainDataBase(path.join(directory, "db.json")).getData()[keys[0]].builderConfig).to.equal(null); + expect(api.calls.at(-1)?.method).to.equal("DELETE"); + }); + + it("reads resolved values separately without ever freezing defaults or mutating persistence", async () => { + await update(db, api, cron, [{ pubkey: keys[0], builderConfig: {} }]); + const before = fs.readFileSync(path.join(directory, "db.json"), "utf8"); + const result = await getBuilderConfigs({ pubkeys: [keys[0]], brainDb: db, validatorApi: api }); + expect(result[0]).to.include({ status: "success" }); + expect(result[0].builderConfig).to.deep.equal({}); + expect(result[0].resolved).to.deep.equal(api.resolved); + api.resolved = { min_bid: "999", builders: [] }; + await getBuilderConfigs({ pubkeys: [keys[0]], brainDb: db, validatorApi: api }); + expect(fs.readFileSync(path.join(directory, "db.json"), "utf8")).to.equal(before); + }); + + it("finishes an in-flight reload before applying a newer reset", async () => { + db.updateValidators({ validators: Object.fromEntries(keys.map((key) => [key, { builderConfig: config }])) }); + const signer = sinon.createStubInstance(Web3SignerApi); + signer.upcheck.resolves(); + signer.listRemoteKeys.resolves({ + data: keys.map((validating_pubkey) => ({ validating_pubkey, derivation_path: "", readonly: false })) + }); + const entered = deferred(); + const resume = deferred(); + const originalSet = api.setBuilderConfig.bind(api); + sinon.stub(api, "setBuilderConfig").callsFake(async (settings, pubkey) => { + if (pubkey === keys[0]) { + entered.resolve(); + await resume.promise; + } + await originalSet(settings, pubkey); + }); + const reload = reloadValidators(signer, "http://signer:9000", api, db); + await entered.promise; + const reset = update(db, api, cron, [{ pubkey: keys[1], builderConfig: null }]); + await Promise.resolve(); + expect(db.getData()[keys[1]].builderConfig).to.deep.equal(config); + resume.resolve(); + await Promise.all([reload, reset]); + expect(db.getData()[keys[1]].builderConfig).to.equal(null); + const writes = api.calls.filter( + ({ endpoint }) => endpoint.includes(keys[1]) && endpoint.endsWith("builder_config") + ); + expect(writes.map(({ method }) => method)).to.deep.equal(["POST", "DELETE"]); + expect(api.getBuilderConfigStatus(keys[1])).to.deep.equal({ status: "applied" }); + }); + + it("orders concurrent updates and lets the next update run after an application failure", async () => { + const entered = deferred(); + const resume = deferred(); + sinon.stub(api, "setBuilderConfig").callsFake(async () => { + entered.resolve(); + await resume.promise; + throw new Error("Client unavailable"); + }); + const first = rejection(update(db, api, cron, [{ pubkey: keys[0], builderConfig: config }])); + await entered.promise; + const reset = update(db, api, cron, [{ pubkey: keys[0], builderConfig: null }]); + await Promise.resolve(); + expect(db.getData()[keys[0]].builderConfig).to.deep.equal(config); + resume.resolve(); + expect((await first).message).to.include("Settings saved"); + await reset; + expect(db.getData()[keys[0]].builderConfig).to.equal(null); + expect(api.calls.at(-1)?.method).to.equal("DELETE"); + expect(cron.start.callCount).to.equal(2); + }); + + it("does not restore old settings when a concurrent validator-index lookup finishes", async () => { + delete db.data![keys[0]].index; + db.write(); + const entered = deferred(); + const resume = deferred(); + const beacon = sinon.createStubInstance(BeaconchainApi); + beacon.postStateValidators.callsFake(async ({ body }) => { + if (body.statuses.length === 0) { + entered.resolve(); + await resume.promise; + } + return { + execution_optimistic: false, + finalized: true, + data: [ + { + index: "77", + balance: "0", + status: ValidatorStatus.ACTIVE_ONGOING, + validator: { + pubkey: keys[0], + withdrawal_credentials: "", + effective_balance: "", + slashed: false, + activation_eligibility_epoch: "", + activation_epoch: "", + exit_epoch: "", + withdrawable_epoch: "" + } + } + ] + }; + }); + const lookup = setActiveValidatorsLoadedInBrain({ + beaconchainApi: beacon, + brainDb: db, + validatorsDataPerEpochMap: new Map(), + clients: { execution: ExecutionClient.Geth, consensus: ConsensusClient.Lighthouse } + }); + await entered.promise; + const newRecipient = "0x" + "cd".repeat(20); + await update(db, api, cron, [{ pubkey: keys[0], feeRecipient: newRecipient, builderConfig: config }]); + resume.resolve(); + await lookup; + expect(db.getData()[keys[0]]).to.include({ index: 77, feeRecipient: newRecipient }); + expect(db.getData()[keys[0]].builderConfig).to.deep.equal(config); + }); + + it("bounds concurrent reads, preserves input order and keeps per-key failures separate", async () => { + const clock = sinon.useFakeTimers(); + const pubkeys = Array.from({ length: 20 }, (_, index) => "0x" + (index + 1).toString(16).padStart(96, "0")); + db.addValidators({ + validators: Object.fromEntries( + pubkeys.map((key) => [key, { tag: "solo", feeRecipient: recipient, automaticImport: false }]) + ) + }); + let active = 0; + let maximum = 0; + sinon.stub(api, "getBuilderConfig").callsFake(async (pubkey) => { + active++; + maximum = Math.max(maximum, active); + await new Promise((resolve) => setTimeout(resolve, pubkey === pubkeys[0] ? 100 : 10)); + active--; + if (pubkey === pubkeys[1]) throw new Error("Key not found"); + return { data: { min_bid: "100" } }; + }); + const pending = getBuilderConfigs({ pubkeys, brainDb: db, validatorApi: api }); + await clock.runAllAsync(); + const result = await pending; + expect(maximum).to.equal(8); + expect(result.map(({ pubkey }) => pubkey)).to.deep.equal(pubkeys); + expect(result[1]).to.include({ status: "error", message: "Key not found" }); + expect(result.filter(({ status }) => status === "success")).to.have.length(19); + expect(clock.countTimers()).to.equal(0); + }); + + it("aborts slow reads after one batch deadline and reports keys that were not started", async () => { + const clock = sinon.useFakeTimers(); + const pubkeys = Array.from({ length: 100 }, (_, index) => "0x" + (index + 1).toString(16).padStart(96, "0")); + db.addValidators({ + validators: Object.fromEntries( + pubkeys.map((key) => [ + key, + { tag: "solo", feeRecipient: recipient, automaticImport: false, builderConfig: config } + ]) + ) + }); + const get = sinon.stub(api, "getBuilderConfig").callsFake(async (pubkey, signal) => { + expect(pubkey).to.be.oneOf(pubkeys); + return new Promise<{ data: BuilderConfig }>((_resolve, reject) => { + signal!.addEventListener("abort", () => reject(new Error("Aborted")), { once: true }); + }); + }); + let finished = false; + const pending = getBuilderConfigs({ pubkeys, brainDb: db, validatorApi: api }).then((result) => { + finished = true; + return result; + }); + await clock.tickAsync(9999); + expect(finished).to.equal(false); + expect(get.callCount).to.equal(8); + await clock.tickAsync(1); + const result = await pending; + expect(result).to.have.length(100); + expect(result.every(({ status, message }) => status === "error" && message?.includes("timed out"))).to.equal(true); + expect(result.every(({ builderConfig }) => builderConfig?.min_bid === config.min_bid)).to.equal(true); + expect(get.callCount).to.equal(8); + expect(get.firstCall.args[1]?.aborted).to.equal(true); + expect(clock.countTimers()).to.equal(0); + }); + + it("passes cancellation through to the HTTP client", async () => { + const controller = new AbortController(); + controller.abort(); + const client = new ValidatorApi({ baseUrl: "http://127.0.0.1:1" }, Network.Mainnet); + const error = await rejection(client.getBuilderConfig(keys[0], controller.signal)); + expect(error.message).to.include("aborted"); + }); + + it("validates the whole batch before writes and always resumes reload", async () => { + const before = fs.readFileSync(path.join(directory, "db.json"), "utf8"); + for (const invalid of [ + { pubkey: keys[1], builderConfig: { builders: [{ url: "bad" }] } }, + { pubkey: keys[1], feeRecipient: "0x1234" }, + { pubkey: "0x" + "ff".repeat(48), builderConfig: {} } + ]) { + await rejection(update(db, api, cron, [{ pubkey: keys[0], builderConfig: config }, invalid])); + expect(fs.readFileSync(path.join(directory, "db.json"), "utf8")).to.equal(before); + } + expect(api.calls).to.have.length(0); + expect(cron.start.callCount).to.equal(3); + }); + + it("reports unsupported/failing APIs, redacts echoed auth and continues the batch", async () => { + for (const statusCode of [400, 401, 403, 404, 405, 500, 501]) { + api.failure = new HttpApiError(statusCode, "echoed-secret-authorization"); + api.failKey = keys[0]; + const error = await rejection( + update(db, api, cron, [ + { pubkey: keys[0], builderConfig: config }, + { pubkey: keys[1], builderConfig: {} } + ]) + ); + expect(error.message) + .to.include(`HTTP ${statusCode}`) + .and.include("Settings saved") + .and.not.include("echoed-secret"); + expect(api.getBuilderConfigStatus(keys[0])?.status).to.equal( + [405, 501].includes(statusCode) ? "unsupported" : "error" + ); + expect(api.getBuilderConfigStatus(keys[1])?.status).to.equal("applied"); + expect(db.getData()[keys[0]].builderConfig).to.deep.equal(config); + } + const result = await getBuilderConfigs({ pubkeys: keys, brainDb: db, validatorApi: api }); + expect(result[0].status).to.equal("error"); + expect(result[1].status).to.equal("success"); + await api.setFeeRecipient(recipient, keys[0]); + await api.getRemoteKeys(); + expect(api.calls.at(-1)?.endpoint).to.equal("/eth/v1/remotekeys"); + }); + + it("reimports keys after restarts, retries failures/resets and replays sparse overrides on every reload", async () => { + db.updateValidators({ validators: { [keys[0]]: { builderConfig: config }, [keys[1]]: { builderConfig: null } } }); + const signer = sinon.createStubInstance(Web3SignerApi); + signer.upcheck.resolves(); + signer.listRemoteKeys.resolves({ + data: keys.map((validating_pubkey) => ({ validating_pubkey, derivation_path: "", readonly: false })) + }); + api.failure = new HttpApiError(500, "failure"); + api.failKey = keys[0]; + await reloadValidators(signer, "http://signer:9000", api, db); + expect(api.remoteKeys).to.deep.equal(keys); + expect(api.calls.findIndex(({ endpoint }) => endpoint.endsWith("feerecipient"))).to.be.lessThan( + api.calls.findIndex(({ endpoint }) => endpoint.endsWith("builder_config")) + ); + expect(api.getBuilderConfigStatus(keys[0])?.status).to.equal("error"); + expect(api.getBuilderConfigStatus(keys[1])?.status).to.equal("applied"); + api.failure = undefined; + api.remoteKeys = []; // validator restart + await reloadValidators(signer, "http://signer:9000", api, db); + await reloadValidators(signer, "http://signer:9000", api, db); + expect(api.getBuilderConfigStatus(keys[0])?.status).to.equal("applied"); + expect( + api.calls.filter(({ endpoint, method }) => endpoint.includes(keys[1]) && method === "DELETE") + ).to.have.length(3); + expect( + api.calls.filter(({ endpoint }) => endpoint.includes(keys[2]) && endpoint.endsWith("builder_config")) + ).to.have.length(0); + expect( + api.calls + .filter( + ({ endpoint, method }) => + endpoint.includes(keys[0]) && endpoint.endsWith("builder_config") && method === "POST" + ) + .map(({ body }) => body) + ).to.deep.equal(Array(3).fill(JSON.stringify(config))); + expect( + api.calls.filter(({ endpoint, method }) => endpoint.endsWith("builder_config") && method === "GET") + ).to.have.length(0); + }); + + it("preserves Smooth payout destination and protocol fee-recipient restrictions", async () => { + const smooth = MEV_SP_ADDRESS_MAINNET; + db.updateValidators({ validators: { [keys[0]]: { feeRecipient: smooth } } }); + await update(db, api, cron, [{ pubkey: keys[0], builderConfig: { builders: [], builder_boost_factor: "0" } }]); + expect(db.getData()[keys[0]].feeRecipient).to.equal(smooth); + // Other protocols may also configure builders without gaining fee-recipient editing rights. + db.data![keys[1]].tag = "rocketpool"; + db.write(); + await update(db, api, cron, [{ pubkey: keys[1], feeRecipient: smooth, builderConfig: {} }]); + expect(db.getData()[keys[1]].feeRecipient).to.equal(recipient); + const signer = sinon.createStubInstance(Web3SignerApi); + signer.upcheck.resolves(); + signer.listRemoteKeys.resolves({ + data: keys.map((validating_pubkey) => ({ validating_pubkey, derivation_path: "", readonly: false })) + }); + api.failure = new HttpApiError(501, "unsupported"); + await reloadValidators(signer, "http://signer:9000", api, db); + expect( + api.calls.filter(({ endpoint }) => endpoint.includes(keys[0]) && endpoint.endsWith("feerecipient")).at(-1)?.body + ).to.equal(JSON.stringify({ ethaddress: smooth })); + expect(db.getData()[keys[0]].feeRecipient).to.equal(smooth); + }); + + it("retains failed reset intent across Brain restart and applies configuration to a newly imported key", async () => { + await update(db, api, cron, [{ pubkey: keys[0], builderConfig: config }]); + api.failure = new HttpApiError(403, "read-only validator config"); + await rejection(update(db, api, cron, [{ pubkey: keys[0], builderConfig: null }])); + const reopened = new BrainDataBase(path.join(directory, "db.json")); + expect(reopened.getData()[keys[0]].builderConfig).to.equal(null); + const restartedApi = new RecordingValidator(); + const newKey = "0x" + "44".repeat(48); + reopened.addValidators({ + validators: { + [newKey]: { + tag: "solo", + feeRecipient: recipient, + automaticImport: false, + builderConfig: { builder_boost_factor: "0" } + } + } + }); + const signer = sinon.createStubInstance(Web3SignerApi); + signer.upcheck.resolves(); + signer.listRemoteKeys.resolves({ + data: [...keys, newKey].map((validating_pubkey) => ({ validating_pubkey, derivation_path: "", readonly: false })) + }); + await reloadValidators(signer, "http://signer:9000", restartedApi, reopened); + expect( + restartedApi.calls.some( + ({ endpoint, method }) => + endpoint.includes(keys[0]) && endpoint.endsWith("builder_config") && method === "DELETE" + ) + ).to.equal(true); + expect(restartedApi.remoteKeys).to.include(newKey); + expect( + restartedApi.calls.find(({ endpoint }) => endpoint.includes(newKey) && endpoint.endsWith("builder_config"))?.body + ).to.equal('{"builder_boost_factor":"0"}'); + expect(restartedApi.getBuilderConfigStatus(keys[0])?.status).to.equal("applied"); + }); + + it("exposes network errors and malformed GET responses without changing stored overrides", async () => { + await update(db, api, cron, [{ pubkey: keys[0], builderConfig: {} }]); + api.failure = new Error("Request timed out"); + const network = await getBuilderConfigs({ pubkeys: [keys[0]], brainDb: db, validatorApi: api }); + expect(network[0].message).to.include("timed out"); + api.failure = undefined; + api.resolved = { min_bid: 123 } as unknown as BuilderConfig; + const malformed = await getBuilderConfigs({ pubkeys: [keys[0]], brainDb: db, validatorApi: api }); + expect(malformed[0].status).to.equal("error"); + expect(malformed[0].message).to.include("uint64"); + expect(db.getData()[keys[0]].builderConfig).to.deep.equal({}); + }); + + it("enforces the existing database size limit before persisting a large bulk replacement", () => { + const manyKeys = Array.from({ length: 14 }, (_, index) => "0x" + (index + 100).toString(16).padStart(96, "0")); + db.addValidators({ + validators: Object.fromEntries( + manyKeys.map((key) => [key, { tag: "solo", feeRecipient: recipient, automaticImport: false }]) + ) + }); + const large: BuilderConfig = { + builders: Array.from({ length: 64 }, (_, index) => ({ + url: `https://builder-${index}.example`, + auth_data: "0x" + "ab".repeat(4096) + })) + }; + const before = fs.readFileSync(path.join(directory, "db.json"), "utf8"); + expect(() => + db.updateValidators({ validators: Object.fromEntries(manyKeys.map((key) => [key, { builderConfig: large }])) }) + ).to.throw("too big"); + expect(fs.readFileSync(path.join(directory, "db.json"), "utf8")).to.equal(before); + expect(db.getData()[manyKeys[0]]).not.to.have.property("builderConfig"); + }); + + for (const includePubkey of [true, false]) { + it(`reports fee destination matches when responses ${includePubkey ? "include" : "omit"} pubkeys`, async () => { + const validator = sinon.createStubInstance(ValidatorApi); + validator.getRemoteKeys.resolves({ + data: keys.map((pubkey) => ({ pubkey, readonly: false, url: "http://signer" })) + }); + for (const [index, key] of keys.entries()) + validator.getFeeRecipient.withArgs(key).resolves({ + data: { + ...(includePubkey ? { pubkey: key } : {}), + ethaddress: index === 0 ? recipient.toUpperCase().replace("0X", "0x") : "0x" + "00".repeat(20) + } + }); + const signer = sinon.createStubInstance(Web3SignerApi); + signer.listRemoteKeys.resolves({ + data: keys.map((validating_pubkey) => ({ validating_pubkey, derivation_path: "", readonly: false })) + }); + const beacon = sinon.createStubInstance(BeaconchainApi); + beacon.getStateValidator.rejects(new Error("not deposited")); + const result = await getValidators({ + brainDb: db, + validatorApi: validator, + signerApi: signer, + beaconchainApi: beacon + }); + expect(result.map(({ validatorFeeRecipientCorrect }) => validatorFeeRecipientCorrect)).to.deep.equal([ + true, + false, + false + ]); + expect(validator.getBuilderConfig.called).to.equal(false); + }); + } +}); diff --git a/packages/brain/test/unit/modules/config/builderRelayPresets.unit.test.ts b/packages/brain/test/unit/modules/config/builderRelayPresets.unit.test.ts new file mode 100644 index 00000000..45865180 --- /dev/null +++ b/packages/brain/test/unit/modules/config/builderRelayPresets.unit.test.ts @@ -0,0 +1,38 @@ +import { expect } from "chai"; +import { Network, validateBuilderConfig } from "@stakingbrain/common"; +import { getBuilderRelayPresets } from "../../../../src/modules/config/builderRelayPresets.js"; + +describe("ePBS relay presets", () => { + it("provides the six Hoodi ePBS endpoints as valid builder entries", () => { + const catalog = getBuilderRelayPresets(Network.Hoodi); + expect(catalog.network).to.equal(Network.Hoodi); + expect(catalog.sourceUrl).to.equal("https://ethstaker.org/mev-relay-list"); + expect(catalog.relays.map(({ name }) => name)).to.deep.equal([ + "Aestus", + "Flashbots", + "nflaig", + "Titan", + "Ultra Sound", + "pk910" + ]); + expect(new Set(catalog.relays.map(({ id }) => id)).size).to.equal(6); + const config = { builders: catalog.relays.map(({ url }) => ({ url })) }; + validateBuilderConfig(config); + for (const { url } of config.builders) { + expect(new URL(url).protocol).to.equal("https:"); + expect(new URL(url).username).to.equal(""); + } + }); + + it("keeps future mainnet and other network catalogs separate from Hoodi", () => { + for (const network of [Network.Mainnet, Network.Gnosis, Network.Lukso]) { + expect(getBuilderRelayPresets(network)).to.include({ network }); + expect(getBuilderRelayPresets(network).relays).to.deep.equal([]); + } + const catalog = getBuilderRelayPresets(Network.Hoodi); + catalog.relays[0].url = "https://changed.example"; + catalog.relays.pop(); + expect(getBuilderRelayPresets(Network.Hoodi).relays).to.have.length(6); + expect(getBuilderRelayPresets(Network.Hoodi).relays[0].url).to.equal("https://hoodi.aestus.live"); + }); +}); diff --git a/packages/brain/test/unit/modules/config/importBuilderIds.unit.test.ts b/packages/brain/test/unit/modules/config/importBuilderIds.unit.test.ts new file mode 100644 index 00000000..f6ca571c --- /dev/null +++ b/packages/brain/test/unit/modules/config/importBuilderIds.unit.test.ts @@ -0,0 +1,154 @@ +import { expect } from "chai"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import sinon from "sinon"; +import { BuilderConfig, Network } from "@stakingbrain/common"; +import { BrainDataBase } from "../../../../src/modules/db/index.js"; +import { importBuilderIds } from "../../../../src/modules/config/importBuilderIds.js"; + +const keys = ["11", "22", "33", "44"].map((byte) => "0x" + byte.repeat(48)); +const recipient = "0x" + "ab".repeat(20); +const config: BuilderConfig = { + builders: [{ url: "https://hoodi.aestus.live" }, { url: "https://boost-relay-hoodi.flashbots.net" }] +}; + +describe("One-time MEV-Boost builder settings import", () => { + let directory: string; + let dbPath: string; + let markerPath: string; + let brainDb: BrainDataBase; + + beforeEach(() => { + directory = fs.mkdtempSync(path.join(os.tmpdir(), "stakingbrain-builder-import-")); + dbPath = path.join(directory, "brain-db.json"); + markerPath = path.join(directory, "builder-config-imported"); + brainDb = new BrainDataBase(dbPath); + brainDb.addValidators({ + validators: Object.fromEntries( + keys.slice(0, 3).map((pubkey) => [pubkey, { tag: "solo", feeRecipient: recipient, automaticImport: false }]) + ) + }); + }); + + afterEach(() => { + sinon.restore(); + fs.rmSync(directory, { recursive: true, force: true }); + }); + + const run = (brainDb: BrainDataBase, markerPath: string, builderIds = "aestus,flashbots") => + importBuilderIds({ builderIds, network: Network.Hoodi, brainDb, markerPath }); + + it("does nothing without builder IDs", () => { + const before = fs.readFileSync(dbPath, "utf8"); + for (const builderIds of [undefined, "", " \n ", ", ,"]) { + expect(importBuilderIds({ builderIds, network: Network.Hoodi, brainDb, markerPath })).to.equal(undefined); + } + expect(fs.existsSync(markerPath)).to.equal(false); + expect(fs.readFileSync(dbPath, "utf8")).to.equal(before); + }); + + it("persists exact settings for unmanaged validators and preserves existing choices and resets", () => { + brainDb.updateValidators({ + validators: { + [keys[1]]: { builderConfig: {} }, + [keys[2]]: { builderConfig: null } + } + }); + const before = brainDb.getData(); + expect(run(brainDb, markerPath)).to.deep.equal({ imported: 1, skipped: 2 }); + const persisted = new BrainDataBase(dbPath).getData(); + expect(persisted[keys[0]]).to.deep.equal({ ...before[keys[0]], builderConfig: config }); + expect(persisted[keys[1]]).to.deep.equal(before[keys[1]]); + expect(persisted[keys[2]]).to.deep.equal(before[keys[2]]); + expect(fs.statSync(markerPath).mode & 0o777).to.equal(0o600); + expect(fs.readFileSync(markerPath, "utf8")).to.equal(""); + }); + + it("ignores retained, changed and invalid IDs after a restart and never configures future keys", () => { + expect(run(brainDb, markerPath)).to.deep.equal({ imported: 3, skipped: 0 }); + brainDb.updateValidators({ + validators: { + [keys[0]]: { builderConfig: { builders: [] } }, + [keys[1]]: { builderConfig: null } + } + }); + brainDb.addValidators({ + validators: { [keys[3]]: { tag: "solo", feeRecipient: recipient, automaticImport: false } } + }); + const before = fs.readFileSync(dbPath, "utf8"); + const restarted = new BrainDataBase(dbPath); + for (const input of ["aestus,flashbots", "titan", "unknown"]) { + expect(run(restarted, markerPath, input)).to.equal(undefined); + expect(fs.readFileSync(dbPath, "utf8")).to.equal(before); + } + expect(restarted.getData()[keys[3]].builderConfig).to.equal(undefined); + }); + + it("marks an empty database complete without setting defaults for future keys", () => { + brainDb.deleteValidators(keys.slice(0, 3)); + expect(run(brainDb, markerPath)).to.deep.equal({ imported: 0, skipped: 0 }); + brainDb.addValidators({ + validators: { [keys[3]]: { tag: "solo", feeRecipient: recipient, automaticImport: false } } + }); + expect(run(brainDb, markerPath)).to.equal(undefined); + expect(brainDb.getData()[keys[3]].builderConfig).to.equal(undefined); + }); + + it("completes without rewriting the database when every validator already has settings", () => { + brainDb.updateValidators({ + validators: Object.fromEntries(keys.slice(0, 3).map((pubkey) => [pubkey, { builderConfig: null }])) + }); + const update = sinon.spy(brainDb, "updateValidators"); + expect(run(brainDb, markerPath)).to.deep.equal({ imported: 0, skipped: 3 }); + expect(update.called).to.equal(false); + expect(fs.existsSync(markerPath)).to.equal(true); + }); + + it("rejects unknown IDs without partially applying a list", () => { + const before = fs.readFileSync(dbPath, "utf8"); + const inputs = ["unknown", "aestus,unknown", "https://hoodi.aestus.live"]; + for (const input of inputs) { + expect(() => run(brainDb, markerPath, input)).to.throw(); + expect(fs.existsSync(markerPath)).to.equal(false); + expect(fs.readFileSync(dbPath, "utf8")).to.equal(before); + } + expect(run(brainDb, markerPath)).to.deep.equal({ imported: 3, skipped: 0 }); + }); + + it("uses the active network without falling back to Hoodi presets", () => { + for (const network of [Network.Mainnet, Network.Gnosis, Network.Lukso]) { + expect(() => importBuilderIds({ builderIds: "aestus", network, brainDb, markerPath })).to.throw( + `BUILDER_IDS contains an unknown builder ID for ${network}` + ); + expect(fs.existsSync(markerPath)).to.equal(false); + expect(brainDb.getData()[keys[0]].builderConfig).to.equal(undefined); + } + }); + + it("trims IDs and ignores duplicates and empty entries", () => { + run(brainDb, markerPath, " aestus, flashbots,aestus, ,"); + expect(brainDb.getData()[keys[0]].builderConfig).to.deep.equal(config); + }); + + it("does not consume the import if the database write fails", () => { + const before = fs.readFileSync(dbPath, "utf8"); + const update = sinon.stub(brainDb, "updateValidators").throws(new Error("disk full")); + expect(() => run(brainDb, markerPath)).to.throw("disk full"); + expect(fs.existsSync(markerPath)).to.equal(false); + expect(fs.readFileSync(dbPath, "utf8")).to.equal(before); + update.restore(); + expect(run(brainDb, markerPath)).to.deep.equal({ imported: 3, skipped: 0 }); + }); + + it("preserves edits when retrying after the database was saved but marker creation failed", () => { + markerPath = path.join(directory, "missing", "import.json"); + expect(() => run(brainDb, markerPath)).to.throw(); + expect(fs.existsSync(markerPath)).to.equal(false); + expect(brainDb.getData()[keys[0]].builderConfig).to.deep.equal(config); + brainDb.updateValidators({ validators: { [keys[0]]: { builderConfig: { builders: [] } } } }); + fs.mkdirSync(path.dirname(markerPath)); + expect(run(new BrainDataBase(dbPath), markerPath)).to.deep.equal({ imported: 0, skipped: 3 }); + expect(brainDb.getData()[keys[0]].builderConfig).to.deep.equal({ builders: [] }); + }); +}); diff --git a/packages/brain/test/unit/modules/config/networks.unit.test.ts b/packages/brain/test/unit/modules/config/networks.unit.test.ts new file mode 100644 index 00000000..f202116e --- /dev/null +++ b/packages/brain/test/unit/modules/config/networks.unit.test.ts @@ -0,0 +1,38 @@ +import { expect } from "chai"; +import sinon from "sinon"; +import { ConsensusClient, ExecutionClient, Network } from "@stakingbrain/common"; +import { loadEnvs } from "../../../../src/modules/config/loadEnvs.js"; +import { networkConfig } from "../../../../src/modules/config/networks/index.js"; + +describe("Supported networks", () => { + afterEach(() => sinon.restore()); + + it("loads current networks and their chain parameters", () => { + expect(Object.values(Network)).to.have.members(["mainnet", "gnosis", "lukso", "hoodi"]); + for (const network of Object.values(Network)) { + const stub = sinon.stub(process, "env").value({ + NETWORK: network, + [`_DAPPNODE_GLOBAL_EXECUTION_CLIENT_${network.toUpperCase()}`]: "geth", + [`_DAPPNODE_GLOBAL_CONSENSUS_CLIENT_${network.toUpperCase()}`]: "lighthouse" + }); + expect(loadEnvs()).to.include({ + network, + executionClient: ExecutionClient.Geth, + consensusClient: ConsensusClient.Lighthouse + }); + const config = networkConfig(network); + expect(config.minGenesisTime).to.be.greaterThan(0); + expect(config.secondsPerSlot).to.be.greaterThan(0); + stub.restore(); + } + }); + + it("rejects retired testnets before resolving any client configuration", () => { + for (const network of ["prater", "holesky", "goerli"]) { + const stub = sinon.stub(process, "env").value({ NETWORK: network }); + expect(() => loadEnvs()).to.throw("NETWORK env must be one of mainnet, gnosis, lukso, hoodi"); + expect(() => networkConfig(network as Network)).to.throw("is not supported"); + stub.restore(); + } + }); +}); diff --git a/packages/brain/test/unit/modules/cron/cron.unit.test.ts b/packages/brain/test/unit/modules/cron/cron.unit.test.ts new file mode 100644 index 00000000..fe0adb10 --- /dev/null +++ b/packages/brain/test/unit/modules/cron/cron.unit.test.ts @@ -0,0 +1,46 @@ +import { expect } from "chai"; +import sinon from "sinon"; +import { CronJob } from "../../../../src/modules/cron/cron.js"; +import logger from "../../../../src/modules/logger/index.js"; + +describe("Cron execution", () => { + afterEach(() => sinon.restore()); + + it("skips overlapping ticks, including across stop/start", async () => { + const clock = sinon.useFakeTimers(); + let release!: () => void; + const gate = new Promise((resolve) => { + release = resolve; + }); + const job = sinon.stub().returns(gate); + const cron = new CronJob(100, job); + try { + cron.start(); + await clock.tickAsync(100); + cron.stop(); + cron.start(); + await clock.tickAsync(300); + expect(job.callCount).to.equal(1); + release(); + await clock.tickAsync(100); + expect(job.callCount).to.equal(2); + } finally { + cron.stop(); + } + }); + + it("logs failures and still runs the next tick", async () => { + const clock = sinon.useFakeTimers(); + const error = sinon.stub(logger, "error"); + const job = sinon.stub().rejects(new Error("Failed")); + const cron = new CronJob(100, job); + try { + cron.start(); + await clock.tickAsync(200); + expect(job.callCount).to.equal(2); + expect(error.callCount).to.equal(2); + } finally { + cron.stop(); + } + }); +}); diff --git a/packages/brain/test/unit/modules/db/index.unit.test.ts b/packages/brain/test/unit/modules/db/index.unit.test.ts index d8fa2b37..3980d031 100644 --- a/packages/brain/test/unit/modules/db/index.unit.test.ts +++ b/packages/brain/test/unit/modules/db/index.unit.test.ts @@ -1,174 +1,115 @@ import { expect } from "chai"; import sinon from "sinon"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; import { BrainDataBase } from "../../../../src/modules/db/index.js"; -import fs from "fs"; import { Web3SignerApi, ValidatorApi } from "../../../../src/modules/apiClients/index.js"; -import { execSync } from "node:child_process"; -import path from "path"; -import { Network } from "@stakingbrain/common"; - -describe("DataBase", () => { - const testDbName = "testDb.json"; - const signerDnp = "DAppNodePackage-web3signer.web3signer-prater.dnp.dappnode.eth"; - const consensusClientDnp = "DAppNodePackage-validator.prysm-prater.dnp.dappnode.eth"; +import logger from "../../../../src/modules/logger/index.js"; +describe("Database initialization", () => { + let directory: string; + let filename: string; beforeEach(() => { - if (fs.existsSync(testDbName)) fs.unlinkSync(testDbName); + directory = fs.mkdtempSync(path.join(os.tmpdir(), "stakingbrain-db-")); + filename = path.join(directory, "db.json"); + sinon.stub(logger, "error"); + }); + afterEach(() => { + sinon.restore(); + fs.rmSync(directory, { recursive: true, force: true }); }); - /** - * Test public initializeDb() - */ - describe("public initializeDb()", () => { - /** - * Should do migration if the database file is not found - */ - it.skip("Should do migration if database file not found", async () => { - const expectedDb = { - "0x821a80380122281580ba8a56cd21956933d43c62fdc8f5b4ec31b2c620e8534e80b6b816c9a2cc8d25568dc4ebcfd47a": { - tag: "solo", - feeRecipient: "0x0000000000000000000000000000000000000000", - feeRecipientValidator: "0x0000000000000000000000000000000000000000", - automaticImport: true - }, - "0x86d25af52627204ab822a20ac70da6767952841edbcb0b83c84a395205313661de5f7f76efa475a46f45fa89d95c1dd7": { - tag: "solo", - feeRecipient: "0x0000000000000000000000000000000000000000", - feeRecipientValidator: "0x0000000000000000000000000000000000000000", - automaticImport: true - }, - "0x8f2b698583d69c7a78b4482871282602adb7fb47a1aab66c63feb48e7b9245dad77b82346e0201328d66a8b4d483b716": { - tag: "solo", - feeRecipient: "0x0000000000000000000000000000000000000000", - feeRecipientValidator: "0x0000000000000000000000000000000000000000", - automaticImport: true - }, - "0xa1735a0dd72205dae313c36d7d17f5b06685944c8886ddac530e5aedbe1fca0c8003e7e274ec1b4ddd08b884f5b9a830": { - tag: "solo", - feeRecipient: "0x0000000000000000000000000000000000000000", - feeRecipientValidator: "0x0000000000000000000000000000000000000000", - automaticImport: true - }, - "0xa2cc280ce811bb680cba309103e23dc3c9902f2a08541c6737e8adfe8198e796023b959fc8aadfad39499b56ec3dd184": { - tag: "solo", - feeRecipient: "0x0000000000000000000000000000000000000000", - feeRecipientValidator: "0x0000000000000000000000000000000000000000", - automaticImport: true - } - }; - // get container IPs - const signerIp = execSync( - `docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' ${signerDnp}` - ); - const consensusIp = execSync( - `docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' ${consensusClientDnp}` - ); - // clean database - if (fs.existsSync(testDbName)) fs.unlinkSync(testDbName); - // create instances - const db = new BrainDataBase(testDbName); - const signerApi = new Web3SignerApi( - { - baseUrl: `http://${signerIp}:9000`, - host: `web3signer.web3signer-prater.dappnode` - }, - Network.Prater - ); - const validatorApi = new ValidatorApi( - { - baseUrl: `http://${consensusIp}:3500`, - authToken: `0xd59b8238ecaeb255d62c85c6ca8aee185870bd7a27e43f85fd2658267036d94a` - }, - Network.Prater - ); - // import to web3signer - const keystoresPath = path.resolve(process.cwd(), "keystores"); - const keystoresPaths = fs.readdirSync(keystoresPath).filter((file) => file.endsWith(".json")); - const keystores = keystoresPaths.map((file) => fs.readFileSync(path.join(keystoresPath, file)).toString()); - const passwords = Array(keystores.length).fill("stakingbrain"); - await signerApi.importRemoteKeys({ - keystores, - passwords - }); - - expect(db.data).to.be.null; - // initialize - await db.initialize(signerApi, validatorApi); - // print contents of database - console.log(db.data); - // check database - expect(db.data).to.deep.equal(expectedDb); - }).timeout(10000); + it("migrates existing signer keys and their configured fee destination", async () => { + const pubkey = "0x" + "ab".repeat(48); + const feeRecipient = "0x" + "cd".repeat(20); + const signer = sinon.createStubInstance(Web3SignerApi); + signer.listRemoteKeys.resolves({ data: [{ validating_pubkey: pubkey, derivation_path: "", readonly: false }] }); + const validator = sinon.createStubInstance(ValidatorApi); + validator.getFeeRecipient.withArgs(pubkey).resolves({ data: { pubkey, ethaddress: feeRecipient } }); + const db = new BrainDataBase(filename); + await db.initialize(signer, validator); + expect(db.getData()).to.deep.equal({ [pubkey]: { tag: "solo", feeRecipient, automaticImport: false } }); + expect(fs.statSync(filename).mode & 0o777).to.equal(0o600); + }); - /** - * Create a new empty database if migration fails - */ - it("Should create a new empty database if migration fails", async () => { - const db = new BrainDataBase(testDbName); - async function databaseMigration(): Promise { - throw new Error("Database migration failed"); + it("keeps populated records unchanged without contacting external services", async () => { + const records = { + ["0x" + "ab".repeat(48)]: { + tag: "solo", + feeRecipient: "0x" + "cd".repeat(20), + automaticImport: false, + builderConfig: {} } - const signerApi = sinon.createStubInstance(Web3SignerApi); - const validatorApi = sinon.createStubInstance(ValidatorApi); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - sinon.stub(db, "databaseMigration").callsFake(databaseMigration); - await db.initialize(signerApi, validatorApi); + }; + fs.writeFileSync(filename, JSON.stringify(records)); + const signer = sinon.createStubInstance(Web3SignerApi); + const validator = sinon.createStubInstance(ValidatorApi); + const db = new BrainDataBase(filename); + await db.initialize(signer, validator); + expect(db.getData()).to.deep.equal(records); + expect(signer.listRemoteKeys.called).to.equal(false); + expect(validator.getBuilderConfig.called).to.equal(false); + }); - expect(fs.existsSync(testDbName)).to.be.true; - db.read(); - expect(db.data).to.be.empty; - }).timeout(10000); + it("refuses corrupted JSON without erasing validator metadata", async () => { + fs.writeFileSync(filename, "{invalid JSON"); + const db = new BrainDataBase(filename); + const result = await db + .initialize(sinon.createStubInstance(Web3SignerApi), sinon.createStubInstance(ValidatorApi)) + .catch((error: Error) => error); + expect(result).to.be.instanceOf(Error); + expect(() => db.getData()).to.throw("refusing to reconcile"); + expect(fs.readFileSync(filename, "utf8")).to.equal("{invalid JSON"); + }); - /** - * Do nothing if the database file exists and is valid - */ - it("Should do nothing if the database file exists and is valid", () => { - const db = new BrainDataBase(testDbName); - fs.writeFileSync(testDbName, JSON.stringify({})); - const signerApi = sinon.createStubInstance(Web3SignerApi); - const validatorApi = sinon.createStubInstance(ValidatorApi); - db.initialize(signerApi, validatorApi); - expect(fs.existsSync(testDbName)).to.be.true; - db.read(); - expect(db.data).to.be.empty; - }); + it("refuses non-object JSON rather than letting LowSync treat it as an empty database", () => { + for (const contents of ["null", "false", "0", "[]", '"invalid"']) { + fs.writeFileSync(filename, contents); + expect(() => new BrainDataBase(filename).getData()).to.throw("refusing to reconcile"); + expect(fs.readFileSync(filename, "utf8")).to.equal(contents); + } }); - /** - * Test public updatePubkeys() - */ - describe("public updatePubkeys()", () => { - it("Should update the database", () => { - // + it("preserves each validator's fee destination during migration", async () => { + const keys = ["ab", "cd"].map((byte) => "0x" + byte.repeat(48)); + const recipients = ["11", "22"].map((byte) => "0x" + byte.repeat(20)); + const signer = sinon.createStubInstance(Web3SignerApi); + signer.listRemoteKeys.resolves({ + data: keys.map((validating_pubkey) => ({ validating_pubkey, derivation_path: "", readonly: false })) }); + const validator = sinon.createStubInstance(ValidatorApi); + keys.forEach((key, index) => + validator.getFeeRecipient.withArgs(key).resolves({ data: { ethaddress: recipients[index] } }) + ); + const db = new BrainDataBase(filename); + await db.initialize(signer, validator); + expect(keys.map((key) => db.getData()[key].feeRecipient)).to.deep.equal(recipients); }); - /** - * Test public addPubkeys() - */ - describe("public addPubkeys()", () => { - /** - * Should create a new database if it doesn't exist - */ - it("Should create a new database if it doesn't exist", () => {}); - - /** - * Should throw an error if the pubkeys to be added are invalid - */ - it("Should throw an error if the pubkeys to be added are invalid", () => {}); - - /** - * Should throw an error if the pubkeys to be added and the existing pubkeys exceed the maximum database size - */ - it("Should throw an error if the pubkeys to be added and the existing pubkeys exceed the maximum database size", () => {}); - - /** - * Should add the pubkeys to the database - */ - it("Should add the pubkeys to the database", () => {}); + it("fails startup after migration retries instead of treating unavailable signers as empty", async () => { + const clock = sinon.useFakeTimers(); + const signer = sinon.createStubInstance(Web3SignerApi); + signer.listRemoteKeys.rejects(new Error("Signer unavailable")); + const db = new BrainDataBase(filename); + const pending = db.initialize(signer, sinon.createStubInstance(ValidatorApi)).catch((error: Error) => error); + await clock.runAllAsync(); + expect(await pending).to.be.instanceOf(Error); + expect(signer.listRemoteKeys.callCount).to.equal(10); }); - after(() => { - if (fs.existsSync(testDbName)) fs.unlinkSync(testDbName); + it("never guesses a burn address when an existing key's fee recipient cannot be read", async () => { + const clock = sinon.useFakeTimers(); + const signer = sinon.createStubInstance(Web3SignerApi); + const key = "0x" + "ab".repeat(48); + signer.listRemoteKeys.resolves({ data: [{ validating_pubkey: key, derivation_path: "", readonly: false }] }); + const validator = sinon.createStubInstance(ValidatorApi); + validator.getFeeRecipient.rejects(new Error("Client unavailable")); + const db = new BrainDataBase(filename); + const pending = db.initialize(signer, validator).catch((error: Error) => error); + await clock.runAllAsync(); + expect(await pending).to.be.instanceOf(Error); + expect(JSON.parse(fs.readFileSync(filename, "utf8"))).to.deep.equal({}); + expect(validator.setFeeRecipient.called).to.equal(false); }); }); diff --git a/packages/brain/test/unit/modules/keyManagement.unit.test.ts b/packages/brain/test/unit/modules/keyManagement.unit.test.ts new file mode 100644 index 00000000..f7f62104 --- /dev/null +++ b/packages/brain/test/unit/modules/keyManagement.unit.test.ts @@ -0,0 +1,280 @@ +import { expect } from "chai"; +import sinon from "sinon"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { Network } from "@stakingbrain/common"; +import { BrainDataBase } from "../../../src/modules/db/index.js"; +import { CronJob } from "../../../src/modules/cron/cron.js"; +import { ValidatorApi, Web3SignerApi } from "../../../src/modules/apiClients/index.js"; +import { importValidators } from "../../../src/modules/apiServers/ui/calls/importValidators.js"; +import { deleteValidators } from "../../../src/modules/apiServers/ui/calls/deleteValidators.js"; +import { updateValidators } from "../../../src/modules/apiServers/ui/calls/updateValidators.js"; +import { reloadValidators } from "../../../src/modules/cron/reloadValidators/index.js"; +import type { CustomImportRequest } from "../../../src/modules/apiServers/ui/calls/types.js"; +import logger from "../../../src/modules/logger/index.js"; + +const keys = ["11", "22", "33"].map((byte) => "0x" + byte.repeat(48)); +const recipient = "0x" + "ab".repeat(20); +const record = { tag: "solo" as const, feeRecipient: recipient, automaticImport: false }; + +describe("Key management partial failures", () => { + let directory: string; + let brainDb: BrainDataBase; + let signerApi: sinon.SinonStubbedInstance; + let validatorApi: sinon.SinonStubbedInstance; + let reloadValidatorsCronTask: sinon.SinonStubbedInstance; + + beforeEach(() => { + directory = fs.mkdtempSync(path.join(os.tmpdir(), "stakingbrain-keys-")); + brainDb = new BrainDataBase(path.join(directory, "db.json")); + signerApi = sinon.createStubInstance(Web3SignerApi); + validatorApi = sinon.createStubInstance(ValidatorApi); + reloadValidatorsCronTask = sinon.createStubInstance(CronJob); + signerApi.importRemoteKeys.callsFake(async ({ keystores }) => ({ + data: keystores.map(() => ({ status: "imported", message: "" })) + })); + validatorApi.postRemoteKeys.callsFake(async ({ remote_keys }) => ({ + data: remote_keys.map(() => ({ status: "imported", message: "" })) + })); + validatorApi.setFeeRecipient.resolves(); + validatorApi.deleteRemoteKeys.resolves({ data: [] }); + validatorApi.deleteFeeRecipient.resolves(); + sinon.stub(logger, "error"); + sinon.stub(logger, "debug"); + sinon.stub(logger, "info"); + }); + + afterEach(() => { + sinon.restore(); + fs.rmSync(directory, { recursive: true, force: true }); + }); + + const request = (): CustomImportRequest => ({ + importFrom: "ui", + validatorsImportRequest: keys.map((pubkey) => ({ + keystore: JSON.stringify({ pubkey: pubkey.slice(2) }), + password: "password", + tag: "solo", + feeRecipient: recipient + })) + }); + const runImport = (postRequest: CustomImportRequest) => + importValidators({ + brainDb, + signerApi, + validatorApi, + reloadValidatorsCronTask, + postRequest, + network: Network.Mainnet, + signerUrl: "http://signer:9000" + }); + + it("keeps per-key import results aligned when a protocol fee recipient cannot be resolved", async () => { + const postRequest = request(); + postRequest.validatorsImportRequest[0].tag = "stakewise"; + const result = await runImport(postRequest); + expect(result.data.map(({ status }) => status)).to.deep.equal(["error", "imported", "imported"]); + expect(Object.keys(brainDb.getData())).to.deep.equal(keys.slice(1)); + }); + + it("rejects invalid metadata before sending those keys to the signer", async () => { + const postRequest = request(); + postRequest.validatorsImportRequest[0].feeRecipient = "invalid"; + postRequest.validatorsImportRequest[1].keystore = "not JSON"; + const result = await runImport(postRequest); + expect(result.data.map(({ status }) => status)).to.deep.equal(["error", "error", "imported"]); + expect(signerApi.importRemoteKeys.firstCall.args[0].keystores).to.deep.equal([ + postRequest.validatorsImportRequest[2].keystore + ]); + expect(Object.keys(brainDb.getData())).to.deep.equal([keys[2]]); + }); + + it("recovers signer duplicates missing from Brain after an interrupted import", async () => { + signerApi.importRemoteKeys.resolves({ data: keys.map(() => ({ status: "duplicate", message: "" })) }); + await runImport(request()); + expect(Object.keys(brainDb.getData())).to.deep.equal(keys); + expect(validatorApi.postRemoteKeys.firstCall.args[0].remote_keys.map(({ pubkey }) => pubkey)).to.deep.equal(keys); + }); + + it("preserves existing settings when duplicate keys are uploaded again", async () => { + brainDb.addValidators({ validators: { [keys[0]]: { ...record, builderConfig: { min_bid: "42" } } } }); + signerApi.importRemoteKeys.resolves({ data: keys.map(() => ({ status: "duplicate", message: "" })) }); + const postRequest = request(); + postRequest.validatorsImportRequest[0].feeRecipient = "0x" + "cd".repeat(20); + await runImport(postRequest); + expect(brainDb.getData()[keys[0]]).to.deep.equal({ ...record, builderConfig: { min_bid: "42" } }); + }); + + it("recovers a lost import response after reopening the database without deleting signer keys", async () => { + signerApi.importRemoteKeys.callsFake(async () => { + const persisted = new BrainDataBase(path.join(directory, "db.json")).getData(); + expect(keys.every((key) => persisted[key].importPending)).to.equal(true); + throw new Error("Signer imported keys but its response timed out"); + }); + const result = await runImport(request()).catch((error: Error) => error); + expect(result).to.be.instanceOf(Error); + brainDb = new BrainDataBase(path.join(directory, "db.json")); + signerApi.upcheck.resolves(); + signerApi.listRemoteKeys.resolves({ data: [] }); + validatorApi.getRemoteKeys.resolves({ data: [] }); + // An early signer snapshot must not erase intent or activate unconfirmed keys. + await reloadValidators(signerApi, "http://signer", validatorApi, brainDb); + expect(Object.keys(brainDb.getData())).to.deep.equal(keys); + expect(validatorApi.postRemoteKeys.called).to.equal(false); + expect(validatorApi.setFeeRecipient.called).to.equal(false); + signerApi.listRemoteKeys.resolves({ + data: keys.map((validating_pubkey) => ({ validating_pubkey, derivation_path: "", readonly: false })) + }); + await reloadValidators(signerApi, "http://signer", validatorApi, brainDb); + expect(signerApi.deleteRemoteKeys.called).to.equal(false); + expect(validatorApi.postRemoteKeys.firstCall.args[0].remote_keys.map(({ pubkey }) => pubkey)).to.deep.equal(keys); + expect(keys.every((key) => brainDb.getData()[key].importPending === undefined)).to.equal(true); + expect(validatorApi.setFeeRecipient.callCount).to.equal(keys.length); + }); + + it("removes explicit new-key rejections but retains missing results as uncertain imports", async () => { + signerApi.importRemoteKeys.resolves({ + data: [ + { status: "imported", message: "" }, + { status: "error", message: "Wrong password" } + ] + }); + const result = await runImport(request()); + expect(result.data.map(({ status }) => status)).to.deep.equal(["imported", "error", "error"]); + const persisted = new BrainDataBase(path.join(directory, "db.json")).getData(); + expect(persisted[keys[0]]).to.deep.equal(record); + expect(persisted).not.to.have.property(keys[1]); + expect(persisted[keys[2]]).to.deep.equal({ ...record, importPending: true }); + }); + + it("does not contact the signer when durable import intent cannot be saved", async () => { + brainDb.getData(); + sinon.stub(brainDb, "write").throws(new Error("Disk full")); + const result = await runImport(request()).catch((error: Error) => error); + expect(result).to.be.instanceOf(Error); + expect(signerApi.importRemoteKeys.called).to.equal(false); + expect(reloadValidatorsCronTask.start.calledOnce).to.equal(true); + }); + + it("configures a retried pending import immediately while preserving its original settings", async () => { + brainDb.addValidators({ + validators: Object.fromEntries(keys.map((key) => [key, { ...record, importPending: true }])) + }); + signerApi.importRemoteKeys.resolves({ data: keys.map(() => ({ status: "duplicate", message: "" })) }); + const postRequest = request(); + postRequest.validatorsImportRequest.forEach((validator) => { + validator.feeRecipient = "0x" + "cd".repeat(20); + }); + await runImport(postRequest); + expect(validatorApi.postRemoteKeys.firstCall.args[0].remote_keys.map(({ pubkey }) => pubkey)).to.deep.equal(keys); + expect(validatorApi.setFeeRecipient.args).to.deep.equal(keys.map((key) => [recipient, key])); + expect(Object.values(brainDb.getData())).to.deep.equal(keys.map(() => record)); + }); + + it("retains keys whose signer deletion failed, including across a database reopen", async () => { + brainDb.addValidators({ validators: Object.fromEntries(keys.map((key) => [key, { ...record }])) }); + signerApi.deleteRemoteKeys.resolves({ + data: [ + { status: "deleted", message: "" }, + { status: "error", message: "Signer is busy" }, + { status: "not_found", message: "" } + ] + }); + await deleteValidators({ + brainDb, + signerApi, + validatorApi, + reloadValidatorsCronTask, + deleteRequest: { pubkeys: keys } + }); + expect(Object.keys(new BrainDataBase(path.join(directory, "db.json")).getData())).to.deep.equal([keys[1]]); + expect(validatorApi.deleteRemoteKeys.firstCall.args[0].pubkeys).to.deep.equal([keys[0], keys[2]]); + expect(validatorApi.deleteFeeRecipient.called).to.equal(false); + expect(reloadValidatorsCronTask.start.calledOnce).to.equal(true); + }); + + it("leaves client keys and fee recipients untouched on signer errors and timeouts", async () => { + brainDb.addValidators({ validators: { [keys[0]]: { ...record } } }); + const remove = () => + deleteValidators({ + brainDb, + signerApi, + validatorApi, + reloadValidatorsCronTask, + deleteRequest: { pubkeys: [keys[0]] } + }); + signerApi.deleteRemoteKeys.resolves({ data: [{ status: "error", message: "busy" }] }); + await remove(); + signerApi.deleteRemoteKeys.rejects(new Error("Timed out")); + expect(await remove().catch((error: Error) => error)).to.be.instanceOf(Error); + expect(validatorApi.deleteRemoteKeys.called).to.equal(false); + expect(validatorApi.deleteFeeRecipient.called).to.equal(false); + expect(brainDb.getData()[keys[0]]).to.deep.equal(record); + }); + + it("removes fee settings only after both signer and client confirm removal", async () => { + brainDb.addValidators({ validators: Object.fromEntries(keys.map((key) => [key, { ...record }])) }); + signerApi.deleteRemoteKeys.resolves({ data: keys.map(() => ({ status: "deleted", message: "" })) }); + validatorApi.deleteRemoteKeys.resolves({ + data: [ + { status: "deleted", message: "" }, + { status: "error", message: "busy" }, + { status: "not_found", message: "" } + ] + }); + await deleteValidators({ + brainDb, + signerApi, + validatorApi, + reloadValidatorsCronTask, + deleteRequest: { pubkeys: keys } + }); + expect(signerApi.deleteRemoteKeys.calledBefore(validatorApi.deleteRemoteKeys)).to.equal(true); + expect(validatorApi.deleteRemoteKeys.calledBefore(validatorApi.deleteFeeRecipient)).to.equal(true); + expect(validatorApi.deleteFeeRecipient.args.map(([pubkey]) => pubkey)).to.deep.equal([keys[0], keys[2]]); + expect(brainDb.getData()).to.deep.equal({}); + }); + + it("reports fee-recipient application failures while preserving desired settings for reload", async () => { + brainDb.addValidators({ validators: Object.fromEntries(keys.map((key) => [key, { ...record }])) }); + validatorApi.setFeeRecipient.withArgs(recipient, keys[0]).rejects(new Error("Client unavailable")); + const result = await updateValidators({ + brainDb, + validatorApi, + reloadValidatorsCronTask, + customValidatorUpdateRequest: keys.map((pubkey) => ({ pubkey, feeRecipient: recipient })) + }).catch((error: Error) => error); + expect(result).to.be.instanceOf(Error).and.have.property("message").that.includes("Settings saved"); + expect(validatorApi.setFeeRecipient.callCount).to.equal(keys.length); + expect(brainDb.getData()[keys[0]].feeRecipient).to.equal(recipient); + expect(reloadValidatorsCronTask.start.calledOnce).to.equal(true); + }); + + it("does not delete signer keys when the database becomes corrupted or disappears", async () => { + brainDb.addValidators({ validators: { [keys[0]]: { ...record } } }); + signerApi.upcheck.resolves(); + fs.writeFileSync(path.join(directory, "db.json"), "{invalid"); + await reloadValidators(signerApi, "http://signer", validatorApi, brainDb); + expect(signerApi.deleteRemoteKeys.called).to.equal(false); + expect(signerApi.listRemoteKeys.called).to.equal(false); + fs.rmSync(path.join(directory, "db.json")); + await reloadValidators(signerApi, "http://signer", validatorApi, brainDb); + expect(signerApi.deleteRemoteKeys.called).to.equal(false); + expect(signerApi.listRemoteKeys.called).to.equal(false); + }); + + it("does not remove keys when signer and validator APIs use different hex casing", async () => { + const pubkey = "0x" + "ab".repeat(48); + const upper = "0x" + "AB".repeat(48); + brainDb.addValidators({ validators: { [pubkey]: { ...record } } }); + signerApi.upcheck.resolves(); + signerApi.listRemoteKeys.resolves({ data: [{ validating_pubkey: upper, derivation_path: "", readonly: false }] }); + validatorApi.getRemoteKeys.resolves({ data: [{ pubkey: upper, readonly: false, url: "http://signer" }] }); + await reloadValidators(signerApi, "http://signer", validatorApi, brainDb); + expect(signerApi.deleteRemoteKeys.called).to.equal(false); + expect(validatorApi.deleteRemoteKeys.called).to.equal(false); + expect(validatorApi.postRemoteKeys.called).to.equal(false); + expect(Object.keys(brainDb.getData())).to.deep.equal([pubkey]); + }); +}); diff --git a/packages/brain/test/unit/modules/rpcHandler.unit.test.ts b/packages/brain/test/unit/modules/rpcHandler.unit.test.ts new file mode 100644 index 00000000..e4dc48b5 --- /dev/null +++ b/packages/brain/test/unit/modules/rpcHandler.unit.test.ts @@ -0,0 +1,64 @@ +import { expect } from "chai"; +import sinon from "sinon"; +import { createRpcHandler } from "../../../src/modules/apiServers/ui/rpcHandler.js"; +import type { RpcMethods } from "../../../src/modules/apiServers/ui/calls/types.js"; +import logger from "../../../src/modules/logger/index.js"; + +describe("RPC server dispatch", () => { + beforeEach(() => { + sinon.stub(logger, "error"); + sinon.stub(logger, "debug"); + }); + afterEach(() => sinon.restore()); + + it("returns validation errors for malformed messages without executing methods", async () => { + const getValidators = sinon.stub().resolves([]); + const handler = createRpcHandler({ getValidators } as unknown as RpcMethods); + for (const request of [ + null, + undefined, + [], + "rpc", + 123, + {}, + { jsonrpc: "2.0", method: [] }, + { jsonrpc: "2.0", method: "getValidators", id: {} } + ]) { + const ack = sinon.stub(); + await handler(request, ack); + expect(ack.firstCall.args[0].error.code).to.equal(-32600); + } + expect(getValidators.called).to.equal(false); + }); + + it("ignores messages with no acknowledgement and rejects inherited method names", async () => { + const getValidators = sinon.stub().resolves([]); + const handler = createRpcHandler({ getValidators } as unknown as RpcMethods); + await handler(null, undefined); + await handler({ jsonrpc: "2.0", method: "getValidators" }, "not a callback"); + expect(getValidators.called).to.equal(false); + for (const method of ["toString", "constructor", "__proto__", "missing"]) { + const ack = sinon.stub(); + await handler({ jsonrpc: "2.0", method, id: 7 }, ack); + expect(ack.firstCall.args[0]).to.include({ id: 7 }); + expect(ack.firstCall.args[0].error.code).to.equal(-32601); + } + }); + + it("preserves request IDs, parameters, successful responses and operation failures", async () => { + const getBuilderConfigs = sinon.stub().resolves([{ pubkey: "test" }]); + const handler = createRpcHandler({ getBuilderConfigs } as unknown as RpcMethods); + const ack = sinon.stub(); + const request = { jsonrpc: "2.0", method: "getBuilderConfigs", params: ["key"], id: "request-1" }; + await handler(request, ack); + expect(getBuilderConfigs.firstCall.args).to.deep.equal([["key"]]); + expect(ack.firstCall.args[0]).to.deep.equal({ jsonrpc: "2.0", result: [{ pubkey: "test" }], id: request.id }); + getBuilderConfigs.rejects(new Error("Client unavailable")); + await handler(request, ack); + expect(ack.lastCall.args[0]).to.deep.equal({ + jsonrpc: "2.0", + id: request.id, + error: { code: -32000, message: "Client unavailable" } + }); + }); +}); diff --git a/packages/brain/test/unit/modules/socketOrigins.unit.test.ts b/packages/brain/test/unit/modules/socketOrigins.unit.test.ts new file mode 100644 index 00000000..f6a6ba8d --- /dev/null +++ b/packages/brain/test/unit/modules/socketOrigins.unit.test.ts @@ -0,0 +1,43 @@ +import { expect } from "chai"; +import http from "node:http"; +import { Server } from "socket.io"; +import { Network } from "@stakingbrain/common"; +import { allowedOrigins, uiSocketOptions } from "../../../src/modules/apiServers/ui/config.js"; + +describe("UI WebSocket origin checks", () => { + it("rejects untrusted browser handshakes while allowing the UI and non-browser clients", () => { + const io = new Server(http.createServer(), uiSocketOptions(allowedOrigins(Network.Mainnet))); + try { + // Exercise Engine.IO's real upgrade verification without binding a network port. + const engine = io.engine as unknown as { + verify: (request: unknown, upgrade: boolean, callback: (error?: number) => void) => void; + }; + for (const origin of ["http://my.dappnode", "http://brain.web3signer.dappnode", undefined]) { + let result: number | undefined = -1; + engine.verify( + { method: "GET", headers: { origin }, _query: { transport: "websocket", EIO: "4" } }, + true, + (error) => { + result = error; + } + ); + expect(result, origin).to.equal(undefined); + } + let rejected = false; + engine.verify( + { + method: "GET", + headers: { origin: "https://untrusted.example" }, + _query: { transport: "websocket", EIO: "4" } + }, + true, + (error) => { + rejected = typeof error === "number"; + } + ); + expect(rejected).to.equal(true); + } finally { + io.close(); + } + }); +}); diff --git a/packages/brain/test/unit/modules/validatorList.unit.test.ts b/packages/brain/test/unit/modules/validatorList.unit.test.ts new file mode 100644 index 00000000..0f6ed551 --- /dev/null +++ b/packages/brain/test/unit/modules/validatorList.unit.test.ts @@ -0,0 +1,168 @@ +import { expect } from "chai"; +import sinon from "sinon"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { BrainDataBase } from "../../../src/modules/db/index.js"; +import { ValidatorApi, Web3SignerApi, BeaconchainApi } from "../../../src/modules/apiClients/index.js"; +import { getValidators } from "../../../src/modules/apiServers/ui/calls/getValidators.js"; +import logger from "../../../src/modules/logger/index.js"; +import type { BeaconchainValidatorFromStateGetResponse } from "../../../src/modules/apiClients/beaconchain/types.js"; +import { ValidatorStatus } from "../../../src/modules/apiClients/beaconchain/types.js"; + +const keys = Array.from({ length: 30 }, (_, index) => "0x" + (index + 1).toString(16).padStart(96, "0")); +const feeRecipient = "0x" + "ab".repeat(20); +const beaconResponse = (pubkey: string, address: string): BeaconchainValidatorFromStateGetResponse => ({ + execution_optimistic: false, + data: { + index: String(keys.indexOf(pubkey)), + balance: "32000000000", + status: ValidatorStatus.ACTIVE_ONGOING, + validator: { + pubkey, + withdrawal_credentials: address, + effective_balance: "32000000000", + slashed: false, + activation_eligibility_epoch: "0", + activation_epoch: "0", + exit_epoch: "18446744073709551615", + withdrawable_epoch: "18446744073709551615" + } + } +}); + +describe("Validator list bulk reads", () => { + let directory: string; + let brainDb: BrainDataBase; + let validatorApi: sinon.SinonStubbedInstance; + let signerApi: sinon.SinonStubbedInstance; + let beaconchainApi: sinon.SinonStubbedInstance; + beforeEach(() => { + directory = fs.mkdtempSync(path.join(os.tmpdir(), "stakingbrain-list-")); + brainDb = new BrainDataBase(path.join(directory, "db.json")); + brainDb.addValidators({ + validators: Object.fromEntries(keys.map((key) => [key, { tag: "solo", feeRecipient, automaticImport: false }])) + }); + validatorApi = sinon.createStubInstance(ValidatorApi); + signerApi = sinon.createStubInstance(Web3SignerApi); + beaconchainApi = sinon.createStubInstance(BeaconchainApi); + validatorApi.getRemoteKeys.resolves({ + data: keys.map((pubkey) => ({ pubkey, readonly: false, url: "http://signer" })) + }); + signerApi.listRemoteKeys.resolves({ + data: keys.map((validating_pubkey) => ({ validating_pubkey, derivation_path: "", readonly: false })) + }); + validatorApi.getFeeRecipient.resolves({ data: { ethaddress: feeRecipient } }); + sinon.stub(logger, "error"); + }); + afterEach(() => { + sinon.restore(); + fs.rmSync(directory, { recursive: true, force: true }); + }); + const read = () => getValidators({ brainDb, validatorApi, signerApi, beaconchainApi }); + + it("keeps successful fee checks when a different validator fails", async () => { + validatorApi.getFeeRecipient.withArgs(keys[0]).rejects(new Error("key missing")); + beaconchainApi.getStateValidator.rejects(new Error("not deposited")); + const results = await read(); + expect(results.map(({ pubkey }) => pubkey)).to.deep.equal(keys); + expect(results[0].validatorFeeRecipientCorrect).to.equal(undefined); + expect(results.slice(1).every(({ validatorFeeRecipientCorrect }) => validatorFeeRecipientCorrect)).to.equal(true); + }); + + it("bounds concurrent beacon reads and stops the whole list at its deadline", async () => { + const clock = sinon.useFakeTimers(); + beaconchainApi.getStateValidator.callsFake( + async ({ signal }) => + new Promise((_resolve, reject) => { + if (signal?.aborted) reject(new Error("Aborted")); + signal!.addEventListener("abort", () => reject(new Error("Aborted")), { once: true }); + }) + ); + const pending = read(); + await clock.tickAsync(9999); + expect(beaconchainApi.getStateValidator.callCount).to.equal(8); + await clock.tickAsync(1); + const results = await pending; + expect(results.map(({ pubkey }) => pubkey)).to.deep.equal(keys); + expect(results.every(({ withdrawalCredentials }) => withdrawalCredentials.format === "error")).to.equal(true); + expect(beaconchainApi.getStateValidator.callCount).to.equal(8); + expect(clock.countTimers()).to.equal(0); + }); + + it("finishes later beacon reads on refresh and retains earlier results without delaying fee checks", async () => { + const clock = sinon.useFakeTimers(); + const address = "0x01" + "00".repeat(11) + "ab".repeat(20); + beaconchainApi.getStateValidator.callsFake( + ({ pubkey, signal }) => + new Promise((resolve, reject) => { + const abort = () => { + clearTimeout(timer); + reject(new Error("Aborted")); + }; + const timer = setTimeout(() => { + signal!.removeEventListener("abort", abort); + resolve(beaconResponse(pubkey, address)); + }, 4000); + signal!.addEventListener("abort", abort, { once: true }); + }) + ); + const first = read(); + await clock.tickAsync(10000); + const firstResults = await first; + expect(firstResults.filter(({ index }) => index !== "").length).to.equal(16); + expect(firstResults.every(({ validatorFeeRecipientCorrect }) => validatorFeeRecipientCorrect)).to.equal(true); + const second = read(); + await clock.tickAsync(10000); + const secondResults = await second; + expect(secondResults.map(({ index }) => index)).to.deep.equal(keys.map((_, index) => String(index))); + expect(secondResults.every(({ withdrawalCredentials }) => withdrawalCredentials.address === address)).to.equal( + true + ); + // Expiration forces fresh reads so changed withdrawal credentials are not cached forever. + await clock.tickAsync(60000); + beaconchainApi.getStateValidator.resetHistory(); + const third = read(); + await clock.tickAsync(10000); + await third; + expect(beaconchainApi.getStateValidator.callCount).to.be.greaterThan(0); + expect(clock.countTimers()).to.equal(0); + }); + + it("rotates stalled fee reads and keeps unverified results unknown", async () => { + const clock = sinon.useFakeTimers(); + beaconchainApi.getStateValidator.rejects(new Error("not deposited")); + validatorApi.getFeeRecipient.callsFake( + async (_pubkey, signal) => + new Promise((_resolve, reject) => { + signal!.addEventListener("abort", () => reject(new Error("Aborted")), { once: true }); + }) + ); + const first = read(); + await clock.tickAsync(10000); + expect( + (await first).every(({ validatorFeeRecipientCorrect }) => validatorFeeRecipientCorrect === undefined) + ).to.equal(true); + expect(validatorApi.getFeeRecipient.args.map(([pubkey]) => pubkey)).to.deep.equal(keys.slice(0, 8)); + validatorApi.getFeeRecipient.resetHistory(); + const second = read(); + await clock.tickAsync(10000); + await second; + expect(validatorApi.getFeeRecipient.args.map(([pubkey]) => pubkey)).to.deep.equal(keys.slice(8, 16)); + expect(clock.countTimers()).to.equal(0); + }); + + it("keeps membership unknown on list failures while independently reading beacon data", async () => { + validatorApi.getRemoteKeys.rejects(new Error("unavailable")); + signerApi.listRemoteKeys.rejects(new Error("unavailable")); + beaconchainApi.getStateValidator.callsFake(async ({ pubkey }) => beaconResponse(pubkey, "0x01" + "00".repeat(31))); + const results = await read(); + expect( + results.every( + ({ signerImported, validatorImported, validatorFeeRecipientCorrect }) => + signerImported === undefined && validatorImported === undefined && validatorFeeRecipientCorrect === undefined + ) + ).to.equal(true); + expect(results.map(({ index }) => index)).to.deep.equal(keys.map((_, index) => String(index))); + }); +}); diff --git a/packages/brain/test/unit/modules/validatorsDataIngest/getStartAndEndEpochs.unit.test.ts b/packages/brain/test/unit/modules/validatorsDataIngest/getStartAndEndEpochs.unit.test.ts index 55f90369..a54d037f 100644 --- a/packages/brain/test/unit/modules/validatorsDataIngest/getStartAndEndEpochs.unit.test.ts +++ b/packages/brain/test/unit/modules/validatorsDataIngest/getStartAndEndEpochs.unit.test.ts @@ -1,15 +1,15 @@ -import { getStartAndEndEpochs } from "../../../../dist/modules/validatorsDataIngest/getStartAndEndEpochs.js"; +import { getStartAndEndEpochs } from "../../../../src/modules/validatorsDataIngest/getStartAndEndEpochs.js"; import { expect } from "chai"; describe("validatorsDataIngest - getStartAndEndEpochs", () => { - it("should return correct start and end epochs for the given date range in Holesky", () => { + it("should return correct start and end epochs for the given date range in Hoodi", () => { // Define constants - const minGenesisTime = 1695902100; // Use the provided minGenesisTime + const minGenesisTime = 1742212800; // Use the provided minGenesisTime const secondsPerSlot = 12; // Use the provided secondsPerSlot // Define date range for testing - const startDate = new Date("2024-09-24T06:40:00.000Z"); - const endDate = new Date("2024-09-24T07:05:36.000Z"); + const startDate = new Date("2026-03-14T06:40:00.000Z"); + const endDate = new Date("2026-03-14T07:05:36.000Z"); // Calculate expected epochs const expectedStartEpoch = 81400; @@ -30,7 +30,7 @@ describe("validatorsDataIngest - getStartAndEndEpochs", () => { }); }); - it("should return correct start and end epochs for the given date range in Holesky", () => { + it("should return correct start and end epochs for the given date range on mainnet", () => { // Define constants const minGenesisTime = 1606824000; // Min genesis time ethereum const secondsPerSlot = 12; // Use the provided secondsPerSlot diff --git a/packages/brain/test/unit/modules/validatorsDataIngest/index.unit.test.ts b/packages/brain/test/unit/modules/validatorsDataIngest/index.unit.test.ts deleted file mode 100644 index 096cf55c..00000000 --- a/packages/brain/test/unit/modules/validatorsDataIngest/index.unit.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { PostgresClient } from "../../../../src/modules/apiClients/index.js"; -import { fetchAndProcessValidatorsData } from "../../../../src/modules/validatorsDataIngest/index.js"; - -// This test must be executed with a real database connection - -describe.skip("Validators data ingest", function () { - this.timeout(10 * 1000); - // change the dbUrl on demmand - const dbUrl = "postgres://postgres:password@postgres.web3signer-holesky.dappnode:5432/web3signer"; - const postgresClient = new PostgresClient(dbUrl); - - it("should fetch and process validators data", async () => { - const validatorIndexes = ["1802289", "1802258"]; - const minGenesisTime = 1695902100; - const secondsPerSlot = 12; - - const data = await fetchAndProcessValidatorsData({ - validatorIndexes, - postgresClient, - minGenesisTime, - secondsPerSlot, - numberOfDaysToQuery: 1 - }); - - console.log(data); - }); -}); diff --git a/packages/brain/tsconfig.json b/packages/brain/tsconfig.json index dfb47b62..7d9ebce3 100644 --- a/packages/brain/tsconfig.json +++ b/packages/brain/tsconfig.json @@ -3,6 +3,8 @@ "include": ["src"], "exclude": ["./test/", "./node_modules/", "./dist/"], "compilerOptions": { - "outDir": "dist" + "rootDir": "src", + "outDir": "dist", + "tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo" } } diff --git a/packages/brain/tsconfig.test.json b/packages/brain/tsconfig.test.json index 0cdb9a18..d992a247 100644 --- a/packages/brain/tsconfig.test.json +++ b/packages/brain/tsconfig.test.json @@ -1,7 +1,7 @@ { "extends": "../../tsconfig.json", "compilerOptions": { - "rootDir": "./test" // Root directory of input files + "rootDir": "." // Root directory of input files }, "include": ["test"], "exclude": ["node_modules", "dist"] diff --git a/packages/common/.mocharc.yaml b/packages/common/.mocharc.yaml index 41e7c635..443f3cbd 100644 --- a/packages/common/.mocharc.yaml +++ b/packages/common/.mocharc.yaml @@ -4,5 +4,4 @@ extension: [ts] require: - dotenv/config node-option: - - experimental-specifier-resolution=node - - import=tsx/esm + - import=tsx diff --git a/packages/common/package.json b/packages/common/package.json index c507ed4a..d49e6c92 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -12,8 +12,8 @@ "test:unit": "mocha -r dotenv/config --exit --recursive 'test/**/*.unit.test.ts'" }, "devDependencies": { - "@types/sinon": "^10.0.13", - "sinon": "^15.0.1", - "typescript": "^5.5.4" + "@types/sinon": "^22.0.0", + "sinon": "^22.1.0", + "typescript": "~6.0.3" } } diff --git a/packages/common/src/builderConfig.ts b/packages/common/src/builderConfig.ts new file mode 100644 index 00000000..a296179e --- /dev/null +++ b/packages/common/src/builderConfig.ts @@ -0,0 +1,104 @@ +import { isValidBlsPubkey } from "./utils/index.js"; + +/** Keymanager overrides, not the resolved configuration returned by GET. Uint64s stay strings. */ +export interface BuilderConfig { + min_bid?: string; + builder_boost_factor?: string; + builders?: BuilderEntry[]; +} + +export interface BuilderEntry { + url: string; + auth_data?: string; + builder_pubkeys?: string[]; + max_execution_payment?: string; + min_bid?: string; + builder_boost_factor?: string; +} + +export interface BuilderConfigStatus { + status: "pending" | "applied" | "error" | "unsupported"; + message?: string; +} + +const uintFields = ["min_bid", "builder_boost_factor"]; + +function object(value: unknown, label: string): asserts value is Record { + if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must be an object`); +} + +function fields(value: Record, allowed: string[], label: string): void { + for (const key of Object.keys(value)) { + if (!allowed.includes(key)) throw new Error(`${label}.${key} is not supported`); + } +} + +function uint64(value: unknown, label: string): void { + if (typeof value !== "string" || !/^(0|[1-9][0-9]{0,19})$/.test(value) || BigInt(value) > 18446744073709551615n) + throw new Error(`${label} must be a decimal-string uint64 (0 to 18446744073709551615)`); +} + +/** The Gloas default authorization identity. Do not persist this derived value as an override. */ +export function defaultBuilderAuthData(url: string): string { + const parsed = new URL(url); + const authority = url.match(/^https?:\/\/([^/?#]*)/i)?.[1]; + if (!authority) throw new Error("Builder URL must have an HTTP(S) authority"); + const hostPort = authority.slice(authority.lastIndexOf("@") + 1); + // URL.hostname canonicalizes IPv6 correctly, but also rewrites abbreviated/numeric + // IPv4 hosts. Gloas only canonicalizes IPv6; preserve other ASCII hostname spelling. + const hostname = hostPort.startsWith("[") ? parsed.hostname : hostPort.split(":")[0].toLowerCase(); + return "0x" + Array.from(new TextEncoder().encode(hostname), (byte) => byte.toString(16).padStart(2, "0")).join(""); +} + +/** Validate without coercing, resolving defaults, or dropping unknown input. */ +export function validateBuilderConfig(value: unknown): asserts value is BuilderConfig { + object(value, "builderConfig"); + fields(value, [...uintFields, "builders"], "builderConfig"); + for (const field of uintFields) if (field in value) uint64(value[field], field); + if (!("builders" in value)) return; + if (!Array.isArray(value.builders) || value.builders.length > 64) + throw new Error("builders must be an array with at most 64 entries"); + + const identities = new Set(); + for (const [index, entry] of value.builders.entries()) { + const label = `builders[${index}]`; + object(entry, label); + fields(entry, ["url", "auth_data", "builder_pubkeys", "max_execution_payment", ...uintFields], label); + if (typeof entry.url !== "string" || entry.url.length === 0 || entry.url.length > 2048) + throw new Error(`${label}.url must be a URI of 1 to 2048 characters`); + // Builder requests use HTTP. ASCII URI spelling also keeps default auth data deterministic. + let url: URL; + try { + url = new URL(entry.url); + } catch { + throw new Error(`${label}.url must be an absolute HTTP(S) URL`); + } + if ( + !/^https?:\/\//i.test(entry.url) || + !/^https?:$/.test(url.protocol) || + !url.hostname || + /[^a-zA-Z0-9._~:/?#[\]@!$&'()*+,;=%-]/.test(entry.url) || + /%(?![a-fA-F0-9]{2})/.test(entry.url) + ) + throw new Error(`${label}.url must be an ASCII HTTP(S) URL (use punycode for internationalized hostnames)`); + if ( + "auth_data" in entry && + (typeof entry.auth_data !== "string" || !/^0x(?:[a-fA-F0-9]{2}){1,4096}$/.test(entry.auth_data)) + ) + throw new Error(`${label}.auth_data must be 1 to 4096 bytes, hex encoded with 0x prefix`); + if ( + "builder_pubkeys" in entry && + (!Array.isArray(entry.builder_pubkeys) || + entry.builder_pubkeys.length > 64 || + entry.builder_pubkeys.some((key) => typeof key !== "string" || !isValidBlsPubkey(key))) + ) + throw new Error(`${label}.builder_pubkeys must contain at most 64 BLS public keys (48 bytes, 0x prefixed)`); + for (const field of [...uintFields, "max_execution_payment"]) + if (field in entry) uint64(entry[field], `${label}.${field}`); + const auth = + typeof entry.auth_data === "string" ? entry.auth_data.toLowerCase() : defaultBuilderAuthData(entry.url); + const identity = JSON.stringify([entry.url, auth]); + if (identities.has(identity)) throw new Error(`${label} duplicates a builder URL and authorization identity`); + identities.add(identity); + } +} diff --git a/packages/common/src/builderRelayPresets.ts b/packages/common/src/builderRelayPresets.ts new file mode 100644 index 00000000..7e1c8538 --- /dev/null +++ b/packages/common/src/builderRelayPresets.ts @@ -0,0 +1,15 @@ +import { Network } from "./types/index.js"; + +export interface BuilderRelayPreset { + id: string; + name: string; + url: string; + /** Filtering for this network and endpoint, not the operator's other relays. */ + ofacFiltering: "yes" | "no" | "unknown"; +} + +export interface BuilderRelayPresets { + network: Network; + sourceUrl: string; + relays: BuilderRelayPreset[]; +} diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 4b9da042..94830437 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -1,3 +1,6 @@ export * from "./types/index.js"; export * from "./utils/index.js"; export * from "./params.js"; +export * from "./builderConfig.js"; +export * from "./builderRelayPresets.js"; +export * from "./rpc.js"; diff --git a/packages/common/src/params.ts b/packages/common/src/params.ts index c18560b0..286acc24 100644 --- a/packages/common/src/params.ts +++ b/packages/common/src/params.ts @@ -6,15 +6,11 @@ export const ROCKET_POOL_FEE_RECIPIENT = "0xd4E96eF8eee8678dBFf4d535E033Ed1a4F76 // https://docs.lido.fi/deployed-contracts/#core-protocol export const LIDO_FEE_RECIPIENT_MAINNET = "0x388C818CA8B9251b393131C08a736A67ccB19297" as const; -// https://docs.lido.fi/deployed-contracts/holesky/#core-protocol -export const LIDO_FEE_RECIPIENT_HOLESKY = "0xE73a3602b99f1f913e72F8bdcBC235e206794Ac8" as const; // https://docs.lido.fi/deployed-contracts/hoodi#core-protocol export const LIDO_FEE_RECIPIENT_HOODI = "0x9b108015fe433F173696Af3Aa0CF7CDb3E104258" as const; export const STADER_POOL_FEE_RECIPIENT_MAINNET = "0x9d4C3166c59412CEdBe7d901f5fDe41903a1d6Fc" as const; -export const STADER_POOL_FEE_RECIPIENT_PRATER = "0x34E8Fac4962AF984d6040cec240d1d12eFfac14E" as const; - export const MEV_SP_ADDRESS_HOODI = "0x9CDcc499D53Be0ADb5056355Be774828a593F267" as const; export const MEV_SP_ADDRESS_MAINNET = "0xAdFb8D27671F14f297eE94135e266aAFf8752e35" as const; diff --git a/packages/common/src/rpc.ts b/packages/common/src/rpc.ts new file mode 100644 index 00000000..34dc908c --- /dev/null +++ b/packages/common/src/rpc.ts @@ -0,0 +1,3 @@ +// The database allows 6 MiB. Leave room for RPC framing and bulk settings requests. +export const RPC_MAX_PAYLOAD_BYTES = 8 * 1024 * 1024; +export const RPC_TIMEOUT_MS = 120 * 1000; diff --git a/packages/common/src/types/index.ts b/packages/common/src/types/index.ts index 0d332ccb..2ae8ef64 100644 --- a/packages/common/src/types/index.ts +++ b/packages/common/src/types/index.ts @@ -1,9 +1,7 @@ export enum Network { Mainnet = "mainnet", Gnosis = "gnosis", - Prater = "prater", Lukso = "lukso", - Holesky = "holesky", Hoodi = "hoodi" } diff --git a/packages/common/test/unit/builderConfig.unit.test.ts b/packages/common/test/unit/builderConfig.unit.test.ts new file mode 100644 index 00000000..566729fd --- /dev/null +++ b/packages/common/test/unit/builderConfig.unit.test.ts @@ -0,0 +1,119 @@ +import { expect } from "chai"; +import { defaultBuilderAuthData, validateBuilderConfig } from "../../src/builderConfig.js"; + +const max = "18446744073709551615"; +const url = "https://builder.example.com"; +const pubkey = "0x" + "ab".repeat(48); + +describe("Builder configuration validation", () => { + it("preserves omission, empty builders, explicit zero and full uint64 precision", () => { + for (const config of [ + {}, + { builders: [] }, + { min_bid: "0", builder_boost_factor: max }, + { + builders: [ + { + url, + auth_data: "0xaB", + builder_pubkeys: [pubkey], + max_execution_payment: max, + min_bid: "9007199254740993", + builder_boost_factor: "0" + } + ] + } + ]) { + const before = JSON.stringify(config); + validateBuilderConfig(config); + expect(JSON.stringify(config)).to.equal(before); + } + }); + + it("rejects imprecise numbers, malformed decimals, overflow and null fields", () => { + for (const value of [ + 0, + 9007199254740992, + -1, + "-1", + "01", + "1.0", + "1e3", + "", + "18446744073709551616", + "999999999999999999999", + null, + undefined + ]) { + expect(() => validateBuilderConfig({ min_bid: value })).to.throw(); + expect(() => validateBuilderConfig({ builder_boost_factor: value })).to.throw(); + expect(() => validateBuilderConfig({ builders: [{ url, max_execution_payment: value }] })).to.throw(); + } + }); + + it("validates URL, auth bytes, public keys, array limits and unknown fields", () => { + for (const entry of [ + {}, + { url: "" }, + { url: "/builder" }, + { url: "file:///tmp/key" }, + { url: "https://büilder.example" }, + { url: "https://builder.example/has space" }, + { url: "https://builder.example/" + "a".repeat(2048) }, + { url, auth_data: "0x" }, + { url, auth_data: "0x1" }, + { url, auth_data: "0xgg" }, + { url, auth_data: "0x" + "ab".repeat(4097) }, + { url, builder_pubkeys: [pubkey.slice(2)] }, + { url, builder_pubkeys: ["0x" + "ab".repeat(20)] }, + { url, builder_pubkeys: Array(65).fill(pubkey) }, + { url, fee_recipient: "0x" + "11".repeat(20) } + ]) + expect(() => validateBuilderConfig({ builders: [entry] })).to.throw(); + for (const config of [ + null, + [], + { builders: null }, + { builders: Array(65).fill({ url }) }, + { fee_recipient: "0x" + "11".repeat(20) } + ]) + expect(() => validateBuilderConfig(config)).to.throw(); + validateBuilderConfig({ + builders: [{ url, auth_data: "0x" + "ab".repeat(4096), builder_pubkeys: Array(64).fill(pubkey) }] + }); + }); + + it("compares exact URLs and decoded auth bytes, including derived auth", () => { + expect(() => + validateBuilderConfig({ + builders: [ + { url, auth_data: "0xaB" }, + { url, auth_data: "0xAb" } + ] + }) + ).to.throw("duplicates"); + expect(() => + validateBuilderConfig({ builders: [{ url }, { url, auth_data: defaultBuilderAuthData(url) }] }) + ).to.throw("duplicates"); + validateBuilderConfig({ + builders: [ + { url, auth_data: "0x01" }, + { url, auth_data: "0x02" }, + { url: url + "/", auth_data: "0x01" } + ] + }); + }); + + it("derives the specified ASCII hostname identities including compressed IPv6", () => { + const cases = [ + ["HTTPS://Builder.Example.com:443/bids?x=1", "builder.example.com"], + ["https://user:pw@builder.example.com/", "builder.example.com"], + ["https://10.0.0.5:18550/eth/v1/builder", "10.0.0.5"], + ["https://127.1:18550/", "127.1"], + ["https://[0:0:0:0:0:0:0:1]:8443/", "[::1]"], + ["https://[::ffff:192.0.2.1]/", "[::ffff:c000:201]"] + ]; + for (const [input, expected] of cases) + expect(defaultBuilderAuthData(input)).to.equal("0x" + Buffer.from(expected).toString("hex")); + }); +}); diff --git a/packages/common/tsconfig.json b/packages/common/tsconfig.json index 482a4f22..5bafe48f 100644 --- a/packages/common/tsconfig.json +++ b/packages/common/tsconfig.json @@ -3,7 +3,9 @@ "include": ["src"], "exclude": ["test", "./node_modules/", "./dist/"], "compilerOptions": { + "rootDir": "src", "outDir": "dist", + "tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo", "skipLibCheck": true } } diff --git a/packages/common/tsconfig.test.json b/packages/common/tsconfig.test.json index 0cdb9a18..d992a247 100644 --- a/packages/common/tsconfig.test.json +++ b/packages/common/tsconfig.test.json @@ -1,7 +1,7 @@ { "extends": "../../tsconfig.json", "compilerOptions": { - "rootDir": "./test" // Root directory of input files + "rootDir": "." // Root directory of input files }, "include": ["test"], "exclude": ["node_modules", "dist"] diff --git a/packages/ui/.mocharc.yaml b/packages/ui/.mocharc.yaml index 41e7c635..443f3cbd 100644 --- a/packages/ui/.mocharc.yaml +++ b/packages/ui/.mocharc.yaml @@ -4,5 +4,4 @@ extension: [ts] require: - dotenv/config node-option: - - experimental-specifier-resolution=node - - import=tsx/esm + - import=tsx diff --git a/packages/ui/index.html b/packages/ui/index.html index 4c490ebc..2c71cb11 100644 --- a/packages/ui/index.html +++ b/packages/ui/index.html @@ -5,37 +5,14 @@ - + - - Key Manager UI
- diff --git a/packages/ui/package.json b/packages/ui/package.json index ef82f920..6af7d3a6 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -4,23 +4,19 @@ "private": true, "type": "module", "dependencies": { - "@babel/core": "^7.0.0", - "@emotion/react": "^11.7.0", - "@emotion/styled": "^11.6.0", - "@mui/icons-material": "^5.2.4", - "@mui/material": "^5.2.3", - "@mui/system": "^5.4.1", - "@mui/x-data-grid": "^5.1.0", + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "@mui/icons-material": "^7.3.11", + "@mui/material": "^7.3.11", + "@mui/system": "^7.3.11", + "@mui/x-data-grid": "^8.29.5", "@stakingbrain/brain": "workspace:^0.1.0", "@stakingbrain/common": "workspace:^0.1.0", - "history": "5", - "lodash-es": "^4.17.21", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "react-dropzone": "^11.4.2", - "react-router-dom": "^6.26.1", - "socket.io-client": "^4.7.5", - "vite-tsconfig-paths": "^5.0.1" + "react": "^19.3.1", + "react-dom": "^19.3.1", + "react-dropzone": "^20.1.4", + "react-router-dom": "^7.18.4", + "socket.io-client": "^4.8.4" }, "scripts": { "start": "vite", @@ -29,31 +25,10 @@ "test": "mocha -r dotenv/config --exit --recursive 'test/**/*.test.ts'", "dev": "vite build --watch --mode=development" }, - "eslintConfig": { - "extends": [ - "react-app", - "react-app/jest" - ] - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - }, "devDependencies": { - "@babel/plugin-syntax-flow": "^7.23.3", - "@babel/plugin-transform-react-jsx": "^7.22.15", - "@types/lodash-es": "^4.17.6", - "@types/react": "^17.0.0", - "@types/react-dom": "^17.0.0", - "@vitejs/plugin-react": "^4.3.1", - "vite": "^5.4.2" + "@types/react": "^19.3.0", + "@types/react-dom": "^19.3.0", + "@vitejs/plugin-react": "^6.1.2", + "vite": "^8.3.4" } } diff --git a/packages/ui/src/App.tsx b/packages/ui/src/App.tsx index ba522080..d8384eb6 100644 --- a/packages/ui/src/App.tsx +++ b/packages/ui/src/App.tsx @@ -1,8 +1,10 @@ +import type { JSX } from "react"; import { Alert, CircularProgress } from "@mui/material"; import TopBar from "./components/TopBar/TopBar"; import ImportScreen from "./ImportScreen"; import ValidatorList from "./components/ValidatorList/ValidatorList"; -import { ThemeProvider, createTheme } from "@mui/material/styles"; +import { ThemeProvider } from "@mui/material/styles"; +import { createAppTheme } from "./Styles/theme"; import CssBaseline from "@mui/material/CssBaseline"; import { BrowserRouter, Route, Routes } from "react-router-dom"; import React, { useEffect } from "react"; @@ -10,9 +12,26 @@ import { StakerConfig } from "@stakingbrain/common"; import { rpcClient } from "./socket"; import type { Web3SignerStatus } from "@stakingbrain/brain"; +const COLOR_MODE_STORAGE_KEY = "stakingbrain.color-mode"; + function App(): JSX.Element { - const [mode, setMode] = React.useState<"dark" | "light">("light"); + const [mode, setMode] = React.useState<"dark" | "light">(() => { + try { + return window.localStorage.getItem(COLOR_MODE_STORAGE_KEY) === "dark" ? "dark" : "light"; + } catch { + return "light"; + } + }); const [userMode, setUserMode] = React.useState<"basic" | "advanced">("basic"); + const theme = React.useMemo(() => createAppTheme(mode), [mode]); + + useEffect(() => { + try { + window.localStorage.setItem(COLOR_MODE_STORAGE_KEY, mode); + } catch { + // Keep theme switching available when browser storage is disabled. + } + }, [mode]); const [signerStatus, setSignerStatus] = React.useState("LOADING"); const [stakerConfig, setStakerConfig] = React.useState(); @@ -50,13 +69,7 @@ function App(): JSX.Element { } return ( - + ) : ( <> - + Web3Signer is not available. {signerStatus === "DOWN" ? ( <> Its API is responsive, but signer is down. @@ -90,7 +103,7 @@ function App(): JSX.Element { To avoid slashing, do not upload your validator keystores to another machine. - + To safely migrate your keystores, remove the Web3Signer package (or its volumes) after you make sure you have a backup of your keystores. Then, wait for at least 2 epochs before you upload your keystores to another machine. diff --git a/packages/ui/src/ImportScreen.tsx b/packages/ui/src/ImportScreen.tsx index c35d04fa..caa4bc56 100644 --- a/packages/ui/src/ImportScreen.tsx +++ b/packages/ui/src/ImportScreen.tsx @@ -1,3 +1,4 @@ +import type { JSX } from "react"; import FileDrop from "./components/FileDrop/FileDrop"; import { SecondaryInfoTypography } from "./Styles/Typographies"; import { @@ -6,35 +7,31 @@ import { Card, Switch, TextField, - Select, Typography, FormGroup, FormControlLabel, MenuItem, FormControl, - FormHelperText, - Alert + Alert, + Chip, + IconButton, + Stack } from "@mui/material"; import { Link } from "react-router-dom"; -import { DropEvent } from "react-dropzone"; import { useEffect, useState } from "react"; import BackupIcon from "@mui/icons-material/Backup"; import { ImportStatus, KeystoreInfo, TagSelectOption } from "./types"; import FileCardList from "./components/FileCards/FileCardList"; import ImportDialog from "./components/Dialogs/ImportDialog"; -import { - Tag, - isValidEcdsaPubkey, - BURN_ADDRESS, - isFeeRecipientEditable, - areAllFeeRecipientsEditable, - Network -} from "@stakingbrain/common"; +import { Tag, isValidEcdsaPubkey, BURN_ADDRESS, isFeeRecipientEditable, Network } from "@stakingbrain/common"; import CloseIcon from "@mui/icons-material/Close"; import { rpcClient } from "./socket"; import ArrowCircleLeftOutlinedIcon from "@mui/icons-material/ArrowCircleLeftOutlined"; import { extractPubkey } from "./utils/dataUtils"; -import type { CustomImportRequest, Web3signerPostResponse } from "@stakingbrain/brain"; +import type { Web3signerPostResponse } from "@stakingbrain/brain"; +import { createImportRequest } from "./utils/createImportRequest"; +import { appendImportFiles, isImportFormValid, ImportFormState } from "./utils/importForm"; +import PageLayout, { PageHeader } from "./components/Layout/PageLayout"; export default function ImportScreen({ network, @@ -46,12 +43,21 @@ export default function ImportScreen({ const [keystoresPostResponse, setKeystoresPostResponse] = useState(); const [keystoresPostError, setKeystoresPostError] = useState(); const [openDialog, setOpenDialog] = useState(false); - const [acceptedFiles, setAcceptedFiles] = useState([]); - const [passwords, setPasswords] = useState([]); + const [importForm, setImportForm] = useState({ + acceptedFiles: [], + passwords: [], + tags: [], + feeRecipients: [] + }); + const { acceptedFiles, passwords, tags, feeRecipients } = importForm; + const setAcceptedFiles = (acceptedFiles: KeystoreInfo[]) => + setImportForm((current) => ({ ...current, acceptedFiles })); + const setPasswords = (passwords: string[]) => setImportForm((current) => ({ ...current, passwords })); + const setTags = (tags: ImportFormState["tags"]) => setImportForm((current) => ({ ...current, tags })); + const setFeeRecipients = (feeRecipients: string[]) => setImportForm((current) => ({ ...current, feeRecipients })); + const [fileError, setFileError] = useState(""); const [useSamePassword, setUseSamePassword] = useState(false); - const [tags, setTags] = useState([]); const [useSameTag, setUseSameTag] = useState(false); - const [feeRecipients, setFeeRecipients] = useState([]); const [useSameFeerecipient, setUseSameFeerecipient] = useState(false); const [importStatus, setImportStatus] = useState(ImportStatus.NotImported); const [slashingFile, setSlashingFile] = useState(); @@ -69,46 +75,43 @@ export default function ImportScreen({ switch (switchType) { case "password": setUseSamePassword(!useSamePassword); // Directly toggle the boolean state - setPasswords([]); // Reset passwords to empty + if (!useSamePassword) setPasswords(Array(acceptedFiles.length).fill(passwords[0] ?? "")); break; case "tag": setUseSameTag(!useSameTag); // Directly toggle the boolean state - setTags([]); // Reset tags to empty + if (!useSameTag) { + setTags(Array(acceptedFiles.length).fill(tags[0] ?? "")); + if (tags[0] && !isFeeRecipientEditable(tags[0])) setFeeRecipients(Array(acceptedFiles.length).fill("")); + } break; case "feerecipient": setUseSameFeerecipient(!useSameFeerecipient); // Directly toggle the boolean state - setFeeRecipients([]); // Reset fee recipients to empty + if (!useSameFeerecipient) setFeeRecipients(Array(acceptedFiles.length).fill(feeRecipients[0] ?? "")); break; } }; - // This use effect sets the Lido warning when one of the keystores has the 'lido' tag and MEV-Boost is not set - useEffect(() => { - // Check if any of the selected tags is 'lido' and MEV-Boost is not set - const lidoSelected = tags.includes("lido"); - - setShowMevWarning(lidoSelected && !isMevBoostSet); - }, [tags, isMevBoostSet]); - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const keystoreFilesCallback = async (files: File[], event: DropEvent) => { + const keystoreFilesCallback = async (files: File[]) => { const keystoresToAdd: KeystoreInfo[] = []; - const passwordsToAdd: string[] = []; + const rejected: string[] = []; for (const file of files) { - const pubkey = await extractPubkey(file); - if (pubkey) { - if (acceptedFiles.some((e) => e.pubkey === pubkey) === false) { - keystoresToAdd.push({ file: file, pubkey: pubkey }); - passwordsToAdd.push(""); - } + try { + keystoresToAdd.push({ file, pubkey: await extractPubkey(file) }); + } catch { + rejected.push(file.name); } } - setAcceptedFiles([...acceptedFiles].concat(keystoresToAdd)); - setPasswords([...passwords].concat(passwordsToAdd)); + setFileError(rejected.length ? `Could not read valid validator keystores from: ${rejected.join(", ")}` : ""); + setImportForm((current) => + appendImportFiles(current, keystoresToAdd, { + password: useSamePassword, + tag: useSameTag, + feeRecipient: useSameFeerecipient + }) + ); }; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const slashingFilesCallback = (files: File[], event: DropEvent) => { + const slashingFilesCallback = (files: File[]) => { setSlashingFile(files[0]); }; @@ -125,29 +128,10 @@ export default function ImportScreen({ // This useEffect updates the "submit keystores" button state based on various conditions, // such as if any password is empty, if any tag is empty, if any fee recipient is invalid, etc. useEffect(() => { - const isAnyPasswordEmpty = () => { - return passwords.some((password) => password === "") || passwords.length === 0; - }; - - const hasInvalidTagOrFeeRecipient = () => { - if (tags.length !== acceptedFiles.length) return true; - return tags.some((tag, index) => { - if (tag.length === 0) return true; - if (isFeeRecipientEditable(tag)) { - return !isValidEcdsaPubkey(feeRecipients[index]); - } - return false; - }); - }; - - const hasInvalidKeystoreData = () => { - return isAnyPasswordEmpty() || hasInvalidTagOrFeeRecipient(); - }; - const disable = acceptedFiles.length === 0 || (!slashingFile && slashingProtectionIncluded) || - hasInvalidKeystoreData() || + !isImportFormValid(importForm) || showMevWarning; setIsButtonDisabled(disable); // Set the state based on the conditions @@ -156,20 +140,17 @@ export default function ImportScreen({ async function importKeystores() { try { setImportStatus(ImportStatus.Importing); + setKeystoresPostError(undefined); + setKeystoresPostResponse(undefined); handleClickOpenDialog(); - const importRequest: CustomImportRequest = { - importFrom: "ui", - slashing_protection: slashingFile, - validatorsImportRequest: acceptedFiles.map((f, i) => { - return { - keystore: f.file, - password: passwords[i], - tag: tags[i], - feeRecipient: feeRecipients[i] - }; - }) - }; + const importRequest = await createImportRequest({ + acceptedFiles, + passwords, + tags, + feeRecipients, + slashingFile: slashingProtectionIncluded ? slashingFile : undefined + }); const response = await rpcClient.call("importValidators", importRequest); @@ -213,7 +194,7 @@ export default function ImportScreen({ const tagSelectOptions: TagSelectOption[] = ["gnosis", "lukso"].includes(network) ? [{ value: "solo", label: "Solo" }] - : ["holesky", "hoodi"].includes(network) + : network === "hoodi" ? [ { value: "solo", label: "Solo" }, { value: "rocketpool", label: "Rocketpool" }, @@ -230,54 +211,52 @@ export default function ImportScreen({ ]; return ( -
- + } + /> + - - Import Validator Keystore(s) + + Keystore files - - Upload any keystore JSON file(s). - - + + + {fileError && ( + + {fileError} + + )} - - + + - {acceptedFiles.length > 1 && ( - <> - + {acceptedFiles.length > 0 && + (acceptedFiles.length > 1 || useSameTag || useSamePassword || useSameFeerecipient) && ( + + + Shared settings + + handleSwitchToggle("password")} />} + control={ handleSwitchToggle("password")} />} label="Use same password for every file" /> handleSwitchToggle("feerecipient")} />} + control={ handleSwitchToggle("feerecipient")} />} label="Use same fee recipient for every file" /> handleSwitchToggle("tag")} />} + control={ handleSwitchToggle("tag")} />} label="Use same tag for every file" /> @@ -287,8 +266,9 @@ export default function ImportScreen({ <> setPasswords(Array(acceptedFiles.length).fill(e.target.value))} helperText={"Password to decrypt the keystore(s)"} @@ -297,24 +277,26 @@ export default function ImportScreen({ )} {useSameTag && ( <> - - Staking protocol + )} {useSameFeerecipient && ( @@ -322,9 +304,9 @@ export default function ImportScreen({ !tag || isFeeRecipientEditable(tag)) + ? "Fee recipient" + : "Fee recipient set by protocol" } type="text" sx={{ marginTop: 2 }} @@ -333,149 +315,135 @@ export default function ImportScreen({ }} error={isFeeRecipientFieldWrong(0)} helperText={getFeeRecipientFieldHelperText(0)} - value={feeRecipients[0]} - disabled={!isFeeRecipientEditable(tags[0])} + value={feeRecipients[0] ?? ""} + disabled={tags.every((tag) => tag && !isFeeRecipientEditable(tag))} /> - {!areAllFeeRecipientsEditable(tags) && !useSameTag && ( + {tags.some((tag) => tag && !isFeeRecipientEditable(tag)) && !useSameTag && ( This field will only apply to the editable fee recipients )} )} )} - - )} - - {FileCardList( - acceptedFiles, - setAcceptedFiles, - passwords, - setPasswords, - useSamePassword, - tags, - setTags, - useSameTag, - feeRecipients, - setFeeRecipients, - useSameFeerecipient, - getFeeRecipientFieldHelperText, - isFeeRecipientFieldWrong, - tagSelectOptions + )} - - - Import slashing protection data? (recommended) - - - + {FileCardList( + acceptedFiles, + setAcceptedFiles, + passwords, + setPasswords, + useSamePassword, + tags, + setTags, + useSameTag, + feeRecipients, + setFeeRecipients, + useSameFeerecipient, + getFeeRecipientFieldHelperText, + isFeeRecipientFieldWrong, + tagSelectOptions + )} + + + + + Slashing protection + + Recommended for previously used keystores. + + + + {slashingProtectionIncluded ? ( - - Upload your slashing protection file to protect your keystore(s). - - + {slashingFile ? ( - - ✅ {slashingFile.name} -
+ + {slashingFile.name} + - +
) : ( )} -
+
) : null} - {showMevWarning && ( - - You are importing one or more "Lido" validators, but don't have the MEV Boost package up & running. As a - Lido Node Operator, it is your responsibility to ensure that your validators use MEV boost.
Please - install the MEV Boost package from{" "} - - your stakers tab - {" "} - before importing your Lido validator. Visit{" "} - our docs for more - details. -
- )} - + {showMevWarning && ( + - - - - - - + You are importing one or more "Lido" validators, but don't have the MEV Boost package up & running. As a Lido + Node Operator, it is your responsibility to ensure that your validators use MEV boost.
Please install + the MEV Boost package from{" "} + + your stakers tab + {" "} + before importing your Lido validator. Visit{" "} + our docs for more + details. + + )} + + + + -
+ ); } diff --git a/packages/ui/src/Styles/Typographies.tsx b/packages/ui/src/Styles/Typographies.tsx index 12773462..5d8dbc98 100644 --- a/packages/ui/src/Styles/Typographies.tsx +++ b/packages/ui/src/Styles/Typographies.tsx @@ -1,3 +1,4 @@ +import type { JSX } from "react"; import { SxProps } from "@mui/material"; import Typography from "@mui/material/Typography"; @@ -11,8 +12,8 @@ export function HeaderTypography({ text, sx }: { text: string; sx?: SxProps }): export function SecondaryInfoTypography({ text, sx }: { text: string; sx?: SxProps }): JSX.Element { return ( - - {text} + + {text} ); } diff --git a/packages/ui/src/Styles/dialogStyles.ts b/packages/ui/src/Styles/dialogStyles.ts index 9d8639b1..4b75fd07 100644 --- a/packages/ui/src/Styles/dialogStyles.ts +++ b/packages/ui/src/Styles/dialogStyles.ts @@ -1,6 +1,6 @@ export const importDialogBoxStyle = { - marginTop: 2, display: "flex", flexDirection: "column", - alignItems: "left" + gap: 2, + minWidth: 0 }; diff --git a/packages/ui/src/Styles/fileDropStyles.ts b/packages/ui/src/Styles/fileDropStyles.ts deleted file mode 100644 index 66a54f27..00000000 --- a/packages/ui/src/Styles/fileDropStyles.ts +++ /dev/null @@ -1,27 +0,0 @@ -export const baseStyle = { - flex: 1, - display: "flex", - flexDirection: "column" as const, - alignItems: "center", - padding: "20px", - borderWidth: 2, - borderRadius: 2, - borderColor: "#424242", - borderStyle: "dashed", - backgroundColor: "rgba(0,0,0,0.3)", - color: "#f0f0f0", - outline: "none", - transition: "border .24s ease-in-out" -}; - -export const activeStyle = { - borderColor: "#2196f3" -}; - -export const acceptStyle = { - borderColor: "#00e676" -}; - -export const rejectStyle = { - borderColor: "#ff1744" -}; diff --git a/packages/ui/src/Styles/theme.ts b/packages/ui/src/Styles/theme.ts new file mode 100644 index 00000000..ae92c880 --- /dev/null +++ b/packages/ui/src/Styles/theme.ts @@ -0,0 +1,45 @@ +import { createTheme } from "@mui/material/styles"; +import type { PaletteMode } from "@mui/material"; + +export function createAppTheme(mode: PaletteMode) { + return createTheme({ + palette: { + mode, + background: { + default: mode === "light" ? "#f7f9fc" : "#11151c", + paper: mode === "light" ? "#ffffff" : "#1a2029" + } + }, + shape: { borderRadius: 8 }, + typography: { + h5: { fontWeight: 700 }, + h6: { fontWeight: 700 }, + subtitle1: { fontWeight: 600 }, + button: { textTransform: "none", fontWeight: 600 } + }, + components: { + MuiButton: { + defaultProps: { disableElevation: true }, + styleOverrides: { root: { borderRadius: 16, paddingInline: 16 } } + }, + MuiIconButton: { styleOverrides: { root: { borderRadius: 12 } } }, + MuiPaper: { styleOverrides: { root: { backgroundImage: "none" } } }, + MuiCard: { + defaultProps: { variant: "outlined" }, + styleOverrides: { root: { borderRadius: 20, boxShadow: "none" } } + }, + MuiDialog: { styleOverrides: { paper: { borderRadius: 24 }, paperFullScreen: { borderRadius: 0 } } }, + MuiDialogTitle: { styleOverrides: { root: { padding: "20px 24px" } } }, + MuiDialogContent: { styleOverrides: { root: { padding: 24 } } }, + MuiDialogActions: { styleOverrides: { root: { padding: "16px 24px", gap: 8, flexWrap: "wrap" } } }, + MuiTextField: { defaultProps: { size: "small", fullWidth: true } }, + MuiFormControl: { defaultProps: { size: "small" } }, + MuiFormControlLabel: { styleOverrides: { label: { fontSize: 14 } } }, + MuiDialogContentText: { styleOverrides: { root: { fontSize: 14, lineHeight: 1.6 } } }, + MuiSelect: { defaultProps: { size: "small" } }, + MuiAlert: { styleOverrides: { root: { borderRadius: 16 }, message: { overflowWrap: "anywhere" } } }, + MuiChip: { styleOverrides: { root: { fontWeight: 500 } } }, + MuiLink: { defaultProps: { underline: "hover" } } + } + }); +} diff --git a/packages/ui/src/components/BuilderConfig/BuilderConfigFields.tsx b/packages/ui/src/components/BuilderConfig/BuilderConfigFields.tsx new file mode 100644 index 00000000..f2d2248b --- /dev/null +++ b/packages/ui/src/components/BuilderConfig/BuilderConfigFields.tsx @@ -0,0 +1,389 @@ +import type { JSX } from "react"; +import { useEffect, useState } from "react"; +import { + Box, + Button, + Chip, + Collapse, + FormControl, + FormControlLabel, + FormLabel, + IconButton, + InputAdornment, + MenuItem, + Paper, + Select, + Stack, + Switch, + TextField, + Tooltip, + Typography +} from "@mui/material"; +import AddIcon from "@mui/icons-material/Add"; +import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline"; +import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; +import { BuilderFormErrors, BuilderInput, OverrideInput, splitBuilderPublicKeys } from "../../utils/builderConfigForm"; +import { formatBidAmount, formatBidBoost, MAX_BUILDER_BOOST } from "../../utils/builderConfigDisplay"; + +export function NumberOverrideField({ + label, + input, + onChange, + inherited, + description, + unit, + fallback = "0", + error, + presets = false +}: { + label: string; + input: OverrideInput; + onChange: (input: OverrideInput) => void; + inherited: string; + description: string; + unit?: string; + fallback?: string; + error?: string; + presets?: boolean; +}): JSX.Element { + const maximumBoost = presets && input.value === MAX_BUILDER_BOOST; + return ( + + + + {label} + + {inherited}} + control={ + onChange({ enabled: !checked, value: input.value || fallback })} + slotProps={{ input: { "aria-label": `${label}: ${inherited}` } }} + /> + } + /> + + {input.enabled ? ( + onChange({ enabled: true, value: event.target.value })} + error={!!error} + helperText={error || description} + slotProps={{ + htmlInput: { inputMode: maximumBoost ? undefined : "numeric" }, + input: { + readOnly: maximumBoost, + endAdornment: unit && !maximumBoost ? {unit} : undefined + } + }} + /> + ) : ( + + {description} + + )} + {input.enabled && maximumBoost && ( + + )} + {presets && ( + + onChange({ enabled: true, value: "0" })} + /> + onChange({ enabled: true, value: "100" })} + /> + + onChange({ enabled: true, value: MAX_BUILDER_BOOST })} + /> + + + )} + + ); +} + +export function BuilderCard({ + entry, + index, + onChange, + onRemove, + errors, + globalMinBid, + globalBoost +}: { + entry: BuilderInput; + index: number; + onChange: (entry: BuilderInput) => void; + onRemove: () => void; + errors: BuilderFormErrors; + globalMinBid: OverrideInput; + globalBoost: OverrideInput; +}): JSX.Element { + const prefix = `builders.${entry.id}`; + const hasErrors = Object.keys(errors).some((key) => key.startsWith(prefix + ".")); + const [expanded, setExpanded] = useState(() => !entry.url.trim()); + useEffect(() => { + if (hasErrors) setExpanded(true); + }, [hasErrors]); + let title: string; + try { + title = new URL(entry.url).hostname; + } catch { + title = entry.url.trim() || "New builder"; + } + const customCount = [ + entry.authData.enabled, + entry.maxPayment.enabled, + entry.minBid.enabled, + entry.boost.enabled, + (entry.publicKeys?.length ?? 0) > 0 + ].filter(Boolean).length; + return ( + + + + + + + + + + + + onChange({ ...entry, url: event.target.value })} + error={!!errors[`${prefix}.url`]} + helperText={errors[`${prefix}.url`]} + slotProps={{ htmlInput: { inputMode: "url", autoCapitalize: "none", spellCheck: false } }} + /> + + + + onChange({ ...entry, maxPayment })} + inherited="Use client default" + unit="Gwei" + error={errors[`${prefix}.maxPayment`]} + description="Cap counted toward bid value. Zero counts none." + /> + onChange({ ...entry, minBid })} + inherited="Use global" + unit="Gwei" + error={errors[`${prefix}.minBid`]} + description={`Global: ${globalMinBid.enabled ? formatBidAmount(globalMinBid.value) : "client default"}.`} + /> + onChange({ ...entry, boost })} + inherited="Use global" + unit="%" + fallback="100" + error={errors[`${prefix}.boost`]} + presets + description={`Global: ${globalBoost.enabled ? formatBidBoost(globalBoost.value) : "client default"}.`} + /> + + + + Request authorization + + + + {entry.authData.enabled && ( + onChange({ ...entry, authData: { enabled: true, value: event.target.value } })} + placeholder="0x…" + error={!!errors[`${prefix}.authData`]} + helperText={errors[`${prefix}.authData`] || "Hex data from your builder (1–4096 bytes)."} + slotProps={{ htmlInput: { autoCapitalize: "none", spellCheck: false, autoComplete: "off" } }} + /> + )} + + + + + Accepted builder public keys + + + + {(entry.publicKeys?.length ?? 0) > 0 && ( + + {entry.publicKeys?.map((key, keyIndex) => ( + + { + const pasted = splitBuilderPublicKeys(event.clipboardData.getData("text")); + if (pasted.length > 1) { + event.preventDefault(); + onChange({ + ...entry, + publicKeys: [ + ...(entry.publicKeys ?? []).slice(0, keyIndex), + ...pasted, + ...(entry.publicKeys ?? []).slice(keyIndex + 1) + ] + }); + } + }} + slotProps={{ htmlInput: { autoCapitalize: "none", spellCheck: false } }} + onChange={(event) => + onChange({ + ...entry, + publicKeys: entry.publicKeys?.map((value, current) => + current === keyIndex ? event.target.value : value + ) + }) + } + /> + + onChange({ + ...entry, + publicKeys: entry.publicKeys?.filter((_, current) => current !== keyIndex) + }) + } + > + + + + ))} + + {errors[`${prefix}.publicKeys`] && ( + + {errors[`${prefix}.publicKeys`]} + + )} + + )} + + + + + + ); +} diff --git a/packages/ui/src/components/BuilderConfig/BuilderConfigSummary.tsx b/packages/ui/src/components/BuilderConfig/BuilderConfigSummary.tsx new file mode 100644 index 00000000..4aa7970e --- /dev/null +++ b/packages/ui/src/components/BuilderConfig/BuilderConfigSummary.tsx @@ -0,0 +1,92 @@ +import type { JSX } from "react"; +import { + Accordion, + AccordionDetails, + AccordionSummary, + Chip, + Stack, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Tooltip, + Typography +} from "@mui/material"; +import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; +import { BuilderConfig } from "@stakingbrain/common"; +import { builderConfigComparison, BuilderComparisonRow } from "../../utils/builderConfigDisplay"; + +function ComparisonTable({ rows }: { rows: BuilderComparisonRow[] }): JSX.Element { + return ( + + + + + Setting + Saved in Brain + Validator is using + + + + {rows.map((row) => ( + + + {row.label} + + + + {row.saved} + + + + + {row.effective} + + + + ))} + +
+
+ ); +} + +export default function BuilderConfigSummary({ + saved, + resolved +}: { + saved?: BuilderConfig | null; + resolved?: BuilderConfig; +}): JSX.Element { + const comparison = builderConfigComparison(saved, resolved); + return ( + + + {comparison.builders.map((builder, index) => ( + + } sx={{ px: 1, minWidth: 0 }}> + + + {builder.url} + + {builder.source && } + + + + + + + ))} + + ); +} diff --git a/packages/ui/src/components/BuilderConfig/RelayPresetSelector.tsx b/packages/ui/src/components/BuilderConfig/RelayPresetSelector.tsx new file mode 100644 index 00000000..9ac08402 --- /dev/null +++ b/packages/ui/src/components/BuilderConfig/RelayPresetSelector.tsx @@ -0,0 +1,184 @@ +import type { JSX } from "react"; +import { useEffect, useState } from "react"; +import { Alert, Box, Button, ButtonBase, Chip, CircularProgress, Link, Stack, Typography } from "@mui/material"; +import OpenInNewIcon from "@mui/icons-material/OpenInNew"; +import CheckCircleRoundedIcon from "@mui/icons-material/CheckCircleRounded"; +import AddRoundedIcon from "@mui/icons-material/AddRounded"; +import { alpha } from "@mui/material/styles"; +import { BuilderRelayPresets, Network } from "@stakingbrain/common"; +import { rpcClient } from "../../socket"; +import { BuilderConfigForm, matchesRelayPreset, toggleRelayPreset } from "../../utils/builderConfigForm"; + +export default function RelayPresetSelector({ + network, + form, + onChange, + onAdvancedSettings +}: { + network: Network; + form: BuilderConfigForm; + onChange: (form: BuilderConfigForm) => void; + onAdvancedSettings: () => void; +}): JSX.Element { + const [catalog, setCatalog] = useState(); + const [error, setError] = useState(""); + const [attempt, setAttempt] = useState(0); + useEffect(() => { + let active = true; + setCatalog(undefined); + setError(""); + void rpcClient.call("getBuilderRelayPresets", undefined).then( + (result) => { + if (!active) return; + if (result.network !== network) { + setError("The relay network does not match this page. Reload the page to try again."); + return; + } + setCatalog(result); + }, + (error) => { + if (active) setError(error.message); + } + ); + return () => { + active = false; + }; + }, [network, attempt]); + + const builders = form.buildersMode === "custom" ? form.builders : []; + const customCount = catalog + ? builders.filter((entry) => !catalog.relays.some((preset) => matchesRelayPreset(entry, preset))).length + : 0; + return ( + + + Relays + + + Select the {network === Network.Hoodi ? "Hoodi" : network} ePBS relays you want to use. + + {error ? ( + setAttempt((current) => current + 1)}>Retry}> + Relay presets could not be loaded. {error} You can still edit endpoints in Advanced settings. + + ) : !catalog ? ( + + + Loading relay presets… + + ) : catalog.relays.length === 0 ? ( + + No ePBS relay presets are available for this network yet. Add custom endpoints in Advanced settings. + + ) : ( + + {catalog.relays.map((preset) => { + const checked = builders.some((entry) => matchesRelayPreset(entry, preset)); + const disabled = !checked && builders.length >= 64; + return ( + ({ + position: "relative", + m: 0, + px: 2, + py: 1.5, + border: 1, + borderColor: checked ? "primary.main" : "divider", + bgcolor: checked ? alpha(theme.palette.primary.main, 0.07) : "background.paper", + borderRadius: 2, + transition: "background-color 150ms, border-color 150ms" + })} + > + onChange(toggleRelayPreset(form, preset, !checked))} + sx={{ + position: "absolute", + inset: 0, + borderRadius: "inherit", + "&:hover": { bgcolor: "action.hover" }, + "&.Mui-focusVisible": { outline: "2px solid", outlineColor: "primary.main", outlineOffset: 2 } + }} + /> + + + + {preset.name} + + + + + {new URL(preset.url).hostname} + + + + + + ); + })} + + )} + {customCount > 0 && ( + + )} + + {builders.length === 0 + ? "No direct relays selected. Public P2P bids and local building remain available." + : `${builders.length} direct ${builders.length === 1 ? "endpoint" : "endpoints"} selected. Public P2P bids and local building remain available.`} + + {catalog && catalog.relays.length > 0 && ( + + OFAC filtering excludes blocks involving sanctioned addresses. + + )} + + ); +} diff --git a/packages/ui/src/components/Dialogs/BuilderConfigDialog.tsx b/packages/ui/src/components/Dialogs/BuilderConfigDialog.tsx new file mode 100644 index 00000000..02195615 --- /dev/null +++ b/packages/ui/src/components/Dialogs/BuilderConfigDialog.tsx @@ -0,0 +1,495 @@ +import type { JSX } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; +import { + Accordion, + AccordionDetails, + AccordionSummary, + Alert, + Box, + Button, + Chip, + CircularProgress, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + Divider, + IconButton, + Paper, + Stack, + Tooltip, + Typography, + useMediaQuery, + useTheme +} from "@mui/material"; +import AddIcon from "@mui/icons-material/Add"; +import CloseIcon from "@mui/icons-material/Close"; +import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; +import RefreshIcon from "@mui/icons-material/Refresh"; +import RestartAltIcon from "@mui/icons-material/RestartAlt"; +import TuneIcon from "@mui/icons-material/Tune"; +import { Network, shortenPubkey } from "@stakingbrain/common"; +import type { CustomValidatorGetResponse, ValidatorBuilderConfigResponse } from "@stakingbrain/brain"; +import { rpcClient } from "../../socket"; +import { + builderConfigToForm, + builderFormErrors, + builderFormToConfig, + createBuilderInput, + BuilderConfigForm +} from "../../utils/builderConfigForm"; +import { BuilderCard, NumberOverrideField } from "../BuilderConfig/BuilderConfigFields"; +import BuilderConfigSummary from "../BuilderConfig/BuilderConfigSummary"; +import RelayPresetSelector from "../BuilderConfig/RelayPresetSelector"; + +export default function BuilderConfigDialog({ + validators, + network, + setOpen +}: { + validators: CustomValidatorGetResponse[]; + network: Network; + setOpen: (open: boolean) => void; +}): JSX.Element { + const [form, setForm] = useState(() => + builderConfigToForm(validators.length === 1 ? validators[0].builderConfig : undefined) + ); + const [details, setDetails] = useState([]); + const [busy, setBusy] = useState(false); + const [reading, setReading] = useState(true); + const [submitted, setSubmitted] = useState(false); + const [error, setError] = useState(""); + const [success, setSuccess] = useState(""); + const [advancedExpanded, setAdvancedExpanded] = useState(false); + const contentRef = useRef(null); + const refreshId = useRef(0); + const errors = useMemo(() => (submitted ? builderFormErrors(form) : {}), [form, submitted]); + const fullScreen = useMediaQuery(useTheme().breakpoints.down("sm")); + const bulk = validators.length > 1; + const inheritsBuilderList = (details.length ? details : validators).some( + ({ builderConfig }) => builderConfig?.builders === undefined + ); + const problems = details.filter( + (detail) => + detail.status === "error" || + detail.application?.status === "error" || + detail.application?.status === "unsupported" + ); + + function update(next: BuilderConfigForm): void { + setForm(next); + setError(""); + setSuccess(""); + } + + async function refresh(): Promise { + const id = ++refreshId.current; + setReading(true); + try { + const result = await rpcClient.call( + "getBuilderConfigs", + validators.map(({ pubkey }) => pubkey) + ); + if (id === refreshId.current) setDetails(result); + } catch (error) { + if (id === refreshId.current) setError(error.message); + } finally { + if (id === refreshId.current) setReading(false); + } + } + + useEffect(() => { + void refresh(); + return () => { + refreshId.current++; + }; + }, []); + + async function save(reset: boolean): Promise { + setError(""); + setSuccess(""); + if (!reset) { + setSubmitted(true); + if (Object.keys(builderFormErrors(form)).length > 0) { + setAdvancedExpanded(true); + requestAnimationFrame(() => + contentRef.current?.querySelector("[aria-invalid='true']")?.focus() + ); + return; + } + } + setBusy(true); + // An earlier read must not replace the status of this save when it finishes. + refreshId.current++; + setReading(false); + try { + const config = reset ? null : builderFormToConfig(form); + await rpcClient.call( + "updateValidators", + validators.map(({ pubkey }) => ({ pubkey, builderConfig: config })) + ); + if (reset) { + setForm(builderConfigToForm(null)); + setSubmitted(false); + } + setSuccess( + reset + ? "All overrides reset to client defaults, including the direct-builder list. Saving here again will replace that list." + : `Builder settings saved for ${validators.length === 1 ? "this validator" : `all ${validators.length} validators`}.` + ); + } catch (error) { + setError(error.message); + } finally { + setBusy(false); + void refresh(); + } + } + + return ( + !busy && setOpen(false)} + disableEscapeKeyDown={busy} + fullWidth + maxWidth="md" + fullScreen={fullScreen} + aria-labelledby="builder-settings-title" + slotProps={{ paper: { sx: { borderRadius: { sm: 3 }, maxHeight: { sm: "94dvh" } } } }} + > + + + + + + + + + Builder settings + + + Choose how your validators source and compare bids. + + + + setOpen(false)} size="small"> + + + + + + + + {reading && ( + } label="Checking client…" /> + )} + + + + + + {bulk && ( + + You’re editing {validators.length} validators together. Saving replaces their builder settings with the + choices below. Bid preferences start at client defaults, with no direct builders selected. + + )} + + + setAdvancedExpanded(true)} + /> + {inheritsBuilderList && ( + + Saving replaces the client’s direct-builder list with your selection. + + )} + setAdvancedExpanded(expanded)} + disableGutters + elevation={0} + sx={{ + border: 1, + borderColor: "divider", + borderRadius: "12px !important", + "&::before": { display: "none" } + }} + > + } aria-controls="advanced-builder-settings"> + + + Advanced settings + + {(form.minBid.enabled || + form.boost.enabled || + form.builders.some( + (entry) => + entry.authData.enabled || + entry.minBid.enabled || + entry.boost.enabled || + entry.maxPayment.enabled || + entry.publicKeys !== undefined + )) && } + + + + + + + Global bid preferences + + + These values apply to public P2P bids and every builder below. Per-builder settings can override + them for that builder only. + + + update({ ...form, minBid })} + inherited="Use client default" + unit="Gwei" + error={errors.minBid} + description="The minimum payment to accept. Switch off the default to set your own amount in whole Gwei." + /> + + update({ ...form, boost })} + inherited="Use client default" + unit="%" + fallback="100" + error={errors.boost} + presets + description="100% gives bids equal weight against local building. 50% counts half a bid’s payment; 150% counts 1.5 times. This changes selection weight, not the payment you receive. Zero prefers local building; MAX prefers builder bids, subject to upstream fallback and health checks." + /> + + + + + Custom endpoints and relay overrides + + + Edit selected relays or add any HTTP(S) builder endpoint. Changes here update the relay + selection above. + + {errors.builders && ( + + {errors.builders} + + )} + + {(form.buildersMode === "custom" ? form.builders : []).map((entry, index) => ( + + update({ + ...form, + builders: form.builders.map((current) => (current.id === entry.id ? changed : current)) + }) + } + onRemove={() => { + const remaining = form.builders.filter((current) => current.id !== entry.id); + update({ + ...form, + builders: remaining, + buildersMode: remaining.length ? "custom" : "none" + }); + }} + /> + ))} + + + + {form.builders.length} of 64 builders + + + + + + + + + + {Object.keys(errors).length > 0 && ( + + Check the highlighted fields before saving. + + )} + {error && ( + + {error} + + )} + {success && ( + + {success} + + )} + {problems.length > 0 && ( + + {problems.length === 1 + ? (problems[0].application?.message ?? problems[0].message) + : `${problems.length} validators need attention. Review validator status below for details.`}{" "} + Key and fee-recipient management remain available. + + )} + + }> + + Saved settings and validator status + + + + + + + Brain saves your choices and sends them to your validator software. Compare those choices with the + settings the software reports using. “Client default” lets it choose; “Use global” follows your + saved bid preferences. Unsaved edits aren’t shown. + + + + void refresh()} + > + {reading ? : } + + + + + {details.map((detail) => ( + + + + {shortenPubkey(detail.pubkey)} + + + + {detail.application?.message && ( + + {detail.application.message} + + )} + {detail.status === "error" && ( + + {detail.message} Key and fee-recipient management remain available. + + )} + + + ))} + + + + + + + + + + + + + + + + + ); +} diff --git a/packages/ui/src/components/Dialogs/DeletionWarning.tsx b/packages/ui/src/components/Dialogs/DeletionWarning.tsx index 1fa25039..d01cf23a 100644 --- a/packages/ui/src/components/Dialogs/DeletionWarning.tsx +++ b/packages/ui/src/components/Dialogs/DeletionWarning.tsx @@ -1,4 +1,5 @@ -import { GridSelectionModel } from "@mui/x-data-grid"; +import type { JSX } from "react"; +import type { GridRowId } from "@mui/x-data-grid"; import { shortenPubkey } from "@stakingbrain/common"; import type { CustomValidatorGetResponse } from "@stakingbrain/brain"; @@ -6,7 +7,7 @@ export default function DeletionWarning({ selectedRows, rows }: { - selectedRows: GridSelectionModel; + selectedRows: GridRowId[]; rows: CustomValidatorGetResponse[]; }): JSX.Element { return ( diff --git a/packages/ui/src/components/Dialogs/DialogHeader.tsx b/packages/ui/src/components/Dialogs/DialogHeader.tsx new file mode 100644 index 00000000..711cee24 --- /dev/null +++ b/packages/ui/src/components/Dialogs/DialogHeader.tsx @@ -0,0 +1,57 @@ +import type { ReactNode } from "react"; +import { Box, Chip, DialogTitle, Divider, IconButton, Stack, Typography } from "@mui/material"; +import CloseIcon from "@mui/icons-material/Close"; + +export default function DialogHeader({ + title, + subtitle, + icon, + count, + onClose, + busy = false +}: { + title: string; + subtitle: string; + icon: ReactNode; + count?: number; + onClose: () => void; + busy?: boolean; +}) { + return ( + <> + + + + + {icon} + + + + {title} + + + {subtitle} + + + + + + + + {count !== undefined && ( + + )} + + + + ); +} diff --git a/packages/ui/src/components/Dialogs/EditFeesDialog.tsx b/packages/ui/src/components/Dialogs/EditFeesDialog.tsx index 89a0185d..84adbaba 100644 --- a/packages/ui/src/components/Dialogs/EditFeesDialog.tsx +++ b/packages/ui/src/components/Dialogs/EditFeesDialog.tsx @@ -1,3 +1,4 @@ +import type { JSX } from "react"; //External components import { Alert, @@ -6,7 +7,6 @@ import { Dialog, DialogActions, DialogContent, - DialogTitle, FormControlLabel, FormGroup, Switch, @@ -16,10 +16,13 @@ import { Step, StepLabel, Tooltip, - Checkbox + Checkbox, + Divider } from "@mui/material"; import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; -import { GridSelectionModel } from "@mui/x-data-grid"; +import EditOutlinedIcon from "@mui/icons-material/EditOutlined"; +import DialogHeader from "./DialogHeader"; +import type { GridRowId } from "@mui/x-data-grid"; import { BURN_ADDRESS, isValidEcdsaPubkey, areAllFeeRecipientsEditable, Network } from "@stakingbrain/common"; import React from "react"; import type { @@ -52,7 +55,7 @@ export default function FeeRecipientDialog({ open: boolean; setOpen: (open: boolean) => void; rows: CustomValidatorGetResponse[]; - selectedRows: GridSelectionModel; + selectedRows: GridRowId[]; network: Network; isMevBoostSet: boolean; }): JSX.Element { @@ -153,18 +156,16 @@ export default function FeeRecipientDialog({ const handleApplyChanges = async () => { setLoading(true); - + setErrorMessage(""); + setSuccessMessage(""); try { await updateValidators(); - } catch (err) { - setErrorMessage("There was an error updating some fee recipients: " + err); - } - - setLoading(false); - - if (!errorMessage) { setSuccessMessage("Fee recipients updated successfully"); isMevSpAddressSelected && setActiveStep(1); + } catch (err) { + setErrorMessage("There was an error updating some fee recipients: " + err); + } finally { + setLoading(false); } }; @@ -263,7 +264,7 @@ export default function FeeRecipientDialog({ }} > @@ -282,7 +283,7 @@ export default function FeeRecipientDialog({ }} >
+ {successMessage} ); case "errorAlert": return ( - + {errorMessage} ); @@ -549,7 +550,7 @@ export default function FeeRecipientDialog({ switchSetMevSpAddress()} />} label={ - - Set Smooth Fee Recipient + + Use Smooth fee recipient } checked={isMevSpAddressSelected} @@ -615,12 +616,19 @@ export default function FeeRecipientDialog({ aria-describedby="alert-dialog-description" TransitionComponent={SlideTransition} > - - Edit Fee Recipient (for selected validators) - + } + count={selectedRows.length} + onClose={handleClose} + busy={loading} + /> {modalContent()} + {!loading ? ( + {!errorMessage && activeStep === 0 && ( )} - ) : ( diff --git a/packages/ui/src/components/Dialogs/ExitWarning.tsx b/packages/ui/src/components/Dialogs/ExitWarning.tsx index 630c9280..8e1e9abc 100644 --- a/packages/ui/src/components/Dialogs/ExitWarning.tsx +++ b/packages/ui/src/components/Dialogs/ExitWarning.tsx @@ -1,5 +1,6 @@ +import type { JSX } from "react"; import { Alert } from "@mui/material"; -import { GridSelectionModel } from "@mui/x-data-grid"; +import type { GridRowId } from "@mui/x-data-grid"; import { shortenPubkey } from "@stakingbrain/common"; import type { CustomValidatorGetResponse } from "@stakingbrain/brain"; @@ -7,7 +8,7 @@ export default function ExitWarning({ selectedRows, rows }: { - selectedRows: GridSelectionModel; + selectedRows: GridRowId[]; rows: CustomValidatorGetResponse[]; }): JSX.Element { return ( diff --git a/packages/ui/src/components/Dialogs/ImportDialog.tsx b/packages/ui/src/components/Dialogs/ImportDialog.tsx index 2cafd581..dcc29bcb 100644 --- a/packages/ui/src/components/Dialogs/ImportDialog.tsx +++ b/packages/ui/src/components/Dialogs/ImportDialog.tsx @@ -1,11 +1,23 @@ -import { Dialog, DialogTitle, DialogContent, Box, Typography, Button, DialogActions } from "@mui/material"; -import { Link } from "react-router-dom"; +import type { JSX } from "react"; +import { + Alert, + Dialog, + DialogContent, + Box, + Button, + DialogActions, + Divider, + useMediaQuery, + useTheme +} from "@mui/material"; +import { useNavigate } from "react-router-dom"; import { importDialogBoxStyle } from "../../Styles/dialogStyles"; -import { KeystoreInfo } from "../../types"; +import { ImportStatus, KeystoreInfo } from "../../types"; import WaitBox from "../WaitBox/WaitBox"; -import { shortenPubkey } from "@stakingbrain/common"; import { SlideTransition } from "./Transitions"; -import { getEmoji } from "../../utils/dataUtils"; +import UploadFileIcon from "@mui/icons-material/UploadFile"; +import DialogHeader from "./DialogHeader"; +import ValidatorResult from "./ValidatorResult"; import type { Web3signerPostResponse } from "@stakingbrain/brain"; export default function ImportDialog({ @@ -23,8 +35,11 @@ export default function ImportDialog({ importStatus: string; acceptedFiles: KeystoreInfo[]; }): JSX.Element { + const navigate = useNavigate(); + const isMobile = useMediaQuery(useTheme().breakpoints.down("sm")); const handleClose = () => { setOpen(false); + navigate({ pathname: "/", search: window.location.search }); }; return ( @@ -32,6 +47,7 @@ export default function ImportDialog({ disableEscapeKeyDown={true} open={open} fullWidth={true} + fullScreen={isMobile} onClose={(event, reason) => { if (!reason) { handleClose(); @@ -41,29 +57,33 @@ export default function ImportDialog({ aria-describedby="alert-dialog-description" TransitionComponent={SlideTransition} > - - {importStatus} - + } + count={acceptedFiles.length} + onClose={handleClose} + busy={importStatus === ImportStatus.Importing} + /> {keystoresPostError ? ( - `Error: ${keystoresPostError}` + {keystoresPostError} ) : keystoresPostResponse ? (
{keystoresPostResponse.data.map((result, index) => ( -
- - {shortenPubkey(acceptedFiles[index]?.pubkey)} - - - Status: {result.status} {getEmoji(result.status)} - - {result.message ? ( - - Message: {result.message} - - ) : null} -
+ ))}
) : ( @@ -72,12 +92,16 @@ export default function ImportDialog({
+ - - - + ); diff --git a/packages/ui/src/components/Dialogs/KeystoresDeleteDialog.tsx b/packages/ui/src/components/Dialogs/KeystoresDeleteDialog.tsx index eb829d1e..e23415d4 100644 --- a/packages/ui/src/components/Dialogs/KeystoresDeleteDialog.tsx +++ b/packages/ui/src/components/Dialogs/KeystoresDeleteDialog.tsx @@ -1,23 +1,15 @@ +import type { JSX } from "react"; import { useState } from "react"; -import { - Dialog, - DialogTitle, - DialogContent, - Box, - Typography, - Button, - DialogContentText, - DialogActions, - Alert -} from "@mui/material"; -import { GridSelectionModel } from "@mui/x-data-grid"; +import { Dialog, DialogContent, Box, Button, DialogContentText, DialogActions, Alert, Divider } from "@mui/material"; +import type { GridRowId } from "@mui/x-data-grid"; import { importDialogBoxStyle } from "../../Styles/dialogStyles"; import WaitBox from "../WaitBox/WaitBox"; import DeletionWarning from "./DeletionWarning"; -import { shortenPubkey } from "@stakingbrain/common"; import { rpcClient } from "../../socket"; import { SlideTransition } from "./Transitions"; -import { getEmoji } from "../../utils/dataUtils"; +import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline"; +import DialogHeader from "./DialogHeader"; +import ValidatorResult from "./ValidatorResult"; import type { CustomValidatorGetResponse, Web3signerDeleteResponse } from "@stakingbrain/brain"; import { useTheme, useMediaQuery } from "@mui/material"; @@ -29,8 +21,8 @@ export default function KeystoresDeleteDialog({ setOpen }: { rows: CustomValidatorGetResponse[]; - selectedRows: GridSelectionModel; - setSelectedRows: (selectedRows: GridSelectionModel) => void; + selectedRows: GridRowId[]; + setSelectedRows: (selectedRows: GridRowId[]) => void; open: boolean; setOpen: (open: boolean) => void; }): JSX.Element { @@ -80,36 +72,34 @@ export default function KeystoresDeleteDialog({ aria-describedby="alert-dialog-description" TransitionComponent={SlideTransition} > - - {keystoresDelete ? "Keys deleted successfully" : "Delete Keystores?"} - + } + count={keystoresDelete ? deletedPubkeys.length : selectedRows.length} + onClose={handleClose} + busy={loading} + /> {keystoresDeleteError ? ( - `Error: ${keystoresDeleteError}` + {keystoresDeleteError} ) : keystoresDelete?.data ? (
{keystoresDelete.data.map((result, index) => { const pubkey = deletedPubkeys[index]; return ( -
- - {shortenPubkey(pubkey)} - - - Status: {result.status} {getEmoji(result.status)} - - {result.message ? ( - - Message: {result.message} - - ) : null} -
+ ); })} {keystoresDelete.slashing_protection ? (
- + It is strongly recommended to stop the validator and watch at least 3 missed attestations in the explorer before uploading the keys to another machine. @@ -120,7 +110,7 @@ export default function KeystoresDeleteDialog({ download="slashing_protection.json" sx={{ borderRadius: 2 }} > - Download Slashing Protection Data + Download slashing protection
) : null} @@ -138,7 +128,11 @@ export default function KeystoresDeleteDialog({ )} + + {!keystoresDelete && !loading ? ( ) : null} - ); diff --git a/packages/ui/src/components/Dialogs/KeystoresExitDialog.tsx b/packages/ui/src/components/Dialogs/KeystoresExitDialog.tsx index 7be69ad5..b88c4cac 100644 --- a/packages/ui/src/components/Dialogs/KeystoresExitDialog.tsx +++ b/packages/ui/src/components/Dialogs/KeystoresExitDialog.tsx @@ -1,23 +1,25 @@ +import type { JSX } from "react"; import { useState } from "react"; import { Dialog, - DialogTitle, DialogContent, Box, - Typography, Button, DialogContentText, DialogActions, - TextField + TextField, + Alert, + Divider } from "@mui/material"; -import { GridSelectionModel } from "@mui/x-data-grid"; +import type { GridRowId } from "@mui/x-data-grid"; import { importDialogBoxStyle } from "../../Styles/dialogStyles"; import WaitBox from "../WaitBox/WaitBox"; import ExitWarning from "./ExitWarning"; -import { shortenPubkey } from "@stakingbrain/common"; import { rpcClient } from "../../socket"; import { SlideTransition } from "./Transitions"; -import { getEmoji } from "../../utils/dataUtils"; +import LogoutIcon from "@mui/icons-material/Logout"; +import DialogHeader from "./DialogHeader"; +import ValidatorResult from "./ValidatorResult"; import type { CustomValidatorGetResponse, ValidatorExitExecute } from "@stakingbrain/brain"; import { useTheme, useMediaQuery } from "@mui/material"; @@ -29,8 +31,8 @@ export default function KeystoresExitDialog({ setOpen }: { rows: CustomValidatorGetResponse[]; - selectedRows: GridSelectionModel; - setSelectedRows: (selectedRows: GridSelectionModel) => void; + selectedRows: GridRowId[]; + setSelectedRows: (selectedRows: GridRowId[]) => void; open: boolean; setOpen: (open: boolean) => void; }): JSX.Element { @@ -103,28 +105,27 @@ export default function KeystoresExitDialog({ aria-describedby="alert-dialog-description" TransitionComponent={SlideTransition} > - {validatorsExitResponse ? "Done" : "Exit Validators?"} + } + count={validatorsExitResponse?.length ?? selectedRows.length} + onClose={handleClose} + busy={loading} + /> {validatorsExitError ? ( - `Error: ${validatorsExitError}` + {validatorsExitError} ) : validatorsExitResponse ? (
- {validatorsExitResponse.map((result, index) => ( -
- - {shortenPubkey(rows[index]?.pubkey)} - - - Status: {result.status.exited ? <>Exited : <>Not exited}{" "} - {getEmoji(result.status.exited)} - - {result.status.message ? ( - - Message: {result.status.message} - - ) : null} -
+ {validatorsExitResponse.map((result) => ( + ))}
) : ( @@ -153,9 +154,16 @@ export default function KeystoresExitDialog({ )}
+ + {!validatorsExitResponse && !loading ? ( <> + - ) : null} - ); diff --git a/packages/ui/src/components/Dialogs/ValidatorResult.tsx b/packages/ui/src/components/Dialogs/ValidatorResult.tsx new file mode 100644 index 00000000..c4515541 --- /dev/null +++ b/packages/ui/src/components/Dialogs/ValidatorResult.tsx @@ -0,0 +1,33 @@ +import { Chip, Paper, Stack, Typography } from "@mui/material"; +import { shortenPubkey } from "@stakingbrain/common"; + +export default function ValidatorResult({ + pubkey, + status, + message +}: { + pubkey: string; + status: string; + message?: string; +}) { + const color = ["imported", "deleted", "Exited"].includes(status) + ? "success" + : ["error", "Not exited"].includes(status) + ? "error" + : "default"; + return ( + + + + {shortenPubkey(pubkey)} + + + + {message && ( + + {message} + + )} + + ); +} diff --git a/packages/ui/src/components/FileCards/FileCardList.css b/packages/ui/src/components/FileCards/FileCardList.css deleted file mode 100644 index 1838fced..00000000 --- a/packages/ui/src/components/FileCards/FileCardList.css +++ /dev/null @@ -1,5 +0,0 @@ -button { - background: none; - border: none; - cursor: pointer; -} diff --git a/packages/ui/src/components/FileCards/FileCardList.tsx b/packages/ui/src/components/FileCards/FileCardList.tsx index e52da44c..2c1af428 100644 --- a/packages/ui/src/components/FileCards/FileCardList.tsx +++ b/packages/ui/src/components/FileCards/FileCardList.tsx @@ -1,7 +1,7 @@ -import { Card, Box, Typography, TextField, MenuItem, Select, FormControl, FormHelperText } from "@mui/material"; +import type { JSX } from "react"; +import { Card, Box, Chip, IconButton, Typography, TextField, MenuItem, FormControl } from "@mui/material"; import { KeystoreInfo, TagSelectOption } from "../../types"; import CloseIcon from "@mui/icons-material/Close"; -import "./FileCardList.css"; import { Tag, shortenPubkey, isFeeRecipientEditable } from "@stakingbrain/common"; export default function FileCardList( @@ -10,8 +10,8 @@ export default function FileCardList( passwords: string[], setPasswords: (passwords: string[]) => void, useSamePassword: boolean, - tags: Tag[], - setTags: (tags: Tag[]) => void, + tags: (Tag | "")[], + setTags: (tags: (Tag | "")[]) => void, useSameTag: boolean, feeRecipients: string[], setFeeRecipients: (feeRecipients: string[]) => void, @@ -28,8 +28,8 @@ export default function FileCardList( setPasswords: (passwords: string[]) => void, feeRecipients: string[], setFeeRecipients: (feeRecipients: string[]) => void, - tags: Tag[], - setTags: (tags: Tag[]) => void + tags: (Tag | "")[], + setTags: (tags: (Tag | "")[]) => void ) => { const indexToRemove = fileInfos.indexOf(fileInfo); setAcceptedFiles(fileInfos.filter((f, index) => index !== indexToRemove)); @@ -39,18 +39,29 @@ export default function FileCardList( }; return Array.from(fileInfos).map((fileInfo, index) => ( - + - - ✅ {fileInfo.file.name} - {shortenPubkey(fileInfo.pubkey)} - - + {(!useSameTag || !useSameFeeRecipient || !useSamePassword) && ( @@ -74,23 +85,25 @@ export default function FileCardList( {!useSamePassword && ( { const newPasswords = [...passwords]; newPasswords[index] = event.target.value; setPasswords(newPasswords); }} - helperText={"Password to decrypt the keystore(s)"} + helperText="Password used to create this keystore." /> )} {!useSameTag && ( <> - - Staking protocol + )} {!useSameFeeRecipient && ( { const newFeeRecipients = [...feeRecipients]; @@ -131,7 +142,7 @@ export default function FileCardList( }} error={isFeeRecipientFieldWrong(index)} helperText={getFeeRecipientFieldHelperText(index)} - disabled={!isFeeRecipientEditable(tags[index])} + disabled={!!tags[index] && !isFeeRecipientEditable(tags[index])} /> )} diff --git a/packages/ui/src/components/FileDrop/FileDrop.tsx b/packages/ui/src/components/FileDrop/FileDrop.tsx index 64dd92cf..667b7c58 100644 --- a/packages/ui/src/components/FileDrop/FileDrop.tsx +++ b/packages/ui/src/components/FileDrop/FileDrop.tsx @@ -1,6 +1,7 @@ +import type { JSX } from "react"; import { DropEvent, useDropzone } from "react-dropzone"; -import { useMemo } from "react"; -import { baseStyle, acceptStyle, activeStyle, rejectStyle } from "../../Styles/fileDropStyles"; +import { Box, Stack, Typography } from "@mui/material"; +import UploadFileOutlinedIcon from "@mui/icons-material/UploadFileOutlined"; interface Props { callback: (files: T[], event: DropEvent) => void; @@ -9,27 +10,40 @@ interface Props { export default function FileDrop({ callback }: Props): JSX.Element { const { getRootProps, getInputProps, isDragActive, isDragAccept, isDragReject } = useDropzone({ onDropAccepted: callback, - accept: "application/json" + accept: { "application/json": [".json"] } }); - const style = useMemo( - () => ({ - ...baseStyle, - ...(isDragActive ? activeStyle : {}), - ...(isDragAccept ? acceptStyle : {}), - ...(isDragReject ? rejectStyle : {}) - }), - [isDragActive, isDragReject, isDragAccept] + return ( + + + + + + {isDragActive ? "Drop your files here" : "Drop JSON files here or click to browse"} + + + JSON files only + + + ); - - const dropzone = ( -
-
- - {isDragActive ?

Drop the file here ...

:

Drop JSON file here, or click to select file

} -
-
- ); - - return dropzone; } diff --git a/packages/ui/src/components/Layout/PageLayout.tsx b/packages/ui/src/components/Layout/PageLayout.tsx new file mode 100644 index 00000000..2ede40c1 --- /dev/null +++ b/packages/ui/src/components/Layout/PageLayout.tsx @@ -0,0 +1,55 @@ +import type { ReactNode } from "react"; +import { Box, Stack, Typography } from "@mui/material"; + +export default function PageLayout({ children, maxWidth = 1280 }: { children: ReactNode; maxWidth?: number }) { + return ( + + {children} + + ); +} + +export function PageHeader({ + title, + subtitle, + icon, + action +}: { + title: string; + subtitle: string; + icon: ReactNode; + action?: ReactNode; +}) { + return ( + + + + {icon} + + + + {title} + + + {subtitle} + + + + {action} + + ); +} diff --git a/packages/ui/src/components/StakerConfig/StakerConfig.tsx b/packages/ui/src/components/StakerConfig/StakerConfig.tsx index 7e85f688..f44dbab5 100644 --- a/packages/ui/src/components/StakerConfig/StakerConfig.tsx +++ b/packages/ui/src/components/StakerConfig/StakerConfig.tsx @@ -1,154 +1,40 @@ -import React from "react"; +import type { JSX } from "react"; import { StakerConfig as StakerConfigType } from "@stakingbrain/common"; -import { Card, Box, Container, Typography } from "@mui/material"; -import TrendingFlatRoundedIcon from "@mui/icons-material/TrendingFlatRounded"; -import SyncAltRoundedIcon from "@mui/icons-material/SyncAltRounded"; +import { Box, Paper, Stack, Typography } from "@mui/material"; import { prettyClientDnpName } from "../../utils/dataUtils"; export default function StakerConfig({ stakerConfig }: { stakerConfig: StakerConfigType }): JSX.Element { - const images: { [key: string]: string } = { - erigon: "/assets/erigon.png", - geth: "/assets/geth.png", - reth: "/assets/reth.png", - besu: "/assets/besu.png", - nethermind: "/assets/nethermind.png", - prysm: "/assets/prysm.png", - lighthouse: "/assets/lighthouse.png", - teku: "/assets/teku.png", - nimbus: "/assets/nimbus.png", - lodestar: "/assets/lodestar.png", - - // Default logo until we have a package for them - default: "/assets/dappnode_logo_clean.png" - }; - + const clients = [ + { label: "Execution client", name: stakerConfig.executionClient }, + { label: "Consensus client", name: stakerConfig.consensusClient }, + { label: "Signer", name: "Web3Signer" } + ]; + const images = ["erigon", "geth", "reth", "besu", "nethermind", "prysm", "lighthouse", "teku", "nimbus", "lodestar"]; return ( - //The container should fill all horizontal space - - - - - - - - - {prettyClientDnpName(stakerConfig.executionClient)} - - - - - - - - - - - - {prettyClientDnpName(stakerConfig.consensusClient)} - - - - - - - - - - - - Signer - - - - + + + Connected clients + + + {clients.map(({ label, name }) => ( + + + + + {label} + + + {prettyClientDnpName(name)} + + + + ))} + + ); } diff --git a/packages/ui/src/components/TopBar/ToolBar.tsx b/packages/ui/src/components/TopBar/ToolBar.tsx index a6d5a1a3..2b288a00 100644 --- a/packages/ui/src/components/TopBar/ToolBar.tsx +++ b/packages/ui/src/components/TopBar/ToolBar.tsx @@ -1,7 +1,5 @@ -import Toolbar from "@mui/material/Toolbar"; -import { HeaderTypography } from "../../Styles/Typographies"; -import { Box, capitalize, Typography } from "@mui/material"; -import IconButton from "@mui/material/IconButton"; +import type { JSX } from "react"; +import { Box, capitalize, Chip, IconButton, Stack, Toolbar, Tooltip, Typography } from "@mui/material"; import LightModeIcon from "@mui/icons-material/LightMode"; import DarkModeIcon from "@mui/icons-material/DarkMode"; import UnfoldMoreIcon from "@mui/icons-material/UnfoldMore"; @@ -22,67 +20,49 @@ export default function ToolBar({ network?: Network; }): JSX.Element { return ( - - - logo - - + + + - + + Staking Brain + {network && ( - - {capitalize(network)} - + )} - - -
- - setMode(mode === "dark" ? "light" : "dark")} color="inherit"> - {mode === "dark" ? ( - - ) : ( - - )} + + + + + setMode(mode === "dark" ? "light" : "dark")} + > + {mode === "dark" ? : } + + setUserMode(userMode === "basic" ? "advanced" : "basic")} + aria-label="Advanced information" + aria-pressed={userMode === "advanced"} color="inherit" + sx={{ bgcolor: userMode === "advanced" ? "action.selected" : undefined }} + onClick={() => setUserMode(userMode === "basic" ? "advanced" : "basic")} > - {userMode === "basic" ? ( - - ) : ( - - )} + {userMode === "basic" ? : } - -
+ +
); } diff --git a/packages/ui/src/components/TopBar/TopBar.tsx b/packages/ui/src/components/TopBar/TopBar.tsx index 504b98da..dd21b379 100644 --- a/packages/ui/src/components/TopBar/TopBar.tsx +++ b/packages/ui/src/components/TopBar/TopBar.tsx @@ -1,3 +1,4 @@ +import type { JSX } from "react"; import { AppBar } from "@mui/material"; import ToolBar from "./ToolBar"; import { Network } from "@stakingbrain/common"; @@ -16,7 +17,7 @@ export default function TopBar({ network?: Network; }): JSX.Element { return ( - + ); diff --git a/packages/ui/src/components/ValidatorList/KeystoresDataGrid.tsx b/packages/ui/src/components/ValidatorList/KeystoresDataGrid.tsx index 3e15d344..c9cc70f5 100644 --- a/packages/ui/src/components/ValidatorList/KeystoresDataGrid.tsx +++ b/packages/ui/src/components/ValidatorList/KeystoresDataGrid.tsx @@ -1,4 +1,5 @@ -import { DataGrid, GridAlignment, GridSelectionModel } from "@mui/x-data-grid"; +import type { JSX } from "react"; +import { DataGrid, GridAlignment, GridRowId } from "@mui/x-data-grid"; import { useEffect, useState } from "react"; import { beaconchaApiParamsMap } from "../../params"; import { @@ -15,8 +16,10 @@ import EditIcon from "@mui/icons-material/Edit"; import CheckCircleIcon from "@mui/icons-material/CheckCircle"; import CancelIcon from "@mui/icons-material/Cancel"; import HelpIcon from "@mui/icons-material/Help"; -import { CircularProgress, IconButton, Tooltip } from "@mui/material"; -import { HeaderTypography } from "../../Styles/Typographies"; +import { capitalize, Chip, CircularProgress, IconButton, Paper, Stack, Tooltip } from "@mui/material"; +import { PageHeader } from "../Layout/PageLayout"; +import VpnKeyOutlinedIcon from "@mui/icons-material/VpnKeyOutlined"; +import TuneIcon from "@mui/icons-material/Tune"; import { Box } from "@mui/system"; import UploadFileIcon from "@mui/icons-material/UploadFile"; import { Button } from "@mui/material"; @@ -25,6 +28,7 @@ import { BeaconchaUrlBuildingStatus, SmoothStatusByPubkey } from "../../types"; import buildValidatorSummaryURL from "../../utils/buildValidatorSummaryURL"; import LogoutIcon from "@mui/icons-material/Logout"; import { rpcClient } from "../../socket"; +import { selectedGridRowIds } from "../../utils/dataUtils"; import type { BeaconchaGetResponse, CustomValidatorGetResponse } from "@stakingbrain/brain"; import { SmoothValidatorByIndexApiResponse, SmoothValidator, MevSpSubscriptionStatus } from "../../types"; @@ -37,6 +41,7 @@ export default function KeystoresDataGrid({ userMode, setDeleteOpen, setEditFeesOpen, + setBuilderConfigOpen, setExitOpen, summaryUrlBuildingStatus, setSummaryUrlBuildingStatus, @@ -44,18 +49,19 @@ export default function KeystoresDataGrid({ }: { rows: CustomValidatorGetResponse[]; areRowsSelected: boolean; - selectedRows: GridSelectionModel; - setSelectedRows: (selectedRows: GridSelectionModel) => void; + selectedRows: GridRowId[]; + setSelectedRows: (selectedRows: GridRowId[]) => void; network: string; userMode: "basic" | "advanced"; setDeleteOpen(open: boolean): void; setEditFeesOpen(open: boolean): void; + setBuilderConfigOpen(open: boolean): void; setExitOpen(open: boolean): void; summaryUrlBuildingStatus: BeaconchaUrlBuildingStatus; setSummaryUrlBuildingStatus: (status: BeaconchaUrlBuildingStatus) => void; mevSpFeeRecipient: string | null; }): JSX.Element { - const [pageSize, setPageSize] = useState(rows.length > 10 ? 10 : rows.length); + const [paginationModel, setPaginationModel] = useState({ page: 0, pageSize: 10 }); const beaconchaBaseUrl = beaconchaApiParamsMap.get(network)?.baseUrl; const [validatorSummaryURL, setValidatorSummaryURL] = useState(""); @@ -166,7 +172,7 @@ export default function KeystoresDataGrid({ const columns: GridColDef[] = [ { field: "pubkey", - headerName: "Public Key", + headerName: "Public key", description: "Validating Public Key", disableColumnMenu: true, headerClassName: "tableHeader", @@ -175,7 +181,7 @@ export default function KeystoresDataGrid({ }, { field: "feeRecipient", - headerName: "Fee Recipient", + headerName: "Fee recipient", description: "Address to which the rewards generated from proposing a block are sent", disableReorder: true, disableColumnMenu: true, @@ -241,7 +247,8 @@ export default function KeystoresDataGrid({ sx={{ height: 40, width: 40, - padding: 0 + padding: 0, + display: "block" }} alt={rowData.row.tag} src={"/assets/tagIcons/" + rowData.row.tag + ".png"} @@ -261,11 +268,29 @@ export default function KeystoresDataGrid({ pubkeyInValidator: row.validatorImported, pubkeyInSigner: row.signerImported, feeRecipientImported: row.validatorFeeRecipientCorrect, + builderConfig: row.builderConfig, + builderConfigStatus: row.builderConfigStatus, id: index })); if (userMode === "advanced") columns.push( + { + field: "builderConfigStatus", + headerName: "Builder config", + description: "Last application attempt; open Builder config to inspect client values and refresh status", + width: 180, + sortable: false, + renderCell: ({ row }) => ( + + + {capitalize( + row.builderConfigStatus?.status ?? (row.builderConfig === undefined ? "Unmanaged" : "Pending") + )} + + + ) + }, { field: "beaconchaUrl", headerName: "URL", @@ -277,9 +302,16 @@ export default function KeystoresDataGrid({ align: "center", headerAlign: "center", renderCell: (rowData) => ( - - - + + + ), headerClassName: "tableHeader", width: 60 @@ -297,11 +329,11 @@ export default function KeystoresDataGrid({ renderCell: (rowData) => (
{rowData.row.withdrawalCredentials.format === "ecdsa" ? ( - + ) : rowData.row.withdrawalCredentials.format === "bls" ? ( - + ) : ( - + )}
), @@ -323,11 +355,11 @@ export default function KeystoresDataGrid({ renderCell: (rowData) => (
{rowData.row.pubkeyInSigner === true ? ( - - ) : rowData.row.pubkeyInSigner ? ( - + + ) : rowData.row.pubkeyInSigner === false ? ( + ) : ( - + )}
) @@ -347,11 +379,11 @@ export default function KeystoresDataGrid({ renderCell: (rowData) => (
{rowData.row.pubkeyInValidator === true ? ( - - ) : rowData.row.pubkeyInValidator ? ( - + + ) : rowData.row.pubkeyInValidator === false ? ( + ) : ( - + )}
) @@ -371,11 +403,11 @@ export default function KeystoresDataGrid({ renderCell: (rowData) => (
{rowData.row.feeRecipientImported === true ? ( - - ) : rowData.row.feeRecipientImported ? ( - + + ) : rowData.row.feeRecipientImported === false ? ( + ) : ( - + )}
) @@ -432,17 +464,43 @@ export default function KeystoresDataGrid({ } return ( - <> -
- -
+ + } + action={ + + } + /> + + + {summaryUrlBuildingStatus === BeaconchaUrlBuildingStatus.InProgress ? ( - + ) : ( { await getValidatorSummaryURL(); @@ -455,56 +513,69 @@ export default function KeystoresDataGrid({ )} - } disabled={!areRowsSelected || areAllSelectedRowsUneditable()} onClick={() => setEditFeesOpen(true)} > - - + Fee recipient + + + - setDeleteOpen(true)}> + setDeleteOpen(true)}> - setExitOpen(true)}> + setExitOpen(true)} + > -
-
-
+ + + { if (params.field === "validating_pubkey" || params.field === "fee_recipient") - navigator.clipboard.writeText(params.value); + navigator.clipboard.writeText(String(params.value)); }} columns={columns} - pageSize={pageSize} - rowsPerPageOptions={[10, 20, 50, 100]} - onPageSizeChange={(newPageSize) => setPageSize(newPageSize)} + paginationModel={paginationModel} + pageSizeOptions={[10, 20, 50, 100]} + onPaginationModelChange={setPaginationModel} checkboxSelection - onSelectionModelChange={(selectionModel: GridSelectionModel) => setSelectedRows(selectionModel)} - sx={{ borderRadius: 2 }} + disableRowSelectionExcludeModel + rowSelectionModel={{ type: "include", ids: new Set(selectedRows) }} + onRowSelectionModelChange={(selectionModel) => + setSelectedRows(selectedGridRowIds(selectionModel, customRows)) + } + sx={{ + borderRadius: 2.5, + "& .MuiDataGrid-cell": { display: "flex", alignItems: "center" }, + "& .MuiDataGrid-cell--textCenter": { justifyContent: "center" }, + "& .MuiDataGrid-cell svg": { display: "block" }, + "& .MuiDataGrid-columnHeaderTitle": { fontWeight: 600 }, + "& .MuiDataGrid-columnHeader": { bgcolor: "action.hover" } + }} /> -
- - - - - + ); } diff --git a/packages/ui/src/components/ValidatorList/SmoothStatus.tsx b/packages/ui/src/components/ValidatorList/SmoothStatus.tsx index eeebaf6d..ee1bca09 100644 --- a/packages/ui/src/components/ValidatorList/SmoothStatus.tsx +++ b/packages/ui/src/components/ValidatorList/SmoothStatus.tsx @@ -1,3 +1,4 @@ +import type { JSX } from "react"; import Tooltip from "@mui/material/Tooltip"; import { HourglassTop, CrisisAlert, CheckCircle, Warning, Close, Help, Block } from "@mui/icons-material"; import CircularProgress from "@mui/material/CircularProgress"; @@ -16,19 +17,19 @@ export default function SmoothStatus({ // Helper functions for rendering based on conditions const renderAwaitingSubscription = () => ( - + ); const renderWrongFeeRecipient = () => ( - + ); const renderWrongWithdrawalAddressFormat = () => ( - + ); @@ -38,29 +39,29 @@ export default function SmoothStatus({ switch (status) { case MevSpSubscriptionStatus.ACTIVE: - icon = ; + icon = ; tooltipText = "Active Subscription. All good!"; break; case MevSpSubscriptionStatus.YELLOW_CARD: - icon = ; + icon = ; tooltipText = "Yellow Card Subscription. This validator missed it's last proposal. Propose successfully next block to get back to active."; break; case MevSpSubscriptionStatus.RED_CARD: - icon = ; + icon = ; tooltipText = "Red Card Subscription. This validator missed it's two last proposals in a row. Propose successfully next block to get back to yellow card."; break; case MevSpSubscriptionStatus.BANNED: - icon = ; + icon = ; tooltipText = "Banned Subscription. This validator proposed a block with an incorrect fee recipient."; break; case MevSpSubscriptionStatus.NOT_SUBSCRIBED: - icon = ; + icon = ; tooltipText = "Not Subscribed. Subscribe to earn more rewards!"; break; default: - icon = ; + icon = ; tooltipText = "Unknown Status. Something went wrong."; // we should never get here break; } @@ -74,7 +75,7 @@ export default function SmoothStatus({ if (oracleCallError) { return ( - + ); } @@ -88,7 +89,7 @@ export default function SmoothStatus({ if (subscriptionStatus === "unknown") { return ( - + ); } diff --git a/packages/ui/src/components/ValidatorList/ValidatorList.tsx b/packages/ui/src/components/ValidatorList/ValidatorList.tsx index d7d4bc93..e7637092 100644 --- a/packages/ui/src/components/ValidatorList/ValidatorList.tsx +++ b/packages/ui/src/components/ValidatorList/ValidatorList.tsx @@ -1,8 +1,10 @@ +import type { JSX } from "react"; import KeystoresDataGrid from "./KeystoresDataGrid"; import KeystoresDeleteDialog from "../Dialogs/KeystoresDeleteDialog"; import EditFeesDialog from "../Dialogs/EditFeesDialog"; +import BuilderConfigDialog from "../Dialogs/BuilderConfigDialog"; import { Alert, Box, CircularProgress } from "@mui/material"; -import { GridSelectionModel } from "@mui/x-data-grid"; +import type { GridRowId } from "@mui/x-data-grid"; import { StakerConfig as StakerConfigType } from "@stakingbrain/common"; import { useEffect, useState } from "react"; import { BeaconchaUrlBuildingStatus } from "../../types"; @@ -11,6 +13,7 @@ import StakerConfig from "../StakerConfig/StakerConfig"; import KeystoresExitDialog from "../Dialogs/KeystoresExitDialog"; import { getSmoothAddressByNetwork } from "../../utils/addresses"; import type { CustomValidatorGetResponse } from "@stakingbrain/brain"; +import PageLayout from "../Layout/PageLayout"; export default function ValidatorList({ stakerConfig, @@ -19,9 +22,10 @@ export default function ValidatorList({ stakerConfig: StakerConfigType; userMode: "basic" | "advanced"; }): JSX.Element { - const [selectedRows, setSelectedRows] = useState([]); + const [selectedRows, setSelectedRows] = useState([]); const [deleteOpen, setDeleteOpen] = useState(false); const [editFeesOpen, setEditFeesOpen] = useState(false); + const [builderConfigOpen, setBuilderConfigOpen] = useState(false); const [exitOpen, setExitOpen] = useState(false); const [loading, setLoading] = useState(false); const [validatorsGet, setValidatorsGet] = useState(); @@ -30,17 +34,13 @@ export default function ValidatorList({ const { network, isMevBoostSet } = stakerConfig; const smoothAddress = getSmoothAddressByNetwork(network); - useEffect(() => { - getValidators(); - }, []); - // Re-render table after delete/update validators useEffect(() => { - if (!deleteOpen && !editFeesOpen && !exitOpen) { + if (!deleteOpen && !editFeesOpen && !exitOpen && !builderConfigOpen) { getValidators(); setSelectedRows([]); } - }, [deleteOpen, editFeesOpen, exitOpen]); + }, [deleteOpen, editFeesOpen, exitOpen, builderConfigOpen]); async function getValidators() { try { @@ -57,10 +57,9 @@ export default function ValidatorList({ const SmoothBanner = () => { return ( - - - 🎉 Calling all solo stakers: Smooth has arrived! To join, select your validators and click on the edit fee - recipient button. Learn more{" "} + + + Join Smooth by selecting your validators and editing their fee recipient. Learn more{" "} here! @@ -72,30 +71,26 @@ export default function ValidatorList({ }; return ( -
+ {userMode === "advanced" && } {(network === "hoodi" || network === "mainnet") && } {validatorsGetError ? ( - - {validatorsGetError} - + {validatorsGetError} ) : loading ? ( @@ -116,6 +111,7 @@ export default function ValidatorList({ userMode={userMode} setDeleteOpen={setDeleteOpen} setEditFeesOpen={setEditFeesOpen} + setBuilderConfigOpen={setBuilderConfigOpen} setExitOpen={setExitOpen} summaryUrlBuildingStatus={summaryUrlBuildingStatus} setSummaryUrlBuildingStatus={setSummaryUrlBuildingStatus} @@ -123,14 +119,14 @@ export default function ValidatorList({ /> {summaryUrlBuildingStatus === BeaconchaUrlBuildingStatus.Error && ( - + There was an error loading the dashboard. The number of API calls allowed by the explorer might have been exceeded or the network might be invalid. Please wait for a minute and refresh the page. )} {summaryUrlBuildingStatus === BeaconchaUrlBuildingStatus.NoIndexes && ( - + There was an error loading the dashboard. The explorer may not be able to show a dashboard for all your validators or some of them might not have been indexed yet. Have you done a deposit? @@ -157,6 +153,14 @@ export default function ValidatorList({ /> )} + {builderConfigOpen && ( + validatorsGet[row as number])} + setOpen={setBuilderConfigOpen} + /> + )} + {exitOpen && ( ) : ( - - There are no keystores to display. - + There are no keystores to display. )} -
+ ); } diff --git a/packages/ui/src/components/WaitBox/WaitBox.tsx b/packages/ui/src/components/WaitBox/WaitBox.tsx index 2828094f..ab9a2aa5 100644 --- a/packages/ui/src/components/WaitBox/WaitBox.tsx +++ b/packages/ui/src/components/WaitBox/WaitBox.tsx @@ -1,20 +1,17 @@ +import type { JSX } from "react"; import { Box, CircularProgress, DialogContentText } from "@mui/material"; export default function WaitBox(): JSX.Element { return ( - + Please wait ); diff --git a/packages/ui/src/index.tsx b/packages/ui/src/index.tsx index 846ed2ce..64117b85 100644 --- a/packages/ui/src/index.tsx +++ b/packages/ui/src/index.tsx @@ -1,11 +1,13 @@ import React from "react"; -import ReactDOM from "react-dom"; +import { createRoot } from "react-dom/client"; import "./index.css"; import App from "./App"; -ReactDOM.render( +const root = document.getElementById("root"); +if (!root) throw new Error("Root element is missing"); + +createRoot(root).render( - , - document.getElementById("root") + ); diff --git a/packages/ui/src/params.ts b/packages/ui/src/params.ts index 29409831..0dda7c09 100644 --- a/packages/ui/src/params.ts +++ b/packages/ui/src/params.ts @@ -3,7 +3,6 @@ import type { ApiParams } from "@stakingbrain/brain"; export const beaconchaApiParamsMap = new Map>([ ["mainnet", { baseUrl: "https://beaconcha.in", apiPath: "/api/v1/" }], - ["prater", { baseUrl: "https://prater.beaconcha.in", apiPath: "/api/v1/" }], ["gnosis", { baseUrl: "https://gnosischa.in", apiPath: "/api/v1/" }], [ "lukso", @@ -12,7 +11,6 @@ export const beaconchaApiParamsMap = new Map>([ apiPath: "/api/v1/" } ], - ["holesky", { baseUrl: "https://holesky.beaconcha.in", apiPath: "/api/v1/" }], ["hoodi", { baseUrl: "https://hoodi.beaconcha.in", apiPath: "/api/v1/" }] ]); @@ -35,5 +33,5 @@ export const getSmoothUrlByNetwork = (network: Network): string => { }; export const getStakersLink = (network: Network): string => { - return network === "prater" ? "http://my.dappnode/stakers/prater" : "http://my.dappnode/stakers/ethereum"; + return network === Network.Hoodi ? "http://my.dappnode/stakers/hoodi" : "http://my.dappnode/stakers/ethereum"; }; diff --git a/packages/ui/src/react-app-env.d.ts b/packages/ui/src/react-app-env.d.ts deleted file mode 100644 index 6431bc5f..00000000 --- a/packages/ui/src/react-app-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/packages/ui/src/socket/rpcClient.ts b/packages/ui/src/socket/rpcClient.ts new file mode 100644 index 00000000..afef0787 --- /dev/null +++ b/packages/ui/src/socket/rpcClient.ts @@ -0,0 +1,101 @@ +import type { Socket } from "socket.io-client"; +import { RPC_MAX_PAYLOAD_BYTES, RPC_TIMEOUT_MS } from "@stakingbrain/common"; +import type { RoutesArguments, RoutesReturn, RpcMethodNames } from "./types.js"; + +function toErrorMessage(value: unknown): string { + if (value == null) return "Unknown error"; + if (typeof value === "string") return value; + if (value instanceof Error) return value.message || value.name || "Unknown error"; + + if (typeof value === "object") { + const record = value as Record; + // Common JSON-RPC shape: { code, message, data } + const message = record.message; + + if (typeof message === "string") return message; + if (message instanceof Error) return message.message || message.name || "Unknown error"; + + // In this repo the backend currently sends `message: error` where error can be an object. + if (message && typeof message === "object") { + const nested = message as Record; + if (typeof nested.message === "string") return nested.message; + try { + return JSON.stringify(message); + } catch { + return String(message); + } + } + + try { + return JSON.stringify(value); + } catch { + return String(value); + } + } + + return String(value); +} + +export class RpcClient { + constructor(private readonly socket: Socket) { + this.socket.on("connect", () => { + console.log("Connected to server"); + }); + + this.socket.on("disconnect", () => { + console.log("Disconnected from server"); + }); + + this.socket.on("connect_error", (error) => { + console.error("Connection error", error); + }); + } + + async call(method: T, params: RoutesArguments[T]): Promise { + return new Promise((resolve, reject) => { + const rpcRequest = { + jsonrpc: "2.0", + method, + params, + id: Date.now() + }; + + // Socket.IO adds a small packet prefix around the JSON request. + if (new TextEncoder().encode(JSON.stringify(rpcRequest)).byteLength + 64 > RPC_MAX_PAYLOAD_BYTES) { + reject(new Error("This request is too large. Select fewer validators or reduce the builder configuration.")); + return; + } + + this.socket.timeout(RPC_TIMEOUT_MS).emit( + "rpc", + rpcRequest, + ( + error: Error | null, + response: + | { + error?: unknown; + result?: RoutesReturn[T]; + } + | undefined + ) => { + if (error) { + reject( + new Error( + "Connection lost or request timed out. The request may still be running; refresh status before retrying." + ) + ); + } else if (!response) { + reject(new Error("Invalid response from Brain")); + } else if (response.error) { + const err = new Error(toErrorMessage(response.error)); + // Preserve the raw JSON-RPC error payload for debugging if needed. + (err as Error & { rpcError?: unknown }).rpcError = response.error; + reject(err); + } else { + resolve(response.result as RoutesReturn[T]); + } + } + ); + }); + } +} diff --git a/packages/ui/src/socket/socket.ts b/packages/ui/src/socket/socket.ts index 6d476f01..a57310c4 100644 --- a/packages/ui/src/socket/socket.ts +++ b/packages/ui/src/socket/socket.ts @@ -1,82 +1,7 @@ -import { io, Socket } from "socket.io-client"; -import { RoutesArguments, RoutesReturn, RpcMethodNames } from "./types.js"; +import { io } from "socket.io-client"; import { BRAIN_UI_DOMAIN, Network } from "@stakingbrain/common"; +import { RpcClient } from "./rpcClient.js"; -function toErrorMessage(value: unknown): string { - if (value == null) return "Unknown error"; - if (typeof value === "string") return value; - if (value instanceof Error) return value.message || value.name || "Unknown error"; - - if (typeof value === "object") { - const record = value as Record; - // Common JSON-RPC shape: { code, message, data } - const message = record.message; - - if (typeof message === "string") return message; - if (message instanceof Error) return message.message || message.name || "Unknown error"; - - // In this repo the backend currently sends `message: error` where error can be an object. - if (message && typeof message === "object") { - const nested = message as Record; - if (typeof nested.message === "string") return nested.message; - try { - return JSON.stringify(message); - } catch { - return String(message); - } - } - - try { - return JSON.stringify(value); - } catch { - return String(value); - } - } - - return String(value); -} - -class RpcClient { - private socket: Socket; - - constructor(serverUrl: string) { - this.socket = io(serverUrl, { autoConnect: true }); - - this.socket.on("connect", () => { - console.log("Connected to server"); - }); - - this.socket.on("disconnect", () => { - console.log("Disconnected from server"); - }); - - this.socket.on("connect_error", (error) => { - console.error("Connection error", error); - }); - } - - async call(method: T, params: RoutesArguments[T]): Promise { - return new Promise((resolve, reject) => { - const rpcRequest = { - jsonrpc: "2.0", - method, - params, - id: Date.now() - }; - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - this.socket.emit("rpc", rpcRequest, (response: any) => { - if (response.error) { - const err = new Error(toErrorMessage(response.error)); - // Preserve the raw JSON-RPC error payload for debugging if needed. - (err as Error & { rpcError?: unknown }).rpcError = response.error; - reject(err); - } else { - resolve(response.result as RoutesReturn[T]); - } - }); - }); - } -} - -export const rpcClient = new RpcClient(`http://${BRAIN_UI_DOMAIN(window.env?.NETWORK || Network.Hoodi)}:80`); +export const rpcClient = new RpcClient( + io(`http://${BRAIN_UI_DOMAIN(window.env?.NETWORK || Network.Hoodi)}:80`, { autoConnect: true }) +); diff --git a/packages/ui/src/socket/types.ts b/packages/ui/src/socket/types.ts index 3b6b14df..215d2c52 100644 --- a/packages/ui/src/socket/types.ts +++ b/packages/ui/src/socket/types.ts @@ -4,6 +4,7 @@ import type { CustomImportRequest, CustomValidatorGetResponse, CustomValidatorUpdateRequest, + ValidatorBuilderConfigResponse, ValidatorExitExecute, Web3signerDeleteRequest, Web3signerDeleteResponse, @@ -12,7 +13,7 @@ import type { EpochsValidatorsMap, NumberOfDaysToQuery } from "@stakingbrain/brain"; -import { StakerConfig } from "@stakingbrain/common"; +import { BuilderRelayPresets, StakerConfig } from "@stakingbrain/common"; // Define the type for RPC methods export interface RpcMethods { @@ -24,6 +25,8 @@ export interface RpcMethods { updateValidators: (updateRequest: CustomValidatorUpdateRequest[]) => Promise; deleteValidators: (deleteRequest: Web3signerDeleteRequest) => Promise; getValidators: () => Promise; + getBuilderConfigs: (pubkeys: string[]) => Promise; + getBuilderRelayPresets: () => Promise; signerGetStatus: () => Promise; getExitValidators: ({ pubkeys }: { pubkeys: string[] }) => Promise; exitValidators: ({ pubkeys }: { pubkeys: string[] }) => Promise; diff --git a/packages/ui/src/utils/buildValidatorSummaryURL.ts b/packages/ui/src/utils/buildValidatorSummaryURL.ts index 01c25bbe..712e1cf0 100644 --- a/packages/ui/src/utils/buildValidatorSummaryURL.ts +++ b/packages/ui/src/utils/buildValidatorSummaryURL.ts @@ -15,12 +15,6 @@ export default function buildValidatorSummaryURL({ const baseUrl = beaconchaApiParamsMap.get(network)?.baseUrl; if (!baseUrl) return ""; - let summaryValidatorURL = baseUrl + "/dashboard?validators="; - - allValidatorsInfo.forEach((validatorChunk) => { - const chunkIndexes = validatorChunk.data.map((validator) => validator.validatorindex); - summaryValidatorURL += chunkIndexes.join(","); - }); - - return summaryValidatorURL; + const indexes = allValidatorsInfo.flatMap((chunk) => chunk.data.map(({ validatorindex }) => validatorindex)); + return baseUrl + "/dashboard?validators=" + indexes.join(","); } diff --git a/packages/ui/src/utils/builderConfigDisplay.ts b/packages/ui/src/utils/builderConfigDisplay.ts new file mode 100644 index 00000000..024eb36c --- /dev/null +++ b/packages/ui/src/utils/builderConfigDisplay.ts @@ -0,0 +1,143 @@ +import { BuilderConfig, BuilderEntry, defaultBuilderAuthData } from "@stakingbrain/common"; + +export const MAX_BUILDER_BOOST = "18446744073709551615"; + +function isUint64(value: string): boolean { + return /^(0|[1-9][0-9]{0,19})$/.test(value) && BigInt(value) <= BigInt(MAX_BUILDER_BOOST); +} + +export function formatBidAmount(value: string): string { + return isUint64(value) ? `${BigInt(value).toLocaleString()} Gwei` : "Enter a valid amount above"; +} + +export function formatBidBoost(value: string): string { + if (!isUint64(value)) return "Enter a valid percentage above"; + if (value === MAX_BUILDER_BOOST) return "MAX · Prefer builders"; + if (value === "0") return "0% · Prefer local"; + if (value === "100") return "100% · Equal weight"; + return `${BigInt(value).toLocaleString()}%`; +} + +export interface BuilderComparisonRow { + label: string; + saved: string; + effective: string; + savedDetail?: string; + effectiveDetail?: string; +} + +export interface BuilderComparison { + url: string; + source?: "Saved only" | "Client only" | "Client default"; + rows: BuilderComparisonRow[]; +} + +function builderIdentity(entry: BuilderEntry): string { + return JSON.stringify([entry.url, (entry.auth_data ?? defaultBuilderAuthData(entry.url)).toLowerCase()]); +} + +function authorization(entry: BuilderEntry): string { + return entry.auth_data === undefined || entry.auth_data.toLowerCase() === defaultBuilderAuthData(entry.url) + ? "Builder hostname" + : `Custom · ${(entry.auth_data.length - 2) / 2} bytes`; +} + +function keys(entry: BuilderEntry): string { + const count = entry.builder_pubkeys?.length ?? 0; + return count ? `${count} specific ${count === 1 ? "key" : "keys"}` : "Any builder key"; +} + +/** Compare saved intent with GET values for display only; never fill form fields from GET. */ +export function builderConfigComparison( + saved?: BuilderConfig | null, + resolved?: BuilderConfig +): { rows: BuilderComparisonRow[]; builders: BuilderComparison[] } { + const unavailable = "Unavailable"; + const clientBuilders = resolved?.builders ?? []; + const rows: BuilderComparisonRow[] = [ + { + label: "Minimum bid", + saved: saved?.min_bid === undefined ? "Client default" : formatBidAmount(saved.min_bid), + effective: resolved?.min_bid === undefined ? unavailable : formatBidAmount(resolved.min_bid) + }, + { + label: "Bid boost", + saved: saved?.builder_boost_factor === undefined ? "Client default" : formatBidBoost(saved.builder_boost_factor), + effective: + resolved?.builder_boost_factor === undefined ? unavailable : formatBidBoost(resolved.builder_boost_factor) + }, + { + label: "Direct builders", + saved: saved?.builders === undefined ? "Client’s builder list" : `${saved.builders.length} configured`, + effective: resolved?.builders === undefined ? unavailable : `${clientBuilders.length} configured` + } + ]; + const remaining = new Map(clientBuilders.map((entry) => [builderIdentity(entry), entry])); + const pairs: { saved?: BuilderEntry; effective?: BuilderEntry; inherited?: boolean }[] = []; + if (saved?.builders !== undefined) { + for (const entry of saved.builders) { + const identity = builderIdentity(entry); + pairs.push({ saved: entry, effective: remaining.get(identity) }); + remaining.delete(identity); + } + } + for (const entry of remaining.values()) { + pairs.push({ effective: entry, inherited: saved?.builders === undefined }); + } + const builders = pairs.map(({ saved: entry, effective, inherited }): BuilderComparison => { + const absent = resolved === undefined ? unavailable : "Not on client"; + const savedValue = (value: string): string => (entry || inherited ? value : "Not in saved list"); + return { + url: (entry ?? effective)!.url, + source: inherited ? "Client default" : !entry ? "Client only" : !effective && resolved ? "Saved only" : undefined, + rows: [ + { + label: "Minimum bid", + saved: savedValue(entry?.min_bid === undefined ? "Use global minimum bid" : formatBidAmount(entry.min_bid)), + effective: !effective + ? absent + : effective.min_bid === undefined + ? unavailable + : formatBidAmount(effective.min_bid) + }, + { + label: "Bid boost", + saved: savedValue( + entry?.builder_boost_factor === undefined + ? "Use global bid boost" + : formatBidBoost(entry.builder_boost_factor) + ), + effective: !effective + ? absent + : effective.builder_boost_factor === undefined + ? unavailable + : formatBidBoost(effective.builder_boost_factor) + }, + { + label: "Execution payment limit", + saved: savedValue( + entry?.max_execution_payment === undefined ? "Client default" : formatBidAmount(entry.max_execution_payment) + ), + effective: !effective + ? absent + : effective.max_execution_payment === undefined + ? unavailable + : formatBidAmount(effective.max_execution_payment) + }, + { + label: "Authorization", + saved: savedValue(entry ? authorization(entry) : "Builder hostname"), + effective: effective ? authorization(effective) : absent + }, + { + label: "Accepted builder keys", + saved: savedValue(entry ? keys(entry) : "Any builder key"), + effective: effective ? keys(effective) : absent, + savedDetail: entry?.builder_pubkeys?.join("\n"), + effectiveDetail: effective?.builder_pubkeys?.join("\n") + } + ] + }; + }); + return { rows, builders }; +} diff --git a/packages/ui/src/utils/builderConfigForm.ts b/packages/ui/src/utils/builderConfigForm.ts new file mode 100644 index 00000000..43355f47 --- /dev/null +++ b/packages/ui/src/utils/builderConfigForm.ts @@ -0,0 +1,187 @@ +import { + BuilderConfig, + BuilderEntry, + BuilderRelayPreset, + isValidBlsPubkey, + validateBuilderConfig +} from "@stakingbrain/common"; + +export interface OverrideInput { + enabled: boolean; + value: string; +} + +export interface BuilderInput { + id: string; + url: string; + authData: OverrideInput; + publicKeys?: string[]; + minBid: OverrideInput; + boost: OverrideInput; + maxPayment: OverrideInput; +} + +export interface BuilderConfigForm { + minBid: OverrideInput; + boost: OverrideInput; + buildersMode: "custom" | "none"; + builders: BuilderInput[]; +} + +export type BuilderFormErrors = Record; + +export function splitBuilderPublicKeys(value: string): string[] { + return value.split(/[,;\s]+/).filter(Boolean); +} + +let builderIdSeed = 0; + +/** + * Browser ids are only used as React keys and error-field keys. + * crypto.randomUUID is exposed in secure contexts only, and the DAppNode UI is + * served over plain HTTP, so fall back to a per-session unique id. + */ +export function createBuilderId(): string { + const cryptoApi = globalThis.crypto; + if (typeof cryptoApi?.randomUUID === "function") return cryptoApi.randomUUID(); + builderIdSeed += 1; + return `builder-${builderIdSeed.toString(36)}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`; +} + +function override(value?: string): OverrideInput { + return { enabled: value !== undefined, value: value ?? "" }; +} + +export function createBuilderInput(entry?: BuilderEntry): BuilderInput { + return { + id: createBuilderId(), + url: entry?.url ?? "", + authData: override(entry?.auth_data), + publicKeys: entry?.builder_pubkeys === undefined ? undefined : [...entry.builder_pubkeys], + minBid: override(entry?.min_bid), + boost: override(entry?.builder_boost_factor), + maxPayment: override(entry?.max_execution_payment) + }; +} + +/** Seed saved overrides only. Saving in Brain always replaces the direct-builder list explicitly. */ +export function builderConfigToForm(config?: BuilderConfig | null): BuilderConfigForm { + return { + minBid: override(config?.min_bid), + boost: override(config?.builder_boost_factor), + buildersMode: config?.builders?.length ? "custom" : "none", + builders: config?.builders?.map(createBuilderInput) ?? [] + }; +} + +/** Match endpoint spelling without confusing paths, queries or other networks. */ +export function matchesRelayPreset(entry: BuilderInput, preset: Pick): boolean { + try { + return new URL(entry.url.trim()).href === new URL(preset.url).href; + } catch { + return false; + } +} + +/** Preset selection preserves every unrelated endpoint and all existing advanced overrides. */ +export function toggleRelayPreset( + form: BuilderConfigForm, + preset: Pick, + checked: boolean +): BuilderConfigForm { + const current = form.buildersMode === "custom" ? form.builders : []; + const matches = current.some((entry) => matchesRelayPreset(entry, preset)); + const builders = checked + ? matches + ? current + : [...current, createBuilderInput({ url: preset.url })] + : current.filter((entry) => !matchesRelayPreset(entry, preset)); + return { ...form, builders, buildersMode: builders.length ? "custom" : "none" }; +} + +function include(field: string, input: OverrideInput): Record { + return input.enabled ? { [field]: input.value.trim() } : {}; +} + +function buildConfig(form: BuilderConfigForm): BuilderConfig { + return { + ...include("min_bid", form.minBid), + ...include("builder_boost_factor", form.boost), + builders: + form.buildersMode === "none" + ? [] + : form.builders.map((entry) => ({ + url: entry.url.trim(), + ...include("auth_data", entry.authData), + ...include("min_bid", entry.minBid), + ...include("builder_boost_factor", entry.boost), + ...include("max_execution_payment", entry.maxPayment), + ...(entry.publicKeys === undefined ? {} : { builder_pubkeys: entry.publicKeys.map((key) => key.trim()) }) + })) + }; +} + +export function builderFormToConfig(form: BuilderConfigForm): BuilderConfig { + const config = buildConfig(form); + validateBuilderConfig(config); + return config; +} + +function invalid(config: BuilderConfig): boolean { + try { + validateBuilderConfig(config); + return false; + } catch { + return true; + } +} + +const numberMessage = "Enter a whole number from 0 to 18446744073709551615."; + +/** Validate every visible field so a failed save identifies all corrections in place. */ +export function builderFormErrors(form: BuilderConfigForm): BuilderFormErrors { + const errors: BuilderFormErrors = {}; + if (form.minBid.enabled && invalid({ min_bid: form.minBid.value.trim() })) errors.minBid = numberMessage; + if (form.boost.enabled && invalid({ builder_boost_factor: form.boost.value.trim() })) errors.boost = numberMessage; + if (form.buildersMode !== "custom") return errors; + if (form.builders.length === 0) errors.builders = "Add a builder or choose No direct builders."; + if (form.builders.length > 64) errors.builders = "You can configure up to 64 direct builders."; + for (const entry of form.builders) { + const prefix = `builders.${entry.id}`; + if (invalid({ builders: [{ url: entry.url.trim() }] })) + errors[`${prefix}.url`] = + "Enter an HTTP or HTTPS URL, up to 2048 characters. Use punycode for internationalized hostnames."; + if ( + entry.authData.enabled && + invalid({ builders: [{ url: "https://builder.example", auth_data: entry.authData.value.trim() }] }) + ) + errors[`${prefix}.authData`] = "Enter 1–4096 bytes of hexadecimal data, starting with 0x."; + for (const [name, field] of [ + ["minBid", "min_bid"], + ["boost", "builder_boost_factor"], + ["maxPayment", "max_execution_payment"] + ] as const) { + const input = entry[name]; + if (input.enabled && invalid({ builders: [{ url: "https://builder.example", [field]: input.value.trim() }] })) + errors[`${prefix}.${name}`] = numberMessage; + } + entry.publicKeys?.forEach((key, index) => { + if (!isValidBlsPubkey(key.trim())) + errors[`${prefix}.publicKeys.${index}`] = "Enter a 48-byte BLS public key starting with 0x."; + }); + if ((entry.publicKeys?.length ?? 0) > 64) + errors[`${prefix}.publicKeys`] = "You can allow up to 64 builder public keys."; + } + if (Object.keys(errors).length === 0) { + try { + builderFormToConfig(form); + } catch (error) { + const duplicate = error.message.match(/builders\[(\d+)\] duplicates/); + if (duplicate) + errors[`builders.${form.builders[Number(duplicate[1])].id}.url`] = + "Another builder uses this URL and authorization. Change one or remove the duplicate."; + else errors.builders = error.message; + } + } + return errors; +} diff --git a/packages/ui/src/utils/createImportRequest.ts b/packages/ui/src/utils/createImportRequest.ts new file mode 100644 index 00000000..9a094687 --- /dev/null +++ b/packages/ui/src/utils/createImportRequest.ts @@ -0,0 +1,32 @@ +import type { CustomImportRequest } from "@stakingbrain/brain"; +import { isImportFormValid, ImportFormState } from "./importForm.js"; + +export async function createImportRequest({ + acceptedFiles, + passwords, + tags, + feeRecipients, + slashingFile +}: ImportFormState & { + slashingFile?: File; +}): Promise { + if (!isImportFormValid({ acceptedFiles, passwords, tags, feeRecipients })) + throw new Error("Provide a password, protocol and valid fee recipient for every validator"); + // Send JSON contents as text to avoid one Socket.IO binary attachment per file. + return { + importFrom: "ui", + slashing_protection: await slashingFile?.text(), + validatorsImportRequest: await Promise.all( + acceptedFiles.map(async ({ file }, index) => { + const tag = tags[index]; + if (!tag) throw new Error("Select a protocol for every validator"); + return { + keystore: await file.text(), + password: passwords[index], + tag, + feeRecipient: feeRecipients[index] + }; + }) + ) + }; +} diff --git a/packages/ui/src/utils/dataUtils.ts b/packages/ui/src/utils/dataUtils.ts index 45574f83..c4b50bfe 100644 --- a/packages/ui/src/utils/dataUtils.ts +++ b/packages/ui/src/utils/dataUtils.ts @@ -1,7 +1,12 @@ +import type { GridRowId, GridRowSelectionModel } from "@mui/x-data-grid"; +import { isValidBlsPubkey, prefix0xPubkey } from "@stakingbrain/common"; + export const extractPubkey = async (file: File): Promise => { const text = await file.text(); const json = JSON.parse(text); - return json.pubkey; + if (typeof json?.pubkey !== "string" || !isValidBlsPubkey(prefix0xPubkey(json.pubkey))) + throw new Error("Keystore does not contain a valid validator public key"); + return prefix0xPubkey(json.pubkey).toLowerCase(); }; export function getEmoji(status: string | boolean): string { @@ -22,16 +27,15 @@ export function prettyClientDnpName(dnpName: string): string { const clientName = dnpName .split(".")[0] .split("-") - .find( - (name) => - !name.includes("goerli") && - !name.includes("prater") && - !name.includes("gnosis") && - !name.includes("lukso") && - !name.includes("holesky") && - !name.includes("hoodi") - ); + .find((name) => !name.includes("gnosis") && !name.includes("lukso") && !name.includes("hoodi")); if (!clientName) return dnpName; return clientName.split(".")[0].charAt(0).toUpperCase() + clientName.split(".")[0].slice(1); } + +/** Keep bulk actions explicit even when the grid emits a select-all exclusion model. */ +export function selectedGridRowIds(selection: GridRowSelectionModel, rows: { id: GridRowId }[]): GridRowId[] { + return rows + .map(({ id }) => id) + .filter((id) => (selection.type === "include" ? selection.ids.has(id) : !selection.ids.has(id))); +} diff --git a/packages/ui/src/utils/importForm.ts b/packages/ui/src/utils/importForm.ts new file mode 100644 index 00000000..6390db6a --- /dev/null +++ b/packages/ui/src/utils/importForm.ts @@ -0,0 +1,49 @@ +import { BURN_ADDRESS, isFeeRecipientEditable, isValidEcdsaPubkey } from "@stakingbrain/common"; +import type { Tag } from "@stakingbrain/common"; +import type { KeystoreInfo } from "../types.js"; + +export interface ImportFormState { + acceptedFiles: KeystoreInfo[]; + passwords: string[]; + tags: (Tag | "")[]; + feeRecipients: string[]; +} + +export function appendImportFiles( + current: ImportFormState, + incoming: KeystoreInfo[], + shared: { password: boolean; tag: boolean; feeRecipient: boolean } +): ImportFormState { + const seen = new Set(current.acceptedFiles.map(({ pubkey }) => pubkey.toLowerCase())); + const additions = incoming.filter(({ pubkey }) => { + const key = pubkey.toLowerCase(); + if (seen.has(key)) return false; + seen.add(key); + return true; + }); + return { + acceptedFiles: [...current.acceptedFiles, ...additions], + passwords: [...current.passwords, ...additions.map(() => (shared.password ? (current.passwords[0] ?? "") : ""))], + tags: [...current.tags, ...additions.map(() => (shared.tag ? (current.tags[0] ?? "") : ""))], + feeRecipients: [ + ...current.feeRecipients, + ...additions.map(() => (shared.feeRecipient ? (current.feeRecipients[0] ?? "") : "")) + ] + }; +} + +export function isImportFormValid({ acceptedFiles, passwords, tags, feeRecipients }: ImportFormState): boolean { + return ( + acceptedFiles.length > 0 && + acceptedFiles.every((_, index) => { + const tag = tags[index]; + return ( + typeof passwords[index] === "string" && + passwords[index].length > 0 && + !!tag && + (!isFeeRecipientEditable(tag) || + (isValidEcdsaPubkey(feeRecipients[index]) && feeRecipients[index] !== BURN_ADDRESS)) + ); + }) + ); +} diff --git a/packages/ui/test/builderConfigDisplay.test.ts b/packages/ui/test/builderConfigDisplay.test.ts new file mode 100644 index 00000000..e545b441 --- /dev/null +++ b/packages/ui/test/builderConfigDisplay.test.ts @@ -0,0 +1,85 @@ +import { expect } from "chai"; +import { BuilderConfig, defaultBuilderAuthData } from "@stakingbrain/common"; +import { + builderConfigComparison, + formatBidAmount, + formatBidBoost, + MAX_BUILDER_BOOST +} from "../src/utils/builderConfigDisplay.js"; + +const url = "https://builder.example.com"; + +describe("Builder settings display", () => { + it("labels maximum boost without displaying the uint64 sentinel and safely previews unfinished inputs", () => { + expect(formatBidBoost(MAX_BUILDER_BOOST)).to.equal("MAX · Prefer builders"); + expect(formatBidBoost("0")).to.equal("0% · Prefer local"); + expect(formatBidBoost("100")).to.equal("100% · Equal weight"); + expect(formatBidBoost("150")).to.equal("150%"); + for (const value of ["", "1.5", "abc", "18446744073709551616"]) { + expect(formatBidBoost(value)).to.include("Enter a valid"); + expect(formatBidAmount(value)).to.include("Enter a valid"); + } + expect(formatBidAmount("9007199254740993")).to.equal(`${9007199254740993n.toLocaleString()} Gwei`); + }); + + it("shows saved inheritance alongside effective numbers without freezing defaults", () => { + const saved: BuilderConfig = { min_bid: "25", builders: [{ url }] }; + const original = structuredClone(saved); + const resolved: BuilderConfig = { + min_bid: "25", + builder_boost_factor: "100", + builders: [{ url, min_bid: "25", builder_boost_factor: "100", max_execution_payment: "0" }] + }; + const comparison = builderConfigComparison(saved, resolved); + expect(comparison.rows[1]).to.deep.equal({ + label: "Bid boost", + saved: "Client default", + effective: "100% · Equal weight" + }); + expect(comparison.builders[0].rows[0]).to.include({ saved: "Use global minimum bid", effective: "25 Gwei" }); + expect(comparison.builders[0].rows[1]).to.include({ + saved: "Use global bid boost", + effective: "100% · Equal weight" + }); + expect(saved).to.deep.equal(original); + }); + + it("matches reordered builder entries by exact URL and effective authorization bytes", () => { + const saved: BuilderConfig = { + builders: [ + { url, min_bid: "1" }, + { url, auth_data: "0xaB", min_bid: "2" } + ] + }; + const resolved: BuilderConfig = { + builders: [ + { url, auth_data: "0xAb", min_bid: "2" }, + { url, auth_data: defaultBuilderAuthData(url).toUpperCase().replace("0X", "0x"), min_bid: "1" } + ] + }; + const comparison = builderConfigComparison(saved, resolved); + expect(comparison.builders.map((entry) => entry.rows[0].effective)).to.deep.equal(["1 Gwei", "2 Gwei"]); + expect(comparison.builders.map((entry) => entry.source)).to.deep.equal([undefined, undefined]); + expect(comparison.builders[0].rows[3]).to.include({ saved: "Builder hostname", effective: "Builder hostname" }); + }); + + it("distinguishes reset/defaults, no builders, stale client lists, and unavailable GET values", () => { + const resolved = { builders: [{ url }] }; + for (const saved of [undefined, null, {}]) { + const comparison = builderConfigComparison(saved, resolved); + expect(comparison.rows[2].saved).to.equal("Client’s builder list"); + expect(comparison.builders[0].source).to.equal("Client default"); + } + const empty = builderConfigComparison({ builders: [] }, resolved); + expect(empty.rows[2]).to.include({ saved: "0 configured", effective: "1 configured" }); + expect(empty.builders[0].source).to.equal("Client only"); + expect(empty.builders[0].rows[0].saved).to.equal("Not in saved list"); + const missing = builderConfigComparison({ builders: [{ url }] }, { builders: [] }); + expect(missing.builders[0].source).to.equal("Saved only"); + expect(missing.builders[0].rows[0].effective).to.equal("Not on client"); + const unavailable = builderConfigComparison({ builders: [{ url }] }); + expect(unavailable.rows[2].effective).to.equal("Unavailable"); + expect(unavailable.builders[0].rows[0].effective).to.equal("Unavailable"); + expect(unavailable.builders[0].source).to.equal(undefined); + }); +}); diff --git a/packages/ui/test/builderConfigForm.test.ts b/packages/ui/test/builderConfigForm.test.ts new file mode 100644 index 00000000..054a094b --- /dev/null +++ b/packages/ui/test/builderConfigForm.test.ts @@ -0,0 +1,197 @@ +import { expect } from "chai"; +import { BuilderConfig, defaultBuilderAuthData } from "@stakingbrain/common"; +import { + builderConfigToForm, + BuilderConfigForm, + builderFormErrors, + builderFormToConfig, + createBuilderInput, + matchesRelayPreset, + toggleRelayPreset, + splitBuilderPublicKeys +} from "../src/utils/builderConfigForm.js"; + +const url = "https://builder.example.com"; +const maximum = "18446744073709551615"; + +describe("Builder settings form", () => { + const preset = { id: "aestus", name: "Aestus", url: "https://hoodi.aestus.live" }; + + it("adds and removes relay presets with an explicit empty list after the last checkbox is cleared", () => { + const original = builderConfigToForm(); + const selected = toggleRelayPreset(original, preset, true); + expect(original.builders).to.deep.equal([]); + expect(builderFormToConfig(selected)).to.deep.equal({ builders: [{ url: preset.url }] }); + expect(toggleRelayPreset(selected, preset, true).builders).to.have.length(1); + expect(builderFormToConfig(toggleRelayPreset(selected, preset, false))).to.deep.equal({ builders: [] }); + }); + + it("preserves custom endpoints and all advanced overrides when another relay is selected", () => { + const config: BuilderConfig = { + min_bid: "9007199254740993", + builder_boost_factor: maximum, + builders: [{ url, auth_data: "0xab", min_bid: "0", builder_pubkeys: ["0x" + "ab".repeat(48)] }] + }; + const form = builderConfigToForm(config); + const selected = toggleRelayPreset(form, preset, true); + expect(builderFormToConfig(selected)).to.deep.equal({ + ...config, + builders: [...config.builders!, { url: preset.url }] + }); + expect(builderFormToConfig(toggleRelayPreset(selected, preset, false))).to.deep.equal(config); + expect(builderFormToConfig(form)).to.deep.equal(config); + }); + + it("recognizes saved preset URLs and retains their custom options until unchecked", () => { + const config = { builders: [{ url: preset.url + "/", auth_data: "0x01", max_execution_payment: "0" }] }; + const form = builderConfigToForm(config); + expect(matchesRelayPreset(form.builders[0], preset)).to.equal(true); + expect(builderFormToConfig(toggleRelayPreset(form, preset, true))).to.deep.equal(config); + expect(builderFormToConfig(toggleRelayPreset(form, preset, false))).to.deep.equal({ builders: [] }); + for (const other of ["https://aestus.live", preset.url + "/other", preset.url + "?other", "invalid"]) { + expect(matchesRelayPreset(createBuilderInput({ url: other }), preset)).to.equal(false); + } + }); + + it("does not restore inactive custom cards when a preset is selected", () => { + const form = builderConfigToForm({ builders: [{ url }] }); + form.buildersMode = "none"; + expect(builderFormToConfig(toggleRelayPreset(form, preset, true))).to.deep.equal({ + builders: [{ url: preset.url }] + }); + }); + + it("accepts pasted public-key lists without array syntax or numeric conversion", () => { + const first = "0x" + "ab".repeat(48); + const second = "0x" + "CD".repeat(48); + const form = builderConfigToForm({ builders: [{ url }] }); + form.builders[0].publicKeys = splitBuilderPublicKeys(` ${first},\n ${second} `); + expect(builderFormToConfig(form).builders?.[0].builder_pubkeys).to.deep.equal([first, second]); + }); + it("round-trips explicit lists and per-builder options without resolving numeric defaults", () => { + const configurations: BuilderConfig[] = [ + { builders: [] }, + { min_bid: "0", builder_boost_factor: maximum, builders: [] }, + { builders: [{ url }] }, + { + min_bid: "9007199254740993", + builders: [ + { + url, + auth_data: "0xaBcD", + builder_pubkeys: [], + max_execution_payment: "0", + min_bid: maximum, + builder_boost_factor: "0" + }, + { url, auth_data: "0x01", builder_pubkeys: ["0x" + "ab".repeat(48)], max_execution_payment: maximum } + ] + } + ]; + for (const config of configurations) { + const form = builderConfigToForm(config); + expect(builderFormErrors(form)).to.deep.equal({}); + expect(builderFormToConfig(form)).to.deep.equal(config); + } + }); + + it("saves an explicit empty list for unmanaged, inherited, and reset configurations", () => { + for (const config of [undefined, null, {}, { min_bid: "25" }]) { + const form = builderConfigToForm(config); + expect(form.buildersMode).to.equal("none"); + expect(builderFormToConfig(form)).to.deep.equal({ ...config, builders: [] }); + } + }); + + it("switches between explicit empty lists and custom builders without changing hidden cards", () => { + const form = builderConfigToForm({ builders: [{ url }] }); + form.buildersMode = "none"; + expect(builderFormToConfig(form)).to.deep.equal({ builders: [] }); + form.buildersMode = "custom"; + expect(builderFormToConfig(form)).to.deep.equal({ builders: [{ url }] }); + }); + + it("keeps explicit zero and full uint64 precision when a default is disabled or restored", () => { + const form = builderConfigToForm(); + form.minBid = { enabled: true, value: "9007199254740993" }; + form.boost = { enabled: true, value: "0" }; + expect(builderFormToConfig(form)).to.deep.equal({ + min_bid: "9007199254740993", + builder_boost_factor: "0", + builders: [] + }); + form.minBid.enabled = false; + expect(builderFormToConfig(form)).to.deep.equal({ builder_boost_factor: "0", builders: [] }); + form.boost = { enabled: true, value: maximum }; + expect(builderFormToConfig(form).builder_boost_factor).to.equal(maximum); + }); + + it("creates unique builder ids without crypto.randomUUID in non-secure contexts", () => { + // DAppNode serves the UI over http://my.dappnode, where crypto.randomUUID is unavailable + const original = globalThis.crypto.randomUUID; + Object.defineProperty(globalThis.crypto, "randomUUID", { value: undefined, configurable: true }); + try { + const ids = Array.from({ length: 100 }, () => createBuilderInput().id); + expect(new Set(ids).size).to.equal(ids.length); + expect(builderFormErrors(builderConfigToForm())).to.deep.equal({}); + } finally { + Object.defineProperty(globalThis.crypto, "randomUUID", { value: original, configurable: true }); + } + }); + + it("retains omitted authorization and public keys until the user explicitly configures them", () => { + const form = builderConfigToForm({ builders: [{ url }] }); + const entry = form.builders[0]; + expect(builderFormToConfig(form).builders?.[0]).not.to.have.property("auth_data"); + expect(builderFormToConfig(form).builders?.[0]).not.to.have.property("builder_pubkeys"); + entry.authData = { enabled: true, value: "0xab" }; + entry.publicKeys = []; + expect(builderFormToConfig(form).builders?.[0]).to.deep.equal({ url, auth_data: "0xab", builder_pubkeys: [] }); + entry.authData.enabled = false; + expect(builderFormToConfig(form).builders?.[0]).not.to.have.property("auth_data"); + }); + + it("associates all errors with their visible fields, including advanced public-key inputs", () => { + const entry = createBuilderInput(); + entry.authData = { enabled: true, value: "0x1" }; + entry.maxPayment = { enabled: true, value: "18446744073709551616" }; + entry.publicKeys = ["not-a-public-key"]; + const form: BuilderConfigForm = { + ...builderConfigToForm(), + minBid: { enabled: true, value: "1.5" }, + boost: { enabled: true, value: "" }, + buildersMode: "custom", + builders: [entry] + }; + expect(Object.keys(builderFormErrors(form))).to.have.members([ + "minBid", + "boost", + `builders.${entry.id}.url`, + `builders.${entry.id}.authData`, + `builders.${entry.id}.maxPayment`, + `builders.${entry.id}.publicKeys.0` + ]); + form.buildersMode = "none"; + expect(Object.keys(builderFormErrors(form))).to.have.members(["minBid", "boost"]); + }); + + it("identifies duplicate builder cards using effective authorization bytes", () => { + const form = builderConfigToForm({ builders: [{ url }, { url, auth_data: defaultBuilderAuthData(url) }] }); + expect(builderFormErrors(form)[`builders.${form.builders[1].id}.url`]).to.include("Another builder"); + expect(() => builderFormToConfig(form)).to.throw("duplicates"); + form.builders[1].authData = { enabled: true, value: "0x01" }; + expect(builderFormErrors(form)).to.deep.equal({}); + }); + + it("enforces array limits and does not modify the original saved arrays during edits", () => { + const config: BuilderConfig = { builders: [{ url, builder_pubkeys: ["0x" + "ab".repeat(48)] }] }; + const form = builderConfigToForm(config); + form.builders[0].publicKeys?.push("0x" + "cd".repeat(48)); + expect(config.builders?.[0].builder_pubkeys).to.have.length(1); + form.builders = Array.from({ length: 65 }, (_, index) => + createBuilderInput({ url: `https://builder-${index}.example` }) + ); + expect(builderFormErrors(form).builders).to.include("64"); + expect(() => builderFormToConfig(form)).to.throw("64"); + }); +}); diff --git a/packages/ui/test/gridSelection.test.ts b/packages/ui/test/gridSelection.test.ts new file mode 100644 index 00000000..20713cf1 --- /dev/null +++ b/packages/ui/test/gridSelection.test.ts @@ -0,0 +1,17 @@ +import { expect } from "chai"; +import { selectedGridRowIds } from "../src/utils/dataUtils.js"; + +describe("Bulk validator selection", () => { + const rows = [{ id: 0 }, { id: 1 }, { id: 2 }]; + it("uses only existing selected IDs for individual actions", () => { + expect(selectedGridRowIds({ type: "include", ids: new Set([1, 99]) }, rows)).to.deep.equal([1]); + }); + it("expands select-all exclusions to explicit IDs for bulk actions", () => { + expect(selectedGridRowIds({ type: "exclude", ids: new Set([1]) }, rows)).to.deep.equal([0, 2]); + expect(selectedGridRowIds({ type: "exclude", ids: new Set() }, rows)).to.deep.equal([0, 1, 2]); + }); + it("clears bulk actions when the selection or validator list is empty", () => { + expect(selectedGridRowIds({ type: "include", ids: new Set() }, rows)).to.deep.equal([]); + expect(selectedGridRowIds({ type: "exclude", ids: new Set() }, [])).to.deep.equal([]); + }); +}); diff --git a/packages/ui/test/importForm.test.ts b/packages/ui/test/importForm.test.ts new file mode 100644 index 00000000..b502a21f --- /dev/null +++ b/packages/ui/test/importForm.test.ts @@ -0,0 +1,69 @@ +import { expect } from "chai"; +import { appendImportFiles, isImportFormValid, ImportFormState } from "../src/utils/importForm.js"; +import { createImportRequest } from "../src/utils/createImportRequest.js"; +import { extractPubkey } from "../src/utils/dataUtils.js"; + +const recipient = "0x" + "ab".repeat(20); +const files = ["11", "22", "33"].map((byte) => { + const pubkey = "0x" + byte.repeat(48); + return { pubkey, file: new File([JSON.stringify({ pubkey: pubkey.slice(2) })], `${byte}.json`) }; +}); +const shared = { password: true, tag: true, feeRecipient: true }; +const initial = (): ImportFormState => ({ + acceptedFiles: [files[0]], + passwords: ["password"], + tags: ["solo"], + feeRecipients: [recipient] +}); + +describe("Bulk import form", () => { + it("inherits shared settings for later file drops and imports every selected key", async () => { + const state = appendImportFiles(appendImportFiles(initial(), [files[1]], shared), [files[2]], shared); + const request = await createImportRequest(state); + expect( + request.validatorsImportRequest.map(({ password, tag, feeRecipient }) => ({ password, tag, feeRecipient })) + ).to.deep.equal(files.map(() => ({ password: "password", tag: "solo", feeRecipient: recipient }))); + expect(request.validatorsImportRequest.map(({ keystore }) => JSON.parse(keystore).pubkey)).to.deep.equal( + files.map(({ pubkey }) => pubkey.slice(2)) + ); + }); + + it("deduplicates keys within a drop and against previous drops", () => { + const state = appendImportFiles(initial(), [files[0], files[1], files[1]], shared); + expect(state.acceptedFiles.map(({ pubkey }) => pubkey)).to.deep.equal( + files.slice(0, 2).map(({ pubkey }) => pubkey) + ); + expect(state.passwords).to.have.length(2); + expect(state.tags).to.have.length(2); + expect(state.feeRecipients).to.have.length(2); + }); + + it("requires individual settings for newly added files when shared switches are off", () => { + const state = appendImportFiles(initial(), [files[1]], { password: false, tag: false, feeRecipient: false }); + expect(isImportFormValid(state)).to.equal(false); + expect(state.passwords).to.deep.equal(["password", ""]); + expect(state.tags).to.deep.equal(["solo", ""]); + }); + + it("does not allow sparse or shortened settings arrays to bypass validation", () => { + for (const field of ["passwords", "tags", "feeRecipients"] as const) { + const state = appendImportFiles(initial(), [files[1]], shared); + delete state[field][0]; + expect(isImportFormValid(state), field).to.equal(false); + state[field] = []; + expect(isImportFormValid(state), field).to.equal(false); + } + expect(isImportFormValid({ ...initial(), feeRecipients: ["0x" + "00".repeat(20)] })).to.equal(false); + }); + + it("rejects invalid keystores and normalizes valid public keys", async () => { + const key = "ab".repeat(48); + expect(await extractPubkey(new File([JSON.stringify({ pubkey: key.toUpperCase() })], "key.json"))).to.equal( + "0x" + key + ); + for (const contents of ["{", "null", "{}", '{"pubkey":123}', '{"pubkey":"abc"}']) { + const result = await extractPubkey(new File([contents], "invalid.json")).catch((error: Error) => error); + expect(result).to.be.instanceOf(Error); + } + }); +}); diff --git a/packages/ui/test/rpcClient.test.ts b/packages/ui/test/rpcClient.test.ts new file mode 100644 index 00000000..f825dd86 --- /dev/null +++ b/packages/ui/test/rpcClient.test.ts @@ -0,0 +1,195 @@ +import { expect } from "chai"; +import sinon from "sinon"; +import { Manager, Socket } from "socket.io-client"; +import { RPC_MAX_PAYLOAD_BYTES, RPC_TIMEOUT_MS, validateBuilderConfig } from "@stakingbrain/common"; +import { RpcClient } from "../src/socket/rpcClient.js"; +import { createImportRequest } from "../src/utils/createImportRequest.js"; + +class TestManager extends Manager { + connectSocket(): void { + this.emitReserved("packet", { type: 0, nsp: "/", data: { sid: "test" } }); + } + + acknowledge(id: number, response: unknown): void { + this.emitReserved("packet", { type: 3, nsp: "/", id, data: [response] }); + } + + closeTransport(): void { + this.emitReserved("close", "transport close"); + } +} + +describe("RPC request lifecycle", () => { + let clock: sinon.SinonFakeTimers; + let manager: TestManager; + let socket: Socket; + let client: RpcClient; + let packets: sinon.SinonStub; + const connect = () => manager.connectSocket(); + const acknowledge = (response: unknown) => manager.acknowledge(packets.lastCall.args[0].id, response); + + beforeEach(() => { + clock = sinon.useFakeTimers(); + manager = new TestManager("http://unused", { autoConnect: false }); + // Exercise the real Socket.IO acknowledgement lifecycle without opening a network connection. + sinon.stub(manager, "open").returns(manager); + packets = sinon.stub(manager, "_packet"); + socket = manager.socket("/"); + socket.connect(); + sinon.stub(console, "log"); + client = new RpcClient(socket); + }); + afterEach(() => { + socket.disconnect(); + sinon.restore(); + }); + + it("settles successful requests and clears the acknowledgement timer", async () => { + connect(); + const pending = client.call("updateValidators", [{ pubkey: "0x" + "11".repeat(48), builderConfig: {} }]); + acknowledge({ result: null }); + expect(await pending).to.equal(null); + expect(clock.countTimers()).to.equal(0); + }); + + it("preserves server errors", async () => { + connect(); + const pending = client.call("getValidators", undefined).catch((error: Error) => error); + acknowledge({ error: { code: -32000, message: "Settings saved; client unavailable" } }); + expect(await pending).to.have.property("message", "Settings saved; client unavailable"); + expect(clock.countTimers()).to.equal(0); + }); + + it("rejects a sent request immediately when the transport disconnects", async () => { + connect(); + const pending = client.call("getValidators", undefined).catch((error: Error) => error); + manager.closeTransport(); + expect(await pending) + .to.have.property("message") + .that.includes("Connection lost"); + expect(clock.countTimers()).to.equal(0); + }); + + it("times out queued requests and prevents them from being replayed after reconnection", async () => { + const pending = client.call("getValidators", undefined).catch((error: Error) => error); + expect(socket.sendBuffer).to.have.length(1); + await clock.tickAsync(RPC_TIMEOUT_MS); + expect(await pending) + .to.have.property("message") + .that.includes("timed out"); + expect(socket.sendBuffer).to.have.length(0); + connect(); + expect(packets.called).to.equal(false); + }); + + const largeConfig = { + builders: Array.from({ length: 64 }, (_, index) => ({ + url: `https://builder-${index}.example`, + auth_data: "0x" + "ab".repeat(4096) + })) + }; + + it("sends valid bulk settings larger than the previous one-megabyte limit", async () => { + validateBuilderConfig(largeConfig); + connect(); + const pending = client.call( + "updateValidators", + ["11", "22"].map((byte) => ({ + pubkey: "0x" + byte.repeat(48), + builderConfig: largeConfig + })) + ); + const bytes = new TextEncoder().encode(JSON.stringify(packets.lastCall.args[0])).byteLength; + expect(bytes).to.be.greaterThan(1e6).and.lessThan(RPC_MAX_PAYLOAD_BYTES); + acknowledge({ result: null }); + expect(await pending).to.equal(null); + }); + + it("rejects requests exceeding the server limit before sending them", async () => { + connect(); + const error = await client + .call( + "updateValidators", + Array.from({ length: 17 }, (_, index) => ({ + pubkey: "0x" + index.toString(16).padStart(96, "0"), + builderConfig: largeConfig + })) + ) + .catch((error: Error) => error); + expect(error).to.have.property("message").that.includes("too large"); + expect(packets.called).to.equal(false); + expect(clock.countTimers()).to.equal(0); + }); + + for (const count of [1, 10, 100]) { + it(`sends ${count} keystores and slashing protection as one text packet`, async () => { + const keystores = Array.from({ length: count }, (_, index) => + JSON.stringify({ pubkey: index.toString(16).padStart(96, "0"), description: "Validator 🔑" }) + ); + const slashingProtection = JSON.stringify({ metadata: { interchange_format_version: "5" }, data: [] }); + const request = await createImportRequest({ + acceptedFiles: keystores.map((keystore, index) => ({ + file: new File([keystore], `keystore-${index}.json`), + pubkey: JSON.parse(keystore).pubkey + })), + passwords: keystores.map((_, index) => `password-${index}`), + tags: keystores.map(() => "solo"), + feeRecipients: keystores.map((_, index) => "0x" + (index + 1).toString(16).padStart(40, "0")), + slashingFile: new File([slashingProtection], "slashing-protection.json") + }); + connect(); + // Exercise Socket.IO's real encoder, capturing writes instead of opening a transport. + const write = sinon.stub(); + manager.engine = { + write, + close: sinon.stub(), + _hasPingExpired: () => false + } as unknown as Manager["engine"]; + packets.callsFake((packet) => Manager.prototype._packet.call(manager, packet)); + + const pending = client.call("importValidators", request); + expect(write.calledOnce).to.equal(true); + const encoded = write.firstCall.args[0] as string; + expect(encoded.startsWith("2")).to.equal(true); // EVENT, rather than BINARY_EVENT (5). + const [event, received] = JSON.parse(encoded.slice(encoded.indexOf("["))); + expect(event).to.equal("rpc"); + expect(received.params.importFrom).to.equal("ui"); + expect(received.params.slashing_protection).to.equal(slashingProtection); + expect(received.params.validatorsImportRequest).to.deep.equal( + keystores.map((keystore, index) => ({ + keystore, + password: `password-${index}`, + tag: "solo", + feeRecipient: "0x" + (index + 1).toString(16).padStart(40, "0") + })) + ); + const response = { data: keystores.map(() => ({ status: "imported" })) }; + acknowledge({ result: response }); + expect(await pending).to.deep.equal(response); + expect(clock.countTimers()).to.equal(0); + }); + } + + it("counts file contents toward the payload limit even without slashing protection", async () => { + const request = await createImportRequest({ + acceptedFiles: [ + { + file: new File( + [JSON.stringify({ pubkey: "11".repeat(48), padding: "x".repeat(RPC_MAX_PAYLOAD_BYTES) })], + "key.json" + ), + pubkey: "11".repeat(48) + } + ], + passwords: ["password"], + tags: ["solo"], + feeRecipients: ["0x" + "22".repeat(20)] + }); + expect(request.slashing_protection).to.equal(undefined); + connect(); + const error = await client.call("importValidators", request).catch((error: Error) => error); + expect(error).to.have.property("message").that.includes("too large"); + expect(packets.called).to.equal(false); + expect(clock.countTimers()).to.equal(0); + }); +}); diff --git a/packages/ui/test/testBuildSummaryUrl.test.ts b/packages/ui/test/testBuildSummaryUrl.test.ts index 354225ee..7f23e8c0 100644 --- a/packages/ui/test/testBuildSummaryUrl.test.ts +++ b/packages/ui/test/testBuildSummaryUrl.test.ts @@ -20,7 +20,7 @@ describe("Test the building of a unique URL for all the validators", () => { } for (let i = 1; i <= 3; i++) { - allValidatorsInfo[0].data.push({ validatorindex: i + 100 }); + allValidatorsInfo[1].data.push({ validatorindex: i + 100 }); } const network = "mainnet"; diff --git a/packages/ui/tsconfig.json b/packages/ui/tsconfig.json index 6ea7b85f..d2184c01 100644 --- a/packages/ui/tsconfig.json +++ b/packages/ui/tsconfig.json @@ -4,6 +4,7 @@ "exclude": ["./test/", "./node_modules/", "./dist/"], "compilerOptions": { "outDir": "dist", + "tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo", "target": "ESNext", "lib": ["dom", "dom.iterable", "esnext"], "types": ["vite/client", "node"], @@ -15,7 +16,7 @@ "forceConsistentCasingInFileNames": true, "noFallthroughCasesInSwitch": true, "module": "esnext", - "moduleResolution": "node", + "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, diff --git a/packages/ui/tsconfig.test.json b/packages/ui/tsconfig.test.json index 0cdb9a18..d992a247 100644 --- a/packages/ui/tsconfig.test.json +++ b/packages/ui/tsconfig.test.json @@ -1,7 +1,7 @@ { "extends": "../../tsconfig.json", "compilerOptions": { - "rootDir": "./test" // Root directory of input files + "rootDir": "." // Root directory of input files }, "include": ["test"], "exclude": ["node_modules", "dist"] diff --git a/packages/ui/vite.config.ts b/packages/ui/vite.config.ts index 3b438758..f6886485 100644 --- a/packages/ui/vite.config.ts +++ b/packages/ui/vite.config.ts @@ -1,6 +1,5 @@ import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; -import viteTsconfigPaths from "vite-tsconfig-paths"; export default defineConfig({ // depending on your application, base can also be "/" @@ -8,7 +7,8 @@ export default defineConfig({ outDir: "build" }, base: "/", - plugins: [react(), viteTsconfigPaths()], + plugins: [react()], + resolve: { tsconfigPaths: true }, server: { // this ensures that the browser opens upon server start //open: false, diff --git a/tsconfig.json b/tsconfig.json index c6822502..07dee6c1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,11 +1,11 @@ { "compilerOptions": { "target": "ES2023", // Use the latest stable ECMAScript target - "module": "ES2022", // Use the latest stable ECMAScript module - "lib": ["es2020", "esnext.bigint", "es2020.string", "es2020.symbol.wellknown", "dom"], + "module": "NodeNext", // Use the latest stable ECMAScript module + "lib": ["es2023", "dom"], "types": ["node", "mocha", "chai"], - "moduleResolution": "node", + "moduleResolution": "NodeNext", "pretty": true, "strict": true, diff --git a/yarn.lock b/yarn.lock index 268dd957..afe3f120 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,8727 +2,3806 @@ # Manual changes might be lost - proceed with caution! __metadata: - version: 8 + version: 10 cacheKey: 10c0 -"@ampproject/remapping@npm:^2.2.0": - version: 2.2.0 - resolution: "@ampproject/remapping@npm:2.2.0" +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/code-frame@npm:7.29.7" dependencies: - "@jridgewell/gen-mapping": "npm:^0.1.0" - "@jridgewell/trace-mapping": "npm:^0.3.9" - checksum: 10c0/d267d8def81d75976bed4f1f81418a234a75338963ed0b8565342ef3918b07e9043806eb3a1736df7ac0774edb98e2890f880bba42817f800495e4ae3fac995e + "@babel/helper-validator-identifier": "npm:^7.29.7" + js-tokens: "npm:^4.0.0" + picocolors: "npm:^1.1.1" + checksum: 10c0/169fc2080169a40c1760155eaaaf739bcb882df0bec76a83adbda5493645bc17270a3434b8848c494b1933e96fe1d147370001e3cda09a39f43ae30f08ef2069 languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/code-frame@npm:7.18.6" +"@babel/generator@npm:^7.29.8": + version: 7.29.8 + resolution: "@babel/generator@npm:7.29.8" dependencies: - "@babel/highlight": "npm:^7.18.6" - checksum: 10c0/e3966f2717b7ebd9610524730e10b75ee74154f62617e5e115c97dbbbabc5351845c9aa850788012cb4d9aee85c3dc59fe6bef36690f244e8dcfca34bd35e9c9 + "@babel/parser": "npm:^7.29.8" + "@babel/types": "npm:^7.29.8" + "@jridgewell/gen-mapping": "npm:^0.3.12" + "@jridgewell/trace-mapping": "npm:^0.3.28" + jsesc: "npm:^3.0.2" + checksum: 10c0/7b896696314a659652393b76d78276e236acd0f7fae40a9a1af7f01c76aeafc630dd0966aad6f9386d35d7c674a8e6e2d8e217c44d25fb11460e68afa9ba8441 languageName: node linkType: hard -"@babel/code-frame@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/code-frame@npm:7.24.7" - dependencies: - "@babel/highlight": "npm:^7.24.7" - picocolors: "npm:^1.0.0" - checksum: 10c0/ab0af539473a9f5aeaac7047e377cb4f4edd255a81d84a76058595f8540784cc3fbe8acf73f1e073981104562490aabfb23008cd66dc677a456a4ed5390fdde6 +"@babel/helper-globals@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-globals@npm:7.29.7" + checksum: 10c0/f38417c40b1129a1b2b519ca961b9040c8827d1444fd74068702286b91b77089431dc76b6b9d5c1496e5da2a4f3ad329c6946e688ba3fa0d1d0b3d2b4f34f36a languageName: node linkType: hard -"@babel/compat-data@npm:^7.20.5": - version: 7.21.0 - resolution: "@babel/compat-data@npm:7.21.0" - checksum: 10c0/69c8ddf229d44dc095ec5282d322fb8ad6ad0565d919c0501dbf474e825722f5d6842268b3f434db8f8463e971d155d83fe02884880f26846ef870f58a935743 +"@babel/helper-module-imports@npm:^7.16.7": + version: 7.29.7 + resolution: "@babel/helper-module-imports@npm:7.29.7" + dependencies: + "@babel/traverse": "npm:^7.29.7" + "@babel/types": "npm:^7.29.7" + checksum: 10c0/6adf60d97356027413342a092f818d9678c4f5caff716a33e3284b5ae14e47a9e88059d421dde4ee4894691260039a12602c0e7becadc175602194b40dfa345d languageName: node linkType: hard -"@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.25.2, @babel/compat-data@npm:^7.25.4": - version: 7.25.4 - resolution: "@babel/compat-data@npm:7.25.4" - checksum: 10c0/50d79734d584a28c69d6f5b99adfaa064d0f41609a378aef04eb06accc5b44f8520e68549eba3a082478180957b7d5783f1bfb1672e4ae8574e797ce8bae79fa +"@babel/helper-string-parser@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-string-parser@npm:7.29.7" + checksum: 10c0/194bc0f1716e396d5ffde56ad6119745fb9557662c98611590e5e454906783a4ccb21ce93056b8eb69a4909044834e45d96e50ac695bbe9e3221648fe033c06c languageName: node linkType: hard -"@babel/core@npm:^7.0.0": - version: 7.21.0 - resolution: "@babel/core@npm:7.21.0" - dependencies: - "@ampproject/remapping": "npm:^2.2.0" - "@babel/code-frame": "npm:^7.18.6" - "@babel/generator": "npm:^7.21.0" - "@babel/helper-compilation-targets": "npm:^7.20.7" - "@babel/helper-module-transforms": "npm:^7.21.0" - "@babel/helpers": "npm:^7.21.0" - "@babel/parser": "npm:^7.21.0" - "@babel/template": "npm:^7.20.7" - "@babel/traverse": "npm:^7.21.0" - "@babel/types": "npm:^7.21.0" - convert-source-map: "npm:^1.7.0" - debug: "npm:^4.1.0" - gensync: "npm:^1.0.0-beta.2" - json5: "npm:^2.2.2" - semver: "npm:^6.3.0" - checksum: 10c0/90f986b3ca91382a652776f40d970002cfd67df3e082a4b21e1aeaeb5ac2b6abe8ca829e197b1fc35a0483586ad44ff6ace3f68748a3bd51937df0f3aec38ec2 +"@babel/helper-validator-identifier@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-validator-identifier@npm:7.29.7" + checksum: 10c0/4795354e7ae0dcafa72de1cd04ec51252dc1498517170beaf019e03effc5b7bf13c6b21a3949a77e07b8125be7f106ed1131350d8ebd4566ae874094a726d62b languageName: node linkType: hard -"@babel/core@npm:^7.16.0, @babel/core@npm:^7.24.5": - version: 7.25.2 - resolution: "@babel/core@npm:7.25.2" +"@babel/parser@npm:^7.29.7, @babel/parser@npm:^7.29.8, @babel/parser@npm:^7.29.9": + version: 7.29.9 + resolution: "@babel/parser@npm:7.29.9" dependencies: - "@ampproject/remapping": "npm:^2.2.0" - "@babel/code-frame": "npm:^7.24.7" - "@babel/generator": "npm:^7.25.0" - "@babel/helper-compilation-targets": "npm:^7.25.2" - "@babel/helper-module-transforms": "npm:^7.25.2" - "@babel/helpers": "npm:^7.25.0" - "@babel/parser": "npm:^7.25.0" - "@babel/template": "npm:^7.25.0" - "@babel/traverse": "npm:^7.25.2" - "@babel/types": "npm:^7.25.2" - convert-source-map: "npm:^2.0.0" - debug: "npm:^4.1.0" - gensync: "npm:^1.0.0-beta.2" - json5: "npm:^2.2.3" - semver: "npm:^6.3.1" - checksum: 10c0/a425fa40e73cb72b6464063a57c478bc2de9dbcc19c280f1b55a3d88b35d572e87e8594e7d7b4880331addb6faef641bbeb701b91b41b8806cd4deae5d74f401 + "@babel/types": "npm:^7.29.8" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/171b16eb4527cdbbb72cb98710f91acb22356e2c40196ba3b7a1b84efa8c55d0b0992f399c8c675c3e17f67fd133676c5c4118e5f20db243f427c8b29b0c3f40 languageName: node linkType: hard -"@babel/eslint-parser@npm:^7.16.3": - version: 7.25.1 - resolution: "@babel/eslint-parser@npm:7.25.1" - dependencies: - "@nicolo-ribaudo/eslint-scope-5-internals": "npm:5.1.1-v1" - eslint-visitor-keys: "npm:^2.1.0" - semver: "npm:^6.3.1" - peerDependencies: - "@babel/core": ^7.11.0 - eslint: ^7.5.0 || ^8.0.0 || ^9.0.0 - checksum: 10c0/9f98351b32edfced9e6308a80ad69af1210d9c9780f19339cb286d0c9be0a9afac80d1df3b3793112e720675ce5b927920b19454d0f48ddf8370d08ab62d0dc2 +"@babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.28.4, @babel/runtime@npm:^7.28.6, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.7": + version: 7.29.10 + resolution: "@babel/runtime@npm:7.29.10" + checksum: 10c0/32f975da8e9c2cae6b33e1a477f8fe8bbff6722c982b5ce35f99affaf3a7a1b612c8272b43afa0e51d3000928d6f3ba38f9296301115db5640264907c027b0cd languageName: node linkType: hard -"@babel/generator@npm:^7.21.0": - version: 7.21.1 - resolution: "@babel/generator@npm:7.21.1" +"@babel/template@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/template@npm:7.29.7" dependencies: - "@babel/types": "npm:^7.21.0" - "@jridgewell/gen-mapping": "npm:^0.3.2" - "@jridgewell/trace-mapping": "npm:^0.3.17" - jsesc: "npm:^2.5.1" - checksum: 10c0/833d115009cd03fc7c2668ab9946607297d1283f3a1c6dcef7edbc76f261a65aee126fbfd720a23031a7557f6d0e305362832ee485096f9f50d9a00fad6e0921 + "@babel/code-frame": "npm:^7.29.7" + "@babel/parser": "npm:^7.29.7" + "@babel/types": "npm:^7.29.7" + checksum: 10c0/8bb7f900dcab0e9e1c5ffbc33ca10e0d26b7b2e2ca804becb73ee771b9c4ed6e2908a4ae4a14c08560febb45d2b6b9a173955e42ad404d05f8b04840a14d9c58 languageName: node linkType: hard -"@babel/generator@npm:^7.25.0, @babel/generator@npm:^7.25.6": - version: 7.25.6 - resolution: "@babel/generator@npm:7.25.6" +"@babel/traverse@npm:^7.29.7": + version: 7.29.10 + resolution: "@babel/traverse@npm:7.29.10" dependencies: - "@babel/types": "npm:^7.25.6" - "@jridgewell/gen-mapping": "npm:^0.3.5" - "@jridgewell/trace-mapping": "npm:^0.3.25" - jsesc: "npm:^2.5.1" - checksum: 10c0/f89282cce4ddc63654470b98086994d219407d025497f483eb03ba102086e11e2b685b27122f6ff2e1d93b5b5fa0c3a6b7e974fbf2e4a75b685041a746a4291e + "@babel/code-frame": "npm:^7.29.7" + "@babel/generator": "npm:^7.29.8" + "@babel/helper-globals": "npm:^7.29.7" + "@babel/parser": "npm:^7.29.9" + "@babel/template": "npm:^7.29.7" + "@babel/types": "npm:^7.29.8" + debug: "npm:^4.3.1" + checksum: 10c0/dc97fc5ac9778ba3fa7df2dcff0811bca4466173ad710d9be94ffd82c9944cfa962df33d144fd59b6256fc7ab895aad30d830aade8a8a96327c8f49fbb487fda languageName: node linkType: hard -"@babel/helper-annotate-as-pure@npm:^7.18.6, @babel/helper-annotate-as-pure@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-annotate-as-pure@npm:7.24.7" +"@babel/types@npm:^7.29.7, @babel/types@npm:^7.29.8": + version: 7.29.8 + resolution: "@babel/types@npm:7.29.8" dependencies: - "@babel/types": "npm:^7.24.7" - checksum: 10c0/4679f7df4dffd5b3e26083ae65228116c3da34c3fff2c11ae11b259a61baec440f51e30fd236f7a0435b9d471acd93d0bc5a95df8213cbf02b1e083503d81b9a + "@babel/helper-string-parser": "npm:^7.29.7" + "@babel/helper-validator-identifier": "npm:^7.29.7" + checksum: 10c0/be7c279f0abf2a086c633e21b49c7ca80275d05283cc5a268b67a708c9914bd0c944f1422b3eb3cb37682a2af5d560abf520ccf9b01b53ecbfe6b71fbc3fdde6 languageName: node linkType: hard -"@babel/helper-annotate-as-pure@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/helper-annotate-as-pure@npm:7.22.5" +"@cacheable/memory@npm:^2.2.0": + version: 2.2.0 + resolution: "@cacheable/memory@npm:2.2.0" dependencies: - "@babel/types": "npm:^7.22.5" - checksum: 10c0/5a80dc364ddda26b334bbbc0f6426cab647381555ef7d0cd32eb284e35b867c012ce6ce7d52a64672ed71383099c99d32765b3d260626527bb0e3470b0f58e45 + "@cacheable/utils": "npm:^2.5.0" + "@keyv/bigmap": "npm:^1.3.1" + hookified: "npm:^1.15.1" + keyv: "npm:^5.6.0" + checksum: 10c0/c74889eb09871be0696065778fff013a498ddc73b538d113c2cb6ece361746f2959fbddfa4ce03ce53c9259998be05949e393212e72413dbd07616744eabd261 languageName: node linkType: hard -"@babel/helper-builder-binary-assignment-operator-visitor@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-builder-binary-assignment-operator-visitor@npm:7.24.7" +"@cacheable/utils@npm:^2.5.0": + version: 2.5.0 + resolution: "@cacheable/utils@npm:2.5.0" dependencies: - "@babel/traverse": "npm:^7.24.7" - "@babel/types": "npm:^7.24.7" - checksum: 10c0/0ed84abf848c79fb1cd4c1ddac12c771d32c1904d87fc3087f33cfdeb0c2e0db4e7892b74b407d9d8d0c000044f3645a7391a781f788da8410c290bb123a1f13 + hashery: "npm:^1.5.1" + keyv: "npm:^5.6.0" + checksum: 10c0/99e901accd71eeb0bce91484d6c66d7ef2c731b541561eb5dc9ea7fed78bdc0b65e5dd2a80edde50191143bcfc5f238fc4742475b32e82d43b43eea224038e35 languageName: node linkType: hard -"@babel/helper-compilation-targets@npm:^7.20.7": - version: 7.20.7 - resolution: "@babel/helper-compilation-targets@npm:7.20.7" - dependencies: - "@babel/compat-data": "npm:^7.20.5" - "@babel/helper-validator-option": "npm:^7.18.6" - browserslist: "npm:^4.21.3" - lru-cache: "npm:^5.1.1" - semver: "npm:^6.3.0" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10c0/68c3e12e04c8f26c82a1aabb8003610b818d4171e0b885d1ca87c700acd7f0c50a7f4f1d3c0044947e327cb5670294b55c666d09109144b3b01021c587401e4c +"@dual-bundle/import-meta-resolve@npm:^4.2.1": + version: 4.2.1 + resolution: "@dual-bundle/import-meta-resolve@npm:4.2.1" + checksum: 10c0/8f1e572c14c4d20ea35734635085213abd13bd440c251309cf8ae5ed1082f6759cefa1c2c52a631f76859c57e26062f78a8cee4acc239c0edc87cd316a5d3b5b languageName: node linkType: hard -"@babel/helper-compilation-targets@npm:^7.22.6, @babel/helper-compilation-targets@npm:^7.24.7, @babel/helper-compilation-targets@npm:^7.24.8, @babel/helper-compilation-targets@npm:^7.25.2": - version: 7.25.2 - resolution: "@babel/helper-compilation-targets@npm:7.25.2" +"@emotion/babel-plugin@npm:^11.13.5": + version: 11.13.5 + resolution: "@emotion/babel-plugin@npm:11.13.5" dependencies: - "@babel/compat-data": "npm:^7.25.2" - "@babel/helper-validator-option": "npm:^7.24.8" - browserslist: "npm:^4.23.1" - lru-cache: "npm:^5.1.1" - semver: "npm:^6.3.1" - checksum: 10c0/de10e986b5322c9f807350467dc845ec59df9e596a5926a3b5edbb4710d8e3b8009d4396690e70b88c3844fe8ec4042d61436dd4b92d1f5f75655cf43ab07e99 + "@babel/helper-module-imports": "npm:^7.16.7" + "@babel/runtime": "npm:^7.18.3" + "@emotion/hash": "npm:^0.9.2" + "@emotion/memoize": "npm:^0.9.0" + "@emotion/serialize": "npm:^1.3.3" + babel-plugin-macros: "npm:^3.1.0" + convert-source-map: "npm:^1.5.0" + escape-string-regexp: "npm:^4.0.0" + find-root: "npm:^1.1.0" + source-map: "npm:^0.5.7" + stylis: "npm:4.2.0" + checksum: 10c0/8ccbfec7defd0e513cb8a1568fa179eac1e20c35fda18aed767f6c59ea7314363ebf2de3e9d2df66c8ad78928dc3dceeded84e6fa8059087cae5c280090aeeeb languageName: node linkType: hard -"@babel/helper-create-class-features-plugin@npm:^7.18.6, @babel/helper-create-class-features-plugin@npm:^7.21.0, @babel/helper-create-class-features-plugin@npm:^7.24.7, @babel/helper-create-class-features-plugin@npm:^7.25.0, @babel/helper-create-class-features-plugin@npm:^7.25.4": - version: 7.25.4 - resolution: "@babel/helper-create-class-features-plugin@npm:7.25.4" +"@emotion/cache@npm:^11.14.0": + version: 11.14.0 + resolution: "@emotion/cache@npm:11.14.0" dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.24.7" - "@babel/helper-member-expression-to-functions": "npm:^7.24.8" - "@babel/helper-optimise-call-expression": "npm:^7.24.7" - "@babel/helper-replace-supers": "npm:^7.25.0" - "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.24.7" - "@babel/traverse": "npm:^7.25.4" - semver: "npm:^6.3.1" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10c0/a765d9e0482e13cf96642fa8aa28e6f7d4d7d39f37840d6246e5e10a7c47f47c52d52522edd3073f229449d17ec0db6f9b7b5e398bff6bb0b4994d65957a164c + "@emotion/memoize": "npm:^0.9.0" + "@emotion/sheet": "npm:^1.4.0" + "@emotion/utils": "npm:^1.4.2" + "@emotion/weak-memoize": "npm:^0.4.0" + stylis: "npm:4.2.0" + checksum: 10c0/3fa3e7a431ab6f8a47c67132a00ac8358f428c1b6c8421d4b20de9df7c18e95eec04a5a6ff5a68908f98d3280044f247b4965ac63df8302d2c94dba718769724 languageName: node linkType: hard -"@babel/helper-create-regexp-features-plugin@npm:^7.18.6, @babel/helper-create-regexp-features-plugin@npm:^7.24.7, @babel/helper-create-regexp-features-plugin@npm:^7.25.0, @babel/helper-create-regexp-features-plugin@npm:^7.25.2": - version: 7.25.2 - resolution: "@babel/helper-create-regexp-features-plugin@npm:7.25.2" - dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.24.7" - regexpu-core: "npm:^5.3.1" - semver: "npm:^6.3.1" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10c0/85a7e3639c118856fb1113f54fb7e3bf7698171ddfd0cd6fccccd5426b3727bc1434fe7f69090441dcde327feef9de917e00d35e47ab820047057518dd675317 +"@emotion/hash@npm:^0.9.2": + version: 0.9.2 + resolution: "@emotion/hash@npm:0.9.2" + checksum: 10c0/0dc254561a3cc0a06a10bbce7f6a997883fd240c8c1928b93713f803a2e9153a257a488537012efe89dbe1246f2abfe2add62cdb3471a13d67137fcb808e81c2 languageName: node linkType: hard -"@babel/helper-define-polyfill-provider@npm:^0.6.2": - version: 0.6.2 - resolution: "@babel/helper-define-polyfill-provider@npm:0.6.2" +"@emotion/is-prop-valid@npm:^1.3.0": + version: 1.4.0 + resolution: "@emotion/is-prop-valid@npm:1.4.0" dependencies: - "@babel/helper-compilation-targets": "npm:^7.22.6" - "@babel/helper-plugin-utils": "npm:^7.22.5" - debug: "npm:^4.1.1" - lodash.debounce: "npm:^4.0.8" - resolve: "npm:^1.14.2" - peerDependencies: - "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 10c0/f777fe0ee1e467fdaaac059c39ed203bdc94ef2465fb873316e9e1acfc511a276263724b061e3b0af2f6d7ad3ff174f2bb368fde236a860e0f650fda43d7e022 + "@emotion/memoize": "npm:^0.9.0" + checksum: 10c0/5f857814ec7d8c7e727727346dfb001af6b1fb31d621a3ce9c3edf944a484d8b0d619546c30899ae3ade2f317c76390ba4394449728e9bf628312defc2c41ac3 languageName: node linkType: hard -"@babel/helper-environment-visitor@npm:^7.18.9": - version: 7.18.9 - resolution: "@babel/helper-environment-visitor@npm:7.18.9" - checksum: 10c0/a69dd50ea91d8143b899a40ca7a387fa84dbaa02e606d8692188c7c59bd4007bcd632c189f7b7dab72cb7a016e159557a6fccf7093ab9b584d87cf2ea8cf36b7 +"@emotion/memoize@npm:^0.9.0": + version: 0.9.0 + resolution: "@emotion/memoize@npm:0.9.0" + checksum: 10c0/13f474a9201c7f88b543e6ea42f55c04fb2fdc05e6c5a3108aced2f7e7aa7eda7794c56bba02985a46d8aaa914fcdde238727a98341a96e2aec750d372dadd15 languageName: node linkType: hard -"@babel/helper-function-name@npm:^7.21.0": - version: 7.21.0 - resolution: "@babel/helper-function-name@npm:7.21.0" +"@emotion/react@npm:^11.14.0": + version: 11.14.0 + resolution: "@emotion/react@npm:11.14.0" dependencies: - "@babel/template": "npm:^7.20.7" - "@babel/types": "npm:^7.21.0" - checksum: 10c0/5b4387afd34cd98a3a7f24f42250a5db6f7192a46e57bdbc151dc311b6299ceac151c5236018469af193dfb887b0b7ef8fe7ed89459cd05f00d69b3710c17498 + "@babel/runtime": "npm:^7.18.3" + "@emotion/babel-plugin": "npm:^11.13.5" + "@emotion/cache": "npm:^11.14.0" + "@emotion/serialize": "npm:^1.3.3" + "@emotion/use-insertion-effect-with-fallbacks": "npm:^1.2.0" + "@emotion/utils": "npm:^1.4.2" + "@emotion/weak-memoize": "npm:^0.4.0" + hoist-non-react-statics: "npm:^3.3.1" + peerDependencies: + react: ">=16.8.0" + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/d0864f571a9f99ec643420ef31fde09e2006d3943a6aba079980e4d5f6e9f9fecbcc54b8f617fe003c00092ff9d5241179149ffff2810cb05cf72b4620cfc031 languageName: node linkType: hard -"@babel/helper-hoist-variables@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/helper-hoist-variables@npm:7.18.6" +"@emotion/serialize@npm:^1.3.3": + version: 1.3.3 + resolution: "@emotion/serialize@npm:1.3.3" dependencies: - "@babel/types": "npm:^7.18.6" - checksum: 10c0/830aa7ca663b0d2a025513ab50a9a10adb2a37d8cf3ba40bb74b8ac14d45fbc3d08c37b1889b10d36558edfbd34ff914909118ae156c2f0915f2057901b90eff + "@emotion/hash": "npm:^0.9.2" + "@emotion/memoize": "npm:^0.9.0" + "@emotion/unitless": "npm:^0.10.0" + "@emotion/utils": "npm:^1.4.2" + csstype: "npm:^3.0.2" + checksum: 10c0/b28cb7de59de382021de2b26c0c94ebbfb16967a1b969a56fdb6408465a8993df243bfbd66430badaa6800e1834724e84895f5a6a9d97d0d224de3d77852acb4 languageName: node linkType: hard -"@babel/helper-member-expression-to-functions@npm:^7.24.8": - version: 7.24.8 - resolution: "@babel/helper-member-expression-to-functions@npm:7.24.8" - dependencies: - "@babel/traverse": "npm:^7.24.8" - "@babel/types": "npm:^7.24.8" - checksum: 10c0/7e14a5acc91f6cd26305a4441b82eb6f616bd70b096a4d2099a968f16b26d50207eec0b9ebfc466fefd62bd91587ac3be878117cdfec819b7151911183cb0e5a +"@emotion/sheet@npm:^1.4.0": + version: 1.4.0 + resolution: "@emotion/sheet@npm:1.4.0" + checksum: 10c0/3ca72d1650a07d2fbb7e382761b130b4a887dcd04e6574b2d51ce578791240150d7072a9bcb4161933abbcd1e38b243a6fb4464a7fe991d700c17aa66bb5acc7 languageName: node linkType: hard -"@babel/helper-module-imports@npm:^7.16.7, @babel/helper-module-imports@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/helper-module-imports@npm:7.18.6" +"@emotion/styled@npm:^11.14.1": + version: 11.14.1 + resolution: "@emotion/styled@npm:11.14.1" dependencies: - "@babel/types": "npm:^7.18.6" - checksum: 10c0/a92e28fc4b5dbb0d0afd4a313efc0cf5b26ce1adc0c01fc22724c997789ac7d7f4f30bc9143d94a6ba8b0a035933cf63a727a365ce1c57dbca0935f48de96244 + "@babel/runtime": "npm:^7.18.3" + "@emotion/babel-plugin": "npm:^11.13.5" + "@emotion/is-prop-valid": "npm:^1.3.0" + "@emotion/serialize": "npm:^1.3.3" + "@emotion/use-insertion-effect-with-fallbacks": "npm:^1.2.0" + "@emotion/utils": "npm:^1.4.2" + peerDependencies: + "@emotion/react": ^11.0.0-rc.0 + react: ">=16.8.0" + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/2bbf8451df49c967e41fbcf8111a7f6dafe6757f0cc113f2f6e287206c45ac1d54dc8a95a483b7c0cee8614b8a8d08155bded6453d6721de1f8cc8d5b9216963 languageName: node linkType: hard -"@babel/helper-module-imports@npm:^7.22.15": - version: 7.22.15 - resolution: "@babel/helper-module-imports@npm:7.22.15" - dependencies: - "@babel/types": "npm:^7.22.15" - checksum: 10c0/4e0d7fc36d02c1b8c8b3006dfbfeedf7a367d3334a04934255de5128115ea0bafdeb3e5736a2559917f0653e4e437400d54542da0468e08d3cbc86d3bbfa8f30 +"@emotion/unitless@npm:^0.10.0": + version: 0.10.0 + resolution: "@emotion/unitless@npm:0.10.0" + checksum: 10c0/150943192727b7650eb9a6851a98034ddb58a8b6958b37546080f794696141c3760966ac695ab9af97efe10178690987aee4791f9f0ad1ff76783cdca83c1d49 languageName: node linkType: hard -"@babel/helper-module-imports@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-module-imports@npm:7.24.7" - dependencies: - "@babel/traverse": "npm:^7.24.7" - "@babel/types": "npm:^7.24.7" - checksum: 10c0/97c57db6c3eeaea31564286e328a9fb52b0313c5cfcc7eee4bc226aebcf0418ea5b6fe78673c0e4a774512ec6c86e309d0f326e99d2b37bfc16a25a032498af0 +"@emotion/use-insertion-effect-with-fallbacks@npm:^1.2.0": + version: 1.2.0 + resolution: "@emotion/use-insertion-effect-with-fallbacks@npm:1.2.0" + peerDependencies: + react: ">=16.8.0" + checksum: 10c0/074dbc92b96bdc09209871070076e3b0351b6b47efefa849a7d9c37ab142130767609ca1831da0055988974e3b895c1de7606e4c421fecaa27c3e56a2afd3b08 languageName: node linkType: hard -"@babel/helper-module-transforms@npm:^7.21.0": - version: 7.21.0 - resolution: "@babel/helper-module-transforms@npm:7.21.0" - dependencies: - "@babel/helper-environment-visitor": "npm:^7.18.9" - "@babel/helper-module-imports": "npm:^7.18.6" - "@babel/helper-simple-access": "npm:^7.20.2" - "@babel/helper-split-export-declaration": "npm:^7.18.6" - "@babel/helper-validator-identifier": "npm:^7.19.1" - "@babel/template": "npm:^7.20.7" - "@babel/traverse": "npm:^7.21.0" - "@babel/types": "npm:^7.21.0" - checksum: 10c0/0bc325bde20545a1407b06655ae3af42ab9310fffcf6936a18111d10e8e76fd633ca997c7b75d5305303dab323adc197e38480c41a977c7b4ee09102999e0f96 +"@emotion/utils@npm:^1.4.2": + version: 1.4.2 + resolution: "@emotion/utils@npm:1.4.2" + checksum: 10c0/7d0010bf60a2a8c1a033b6431469de4c80e47aeb8fd856a17c1d1f76bbc3a03161a34aeaa78803566e29681ca551e7bf9994b68e9c5f5c796159923e44f78d9a languageName: node linkType: hard -"@babel/helper-module-transforms@npm:^7.24.7, @babel/helper-module-transforms@npm:^7.24.8, @babel/helper-module-transforms@npm:^7.25.0, @babel/helper-module-transforms@npm:^7.25.2": - version: 7.25.2 - resolution: "@babel/helper-module-transforms@npm:7.25.2" - dependencies: - "@babel/helper-module-imports": "npm:^7.24.7" - "@babel/helper-simple-access": "npm:^7.24.7" - "@babel/helper-validator-identifier": "npm:^7.24.7" - "@babel/traverse": "npm:^7.25.2" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10c0/adaa15970ace0aee5934b5a633789b5795b6229c6a9cf3e09a7e80aa33e478675eee807006a862aa9aa517935d81f88a6db8a9f5936e3a2a40ec75f8062bc329 +"@emotion/weak-memoize@npm:^0.4.0": + version: 0.4.0 + resolution: "@emotion/weak-memoize@npm:0.4.0" + checksum: 10c0/64376af11f1266042d03b3305c30b7502e6084868e33327e944b539091a472f089db307af69240f7188f8bc6b319276fd7b141a36613f1160d73d12a60f6ca1a languageName: node linkType: hard -"@babel/helper-optimise-call-expression@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-optimise-call-expression@npm:7.24.7" - dependencies: - "@babel/types": "npm:^7.24.7" - checksum: 10c0/ca6a9884705dea5c95a8b3ce132d1e3f2ae951ff74987d400d1d9c215dae9c0f9e29924d8f8e131e116533d182675bc261927be72f6a9a2968eaeeaa51eb1d0f +"@esbuild/aix-ppc64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/aix-ppc64@npm:0.28.2" + conditions: os=aix & cpu=ppc64 languageName: node linkType: hard -"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.20.2, @babel/helper-plugin-utils@npm:^7.24.7, @babel/helper-plugin-utils@npm:^7.24.8, @babel/helper-plugin-utils@npm:^7.8.0, @babel/helper-plugin-utils@npm:^7.8.3": - version: 7.24.8 - resolution: "@babel/helper-plugin-utils@npm:7.24.8" - checksum: 10c0/0376037f94a3bfe6b820a39f81220ac04f243eaee7193774b983e956c1750883ff236b30785795abbcda43fac3ece74750566830c2daa4d6e3870bb0dff34c2d +"@esbuild/android-arm64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/android-arm64@npm:0.28.2" + conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@babel/helper-plugin-utils@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/helper-plugin-utils@npm:7.22.5" - checksum: 10c0/d2c4bfe2fa91058bcdee4f4e57a3f4933aed7af843acfd169cd6179fab8d13c1d636474ecabb2af107dc77462c7e893199aa26632bac1c6d7e025a17cbb9d20d +"@esbuild/android-arm@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/android-arm@npm:0.28.2" + conditions: os=android & cpu=arm languageName: node linkType: hard -"@babel/helper-remap-async-to-generator@npm:^7.24.7, @babel/helper-remap-async-to-generator@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/helper-remap-async-to-generator@npm:7.25.0" - dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.24.7" - "@babel/helper-wrap-function": "npm:^7.25.0" - "@babel/traverse": "npm:^7.25.0" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10c0/0d17b5f7bb6a607edc9cc62fff8056dd9f341bf2f919884f97b99170d143022a5e7ae57922c4891e4fc360ad291e708d2f8cd8989f1d3cd7a17600159984f5a6 +"@esbuild/android-x64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/android-x64@npm:0.28.2" + conditions: os=android & cpu=x64 languageName: node linkType: hard -"@babel/helper-replace-supers@npm:^7.24.7, @babel/helper-replace-supers@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/helper-replace-supers@npm:7.25.0" - dependencies: - "@babel/helper-member-expression-to-functions": "npm:^7.24.8" - "@babel/helper-optimise-call-expression": "npm:^7.24.7" - "@babel/traverse": "npm:^7.25.0" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10c0/b4b6650ab3d56c39a259367cd97f8df2f21c9cebb3716fea7bca40a150f8847bfb82f481e98927c7c6579b48a977b5a8f77318a1c6aeb497f41ecd6dbc3fdfef +"@esbuild/darwin-arm64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/darwin-arm64@npm:0.28.2" + conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@babel/helper-simple-access@npm:^7.20.2": - version: 7.20.2 - resolution: "@babel/helper-simple-access@npm:7.20.2" - dependencies: - "@babel/types": "npm:^7.20.2" - checksum: 10c0/79cea28155536c74b37839748caea534bc413fac8c512e6101e9eecfe83f670db77bc782bdb41114caecbb1e2a73007ff6015d6a5ce58cae5363b8c5bd2dcee9 +"@esbuild/darwin-x64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/darwin-x64@npm:0.28.2" + conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@babel/helper-simple-access@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-simple-access@npm:7.24.7" - dependencies: - "@babel/traverse": "npm:^7.24.7" - "@babel/types": "npm:^7.24.7" - checksum: 10c0/7230e419d59a85f93153415100a5faff23c133d7442c19e0cd070da1784d13cd29096ee6c5a5761065c44e8164f9f80e3a518c41a0256df39e38f7ad6744fed7 +"@esbuild/freebsd-arm64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/freebsd-arm64@npm:0.28.2" + conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@babel/helper-skip-transparent-expression-wrappers@npm:^7.20.0, @babel/helper-skip-transparent-expression-wrappers@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.24.7" - dependencies: - "@babel/traverse": "npm:^7.24.7" - "@babel/types": "npm:^7.24.7" - checksum: 10c0/e3a9b8ac9c262ac976a1bcb5fe59694db5e6f0b4f9e7bdba5c7693b8b5e28113c23bdaa60fe8d3ec32a337091b67720b2053bcb3d5655f5406536c3d0584242b +"@esbuild/freebsd-x64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/freebsd-x64@npm:0.28.2" + conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@babel/helper-split-export-declaration@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/helper-split-export-declaration@npm:7.18.6" - dependencies: - "@babel/types": "npm:^7.18.6" - checksum: 10c0/1335b510a9aefcbf60d89648e622715774e56040d72302dc5e176c8d837c9ab81414ccfa9ed771a9f98da7192579bb12ab7a95948bfdc69b03b4a882b3983e48 +"@esbuild/linux-arm64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/linux-arm64@npm:0.28.2" + conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"@babel/helper-string-parser@npm:^7.19.4": - version: 7.19.4 - resolution: "@babel/helper-string-parser@npm:7.19.4" - checksum: 10c0/e20c81582e75df2a020a1c547376668a6e1e1c2ca535a6b7abb25b83d5536c99c0d113184bbe87c1a26e923a9bb0c6e5279fca8db6bd609cd3499fafafc01598 +"@esbuild/linux-arm@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/linux-arm@npm:0.28.2" + conditions: os=linux & cpu=arm languageName: node linkType: hard -"@babel/helper-string-parser@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/helper-string-parser@npm:7.22.5" - checksum: 10c0/6b0ff8af724377ec41e5587fffa7605198da74cb8e7d8d48a36826df0c0ba210eb9fedb3d9bef4d541156e0bd11040f021945a6cbb731ccec4aefb4affa17aa4 +"@esbuild/linux-ia32@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/linux-ia32@npm:0.28.2" + conditions: os=linux & cpu=ia32 languageName: node linkType: hard -"@babel/helper-string-parser@npm:^7.24.8": - version: 7.24.8 - resolution: "@babel/helper-string-parser@npm:7.24.8" - checksum: 10c0/6361f72076c17fabf305e252bf6d580106429014b3ab3c1f5c4eb3e6d465536ea6b670cc0e9a637a77a9ad40454d3e41361a2909e70e305116a23d68ce094c08 +"@esbuild/linux-loong64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/linux-loong64@npm:0.28.2" + conditions: os=linux & cpu=loong64 languageName: node linkType: hard -"@babel/helper-validator-identifier@npm:^7.18.6, @babel/helper-validator-identifier@npm:^7.19.1": - version: 7.19.1 - resolution: "@babel/helper-validator-identifier@npm:7.19.1" - checksum: 10c0/f978ecfea840f65b64ab9e17fac380625a45f4fe1361eeb29867fcfd1c9eaa72abd7023f2f40ac3168587d7e5153660d16cfccb352a557be2efd347a051b4b20 +"@esbuild/linux-mips64el@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/linux-mips64el@npm:0.28.2" + conditions: os=linux & cpu=mips64el languageName: node linkType: hard -"@babel/helper-validator-identifier@npm:^7.22.20": - version: 7.22.20 - resolution: "@babel/helper-validator-identifier@npm:7.22.20" - checksum: 10c0/dcad63db345fb110e032de46c3688384b0008a42a4845180ce7cd62b1a9c0507a1bed727c4d1060ed1a03ae57b4d918570259f81724aaac1a5b776056f37504e +"@esbuild/linux-ppc64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/linux-ppc64@npm:0.28.2" + conditions: os=linux & cpu=ppc64 languageName: node linkType: hard -"@babel/helper-validator-identifier@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-validator-identifier@npm:7.24.7" - checksum: 10c0/87ad608694c9477814093ed5b5c080c2e06d44cb1924ae8320474a74415241223cc2a725eea2640dd783ff1e3390e5f95eede978bc540e870053152e58f1d651 +"@esbuild/linux-riscv64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/linux-riscv64@npm:0.28.2" + conditions: os=linux & cpu=riscv64 languageName: node linkType: hard -"@babel/helper-validator-option@npm:^7.18.6": - version: 7.21.0 - resolution: "@babel/helper-validator-option@npm:7.21.0" - checksum: 10c0/a5efbf3f09f1514d1704f3f7bf0e5fac401fff48a9b84a9eb47a52a4c13beee9802c6cf212a82c5fb95f6cc6b5932cb32e756cf33075be17352f64827a8ec066 +"@esbuild/linux-s390x@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/linux-s390x@npm:0.28.2" + conditions: os=linux & cpu=s390x languageName: node linkType: hard -"@babel/helper-validator-option@npm:^7.24.7, @babel/helper-validator-option@npm:^7.24.8": - version: 7.24.8 - resolution: "@babel/helper-validator-option@npm:7.24.8" - checksum: 10c0/73db93a34ae89201351288bee7623eed81a54000779462a986105b54ffe82069e764afd15171a428b82e7c7a9b5fec10b5d5603b216317a414062edf5c67a21f +"@esbuild/linux-x64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/linux-x64@npm:0.28.2" + conditions: os=linux & cpu=x64 languageName: node linkType: hard -"@babel/helper-wrap-function@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/helper-wrap-function@npm:7.25.0" - dependencies: - "@babel/template": "npm:^7.25.0" - "@babel/traverse": "npm:^7.25.0" - "@babel/types": "npm:^7.25.0" - checksum: 10c0/d54601a98384c191cbc1ff07b03a19e288ef8d5c6bfafe270b2a303d96e7304eb296002921ed464cc1b105a547d1db146eb86b0be617924dee1ba1b379cdc216 +"@esbuild/netbsd-arm64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/netbsd-arm64@npm:0.28.2" + conditions: os=netbsd & cpu=arm64 languageName: node linkType: hard -"@babel/helpers@npm:^7.21.0": - version: 7.21.0 - resolution: "@babel/helpers@npm:7.21.0" - dependencies: - "@babel/template": "npm:^7.20.7" - "@babel/traverse": "npm:^7.21.0" - "@babel/types": "npm:^7.21.0" - checksum: 10c0/a7415373f1c9b84fe32839d5219c3d695e84b910f49a20786caf3b5a37f5079d26af6a5b36b4f2e3eb450b2413c309785483a8d59246d1326c44184c51c24255 +"@esbuild/netbsd-x64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/netbsd-x64@npm:0.28.2" + conditions: os=netbsd & cpu=x64 languageName: node linkType: hard -"@babel/helpers@npm:^7.25.0": - version: 7.25.6 - resolution: "@babel/helpers@npm:7.25.6" - dependencies: - "@babel/template": "npm:^7.25.0" - "@babel/types": "npm:^7.25.6" - checksum: 10c0/448c1cdabccca42fd97a252f73f1e4bcd93776dbf24044f3b4f49b756bf2ece73ee6df05177473bb74ea7456dddd18d6f481e4d96d2cc7839d078900d48c696c +"@esbuild/openbsd-arm64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/openbsd-arm64@npm:0.28.2" + conditions: os=openbsd & cpu=arm64 languageName: node linkType: hard -"@babel/highlight@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/highlight@npm:7.18.6" - dependencies: - "@babel/helper-validator-identifier": "npm:^7.18.6" - chalk: "npm:^2.0.0" - js-tokens: "npm:^4.0.0" - checksum: 10c0/a6a6928d25099ef04c337fcbb829fab8059bb67d31ac37212efd611bdbe247d0e71a5096c4524272cb56399f40251fac57c025e42d3bc924db0183a6435a60ac +"@esbuild/openbsd-x64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/openbsd-x64@npm:0.28.2" + conditions: os=openbsd & cpu=x64 languageName: node linkType: hard -"@babel/highlight@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/highlight@npm:7.24.7" - dependencies: - "@babel/helper-validator-identifier": "npm:^7.24.7" - chalk: "npm:^2.4.2" - js-tokens: "npm:^4.0.0" - picocolors: "npm:^1.0.0" - checksum: 10c0/674334c571d2bb9d1c89bdd87566383f59231e16bcdcf5bb7835babdf03c9ae585ca0887a7b25bdf78f303984af028df52831c7989fecebb5101cc132da9393a +"@esbuild/openharmony-arm64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/openharmony-arm64@npm:0.28.2" + conditions: os=openharmony & cpu=arm64 languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.21.0": - version: 7.21.1 - resolution: "@babel/parser@npm:7.21.1" - bin: - parser: ./bin/babel-parser.js - checksum: 10c0/748ce9f0786e7492304c23f2a4f0b3f358569452386a330cde76dddd9bff583268abc27f94eecd24cff09ef076fca8f9f546a68137fddc502fc8f09011285a52 +"@esbuild/sunos-x64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/sunos-x64@npm:0.28.2" + conditions: os=sunos & cpu=x64 languageName: node linkType: hard -"@babel/parser@npm:^7.25.0, @babel/parser@npm:^7.25.6": - version: 7.25.6 - resolution: "@babel/parser@npm:7.25.6" - dependencies: - "@babel/types": "npm:^7.25.6" - bin: - parser: ./bin/babel-parser.js - checksum: 10c0/f88a0e895dbb096fd37c4527ea97d12b5fc013720602580a941ac3a339698872f0c911e318c292b184c36b5fbe23b612f05aff9d24071bc847c7b1c21552c41d +"@esbuild/win32-arm64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/win32-arm64@npm:0.28.2" + conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.25.3": - version: 7.25.3 - resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.25.3" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" - "@babel/traverse": "npm:^7.25.3" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10c0/814b4d3f102e7556a5053d1acf57ef601cfcff39a2c81b8cdc6a5c842e3cb9838f5925d1466a5f1e6416e74c9c83586a3c07fbd7fb8610a396c2becdf9ae5790 +"@esbuild/win32-ia32@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/win32-ia32@npm:0.28.2" + conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@babel/plugin-bugfix-safari-class-field-initializer-scope@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/plugin-bugfix-safari-class-field-initializer-scope@npm:7.25.0" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10c0/9645a1f47b3750acadb1353c02e71cc712d072aafe5ce115ed3a886bc14c5d9200cfb0b5b5e60e813baa549b800cf798f8714019fd246c699053cf68c428e426 +"@esbuild/win32-x64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/win32-x64@npm:0.28.2" + conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.25.0" +"@eslint-community/eslint-utils@npm:^4.8.0, @eslint-community/eslint-utils@npm:^4.9.1": + version: 4.10.1 + resolution: "@eslint-community/eslint-utils@npm:4.10.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" + eslint-visitor-keys: "npm:^3.4.3" peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10c0/ed1ce1c90cac46c01825339fd0f2a96fa071b016fb819d8dfaf8e96300eae30e74870cb47e4dc80d4ce2fb287869f102878b4f3b35bc927fec8b1d0d76bcf612 + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + checksum: 10c0/b514586655698bc6b74db72a496c77e813c78b63e36a83429845ac7057dd77113b0b6c31e6e590d5baaeee2abae6ea22363a41209bcafa078d895ab83ce83011 languageName: node linkType: hard -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:7.24.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.24.7" - "@babel/plugin-transform-optional-chaining": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.13.0 - checksum: 10c0/aeb6e7aa363a47f815cf956ea1053c5dd8b786a17799f065c9688ba4b0051fe7565d258bbe9400bfcbfb3114cb9fda66983e10afe4d750bc70ff75403e15dd36 +"@eslint-community/regexpp@npm:^4.12.2": + version: 4.12.2 + resolution: "@eslint-community/regexpp@npm:4.12.2" + checksum: 10c0/fddcbc66851b308478d04e302a4d771d6917a0b3740dc351513c0da9ca2eab8a1adf99f5e0aa7ab8b13fa0df005c81adeee7e63a92f3effd7d367a163b721c2d languageName: node linkType: hard -"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:7.25.0" +"@eslint/config-array@npm:^0.23.5": + version: 0.23.5 + resolution: "@eslint/config-array@npm:0.23.5" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" - "@babel/traverse": "npm:^7.25.0" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10c0/45988025537a9d4a27b610fd696a18fd9ba9336621a69b4fb40560eeb10c79657f85c92a37f30c7c8fb29c22970eea0b373315795a891f1a05549a6cfe5a6bfe + "@eslint/object-schema": "npm:^3.0.5" + debug: "npm:^4.3.1" + minimatch: "npm:^10.2.4" + checksum: 10c0/b24833c4c76e78ee075d306cd3f095db46b2db0f90cc13a6ee6e4275f9889731c05bf5403ab5fefb79c756e07ac9184ed0e04570341382f9eccbccc80e6d1a0c languageName: node linkType: hard -"@babel/plugin-proposal-class-properties@npm:^7.16.0": - version: 7.18.6 - resolution: "@babel/plugin-proposal-class-properties@npm:7.18.6" +"@eslint/config-helpers@npm:^0.7.0": + version: 0.7.0 + resolution: "@eslint/config-helpers@npm:0.7.0" dependencies: - "@babel/helper-create-class-features-plugin": "npm:^7.18.6" - "@babel/helper-plugin-utils": "npm:^7.18.6" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/d5172ac6c9948cdfc387e94f3493ad86cb04035cf7433f86b5d358270b1b9752dc25e176db0c5d65892a246aca7bdb4636672e15626d7a7de4bc0bd0040168d9 + "@eslint/core": "npm:^1.2.1" + checksum: 10c0/fd40d57d6f1db49f7b647048b88a433dc7f6522ef3edf855a43cb526ef4fc40622ceed0dc8de2e03d254f30f8e035370570de1d4bd8e7c2b1200131451e0d331 languageName: node linkType: hard -"@babel/plugin-proposal-decorators@npm:^7.16.4": - version: 7.24.7 - resolution: "@babel/plugin-proposal-decorators@npm:7.24.7" +"@eslint/core@npm:^1.2.1": + version: 1.2.1 + resolution: "@eslint/core@npm:1.2.1" dependencies: - "@babel/helper-create-class-features-plugin": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/plugin-syntax-decorators": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/fb4e4711f90fed63aa4dfe34fc5f5a5d0af175725181678f462ee0df2b78a23ae83b9424403c6b957edbc07d2abc80f82f3b9f91baf568bdaf85e8196a9138d5 + "@types/json-schema": "npm:^7.0.15" + checksum: 10c0/10979b40588ecfef771fcb5013a542a35fb30692cc95a65f3481b0b36fbd89f5679efeb30d57f4eed35203d859aabace2a620177d6c536f71b299a1af2f3398f languageName: node linkType: hard -"@babel/plugin-proposal-nullish-coalescing-operator@npm:^7.16.0": - version: 7.18.6 - resolution: "@babel/plugin-proposal-nullish-coalescing-operator@npm:7.18.6" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.18.6" - "@babel/plugin-syntax-nullish-coalescing-operator": "npm:^7.8.3" +"@eslint/js@npm:^10.0.1": + version: 10.0.1 + resolution: "@eslint/js@npm:10.0.1" peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/f6629158196ee9f16295d16db75825092ef543f8b98f4dfdd516e642a0430c7b1d69319ee676d35485d9b86a53ade6de0b883490d44de6d4336d38cdeccbe0bf + eslint: ^10.0.0 + peerDependenciesMeta: + eslint: + optional: true + checksum: 10c0/9f3fcaf71ba7fdf65d82e8faad6ecfe97e11801cc3c362b306a88ea1ed1344ae0d35330dddb0e8ad18f010f6687a70b75491b9e01c8af57acd7987cee6b3ec6c languageName: node linkType: hard -"@babel/plugin-proposal-numeric-separator@npm:^7.16.0": - version: 7.18.6 - resolution: "@babel/plugin-proposal-numeric-separator@npm:7.18.6" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.18.6" - "@babel/plugin-syntax-numeric-separator": "npm:^7.10.4" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/a83a65c6ec0d2293d830e9db61406d246f22d8ea03583d68460cb1b6330c6699320acce1b45f66ba3c357830720e49267e3d99f95088be457c66e6450fbfe3fa +"@eslint/object-schema@npm:^3.0.5": + version: 3.0.5 + resolution: "@eslint/object-schema@npm:3.0.5" + checksum: 10c0/1db337431f520b99e9edda64ef5fafd7ec6a029843eeb608753025125b6649d861d843cffafafd3c4e37926d7d5f9ec0c6a8e3665c13c3da2144e8132892e92e languageName: node linkType: hard -"@babel/plugin-proposal-optional-chaining@npm:^7.16.0": - version: 7.21.0 - resolution: "@babel/plugin-proposal-optional-chaining@npm:7.21.0" +"@eslint/plugin-kit@npm:^0.7.3": + version: 0.7.3 + resolution: "@eslint/plugin-kit@npm:0.7.3" dependencies: - "@babel/helper-plugin-utils": "npm:^7.20.2" - "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.20.0" - "@babel/plugin-syntax-optional-chaining": "npm:^7.8.3" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/b524a61b1de3f3ad287cd1e98c2a7f662178d21cd02205b0d615512e475f0159fa1b569fa7e34c8ed67baef689c0136fa20ba7d1bf058d186d30736a581a723f + "@eslint/core": "npm:^1.2.1" + levn: "npm:^0.4.1" + checksum: 10c0/2daebaedcfbd261478e8b87dfb46d3118f48277c19eb298483ebc6f3b438e9688b86ddca13b3aeb08dfb06be00fb6073506852386aa124645c1170e7e33d140c languageName: node linkType: hard -"@babel/plugin-proposal-private-methods@npm:^7.16.0": - version: 7.18.6 - resolution: "@babel/plugin-proposal-private-methods@npm:7.18.6" +"@humanfs/core@npm:^0.19.2": + version: 0.19.2 + resolution: "@humanfs/core@npm:0.19.2" dependencies: - "@babel/helper-create-class-features-plugin": "npm:^7.18.6" - "@babel/helper-plugin-utils": "npm:^7.18.6" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/1c273d0ec3d49d0fe80bd754ec0191016e5b3ab4fb1e162ac0c014e9d3c1517a5d973afbf8b6dc9f9c98a8605c79e5f9e8b5ee158a4313fa68d1ff7b02084b6a + "@humanfs/types": "npm:^0.15.0" + checksum: 10c0/d0a1d52d7b30c27d49475a53072d1510b81c5803e44b342fb8faf3887f1aa27593a1e6dc76a45268e7892d3f4e198146659281f6b6d55eacf3fd5a38bac30c5c languageName: node linkType: hard -"@babel/plugin-proposal-private-property-in-object@npm:7.21.0-placeholder-for-preset-env.2": - version: 7.21.0-placeholder-for-preset-env.2 - resolution: "@babel/plugin-proposal-private-property-in-object@npm:7.21.0-placeholder-for-preset-env.2" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/e605e0070da087f6c35579499e65801179a521b6842c15181a1e305c04fded2393f11c1efd09b087be7f8b083d1b75e8f3efcbc1292b4f60d3369e14812cff63 +"@humanfs/node@npm:^0.16.6": + version: 0.16.8 + resolution: "@humanfs/node@npm:0.16.8" + dependencies: + "@humanfs/core": "npm:^0.19.2" + "@humanfs/types": "npm:^0.15.0" + "@humanwhocodes/retry": "npm:^0.4.0" + checksum: 10c0/56140579db811af4e160b195d45d0f29acf644d192c93fe24c9e594ebf06f19dfc157494a07c84540b8a071c0e4b37209c2362765d31734f4d0be869c2422e25 languageName: node linkType: hard -"@babel/plugin-proposal-private-property-in-object@npm:^7.16.7": - version: 7.21.11 - resolution: "@babel/plugin-proposal-private-property-in-object@npm:7.21.11" - dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.18.6" - "@babel/helper-create-class-features-plugin": "npm:^7.21.0" - "@babel/helper-plugin-utils": "npm:^7.20.2" - "@babel/plugin-syntax-private-property-in-object": "npm:^7.14.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/3c8c9ea175101b1cbb2b0e8fee20fcbdd03eb0700d3581aa826ac3573c9b002f39b1512c2af9fd1903ff921bcc864da95ad3cdeba53c9fbcfb3dc23916eacf47 +"@humanfs/types@npm:^0.15.0": + version: 0.15.0 + resolution: "@humanfs/types@npm:0.15.0" + checksum: 10c0/fc26b9a024b0e55f7eaf64036df94345bf5d36d6a41ef80ef38e78f1f7430ce26cf435af736adae58913baae18eac3f38c18739054a3d379102015978eae862e languageName: node linkType: hard -"@babel/plugin-syntax-async-generators@npm:^7.8.4": - version: 7.8.4 - resolution: "@babel/plugin-syntax-async-generators@npm:7.8.4" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/d13efb282838481348c71073b6be6245b35d4f2f964a8f71e4174f235009f929ef7613df25f8d2338e2d3e44bc4265a9f8638c6aaa136d7a61fe95985f9725c8 +"@humanwhocodes/module-importer@npm:^1.0.1": + version: 1.0.1 + resolution: "@humanwhocodes/module-importer@npm:1.0.1" + checksum: 10c0/909b69c3b86d482c26b3359db16e46a32e0fb30bd306a3c176b8313b9e7313dba0f37f519de6aa8b0a1921349e505f259d19475e123182416a506d7f87e7f529 languageName: node linkType: hard -"@babel/plugin-syntax-class-properties@npm:^7.12.13": - version: 7.12.13 - resolution: "@babel/plugin-syntax-class-properties@npm:7.12.13" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.12.13" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/95168fa186416195280b1264fb18afcdcdcea780b3515537b766cb90de6ce042d42dd6a204a39002f794ae5845b02afb0fd4861a3308a861204a55e68310a120 +"@humanwhocodes/retry@npm:^0.4.0, @humanwhocodes/retry@npm:^0.4.2": + version: 0.4.3 + resolution: "@humanwhocodes/retry@npm:0.4.3" + checksum: 10c0/3775bb30087d4440b3f7406d5a057777d90e4b9f435af488a4923ef249e93615fb78565a85f173a186a076c7706a81d0d57d563a2624e4de2c5c9c66c486ce42 languageName: node linkType: hard -"@babel/plugin-syntax-class-static-block@npm:^7.14.5": - version: 7.14.5 - resolution: "@babel/plugin-syntax-class-static-block@npm:7.14.5" +"@isaacs/fs-minipass@npm:^4.0.0": + version: 4.0.1 + resolution: "@isaacs/fs-minipass@npm:4.0.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.14.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/4464bf9115f4a2d02ce1454411baf9cfb665af1da53709c5c56953e5e2913745b0fcce82982a00463d6facbdd93445c691024e310b91431a1e2f024b158f6371 + minipass: "npm:^7.0.4" + checksum: 10c0/c25b6dc1598790d5b55c0947a9b7d111cfa92594db5296c3b907e2f533c033666f692a3939eadac17b1c7c40d362d0b0635dc874cbfe3e70db7c2b07cc97a5d2 languageName: node linkType: hard -"@babel/plugin-syntax-decorators@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-syntax-decorators@npm:7.24.7" +"@jridgewell/gen-mapping@npm:^0.3.12": + version: 0.3.13 + resolution: "@jridgewell/gen-mapping@npm:0.3.13" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/d1ecc334af7a5418a6e8ad5e711bf5d5a892ba00e04ba05b90077b9da735585ff8d4141e4fc3ae781b854f48eda9f3a9cfa9f1c80f5f4a697dbded01058a8b63 + "@jridgewell/sourcemap-codec": "npm:^1.5.0" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/9a7d65fb13bd9aec1fbab74cda08496839b7e2ceb31f5ab922b323e94d7c481ce0fc4fd7e12e2610915ed8af51178bdc61e168e92a8c8b8303b030b03489b13b languageName: node linkType: hard -"@babel/plugin-syntax-dynamic-import@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/plugin-syntax-dynamic-import@npm:7.8.3" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/9c50927bf71adf63f60c75370e2335879402648f468d0172bc912e303c6a3876927d8eb35807331b57f415392732ed05ab9b42c68ac30a936813ab549e0246c5 +"@jridgewell/resolve-uri@npm:^3.1.0": + version: 3.1.2 + resolution: "@jridgewell/resolve-uri@npm:3.1.2" + checksum: 10c0/d502e6fb516b35032331406d4e962c21fe77cdf1cbdb49c6142bcbd9e30507094b18972778a6e27cbad756209cfe34b1a27729e6fa08a2eb92b33943f680cf1e languageName: node linkType: hard -"@babel/plugin-syntax-export-namespace-from@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/plugin-syntax-export-namespace-from@npm:7.8.3" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.3" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/5100d658ba563829700cd8d001ddc09f4c0187b1a13de300d729c5b3e87503f75a6d6c99c1794182f7f1a9f546ee009df4f15a0ce36376e206ed0012fa7cdc24 +"@jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.5.0": + version: 1.6.0 + resolution: "@jridgewell/sourcemap-codec@npm:1.6.0" + checksum: 10c0/b5be700e45a775f218589c3466c4ffea630582b4988657652da464e5ab8a7d18bf928ee4fd2363fb346ede4bea9c6ff0bae05a538358c4565ece6199531b5f72 languageName: node linkType: hard -"@babel/plugin-syntax-flow@npm:^7.23.3": - version: 7.23.3 - resolution: "@babel/plugin-syntax-flow@npm:7.23.3" +"@jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.28": + version: 0.3.31 + resolution: "@jridgewell/trace-mapping@npm:0.3.31" dependencies: - "@babel/helper-plugin-utils": "npm:^7.22.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/8a5e1e8b6a3728a2c8fe6d70c09a43642e737d9c0485e1b041cd3a6021ef05376ec3c9137be3b118c622ba09b5770d26fdc525473f8d06d4ab9e46de2783dd0a + "@jridgewell/resolve-uri": "npm:^3.1.0" + "@jridgewell/sourcemap-codec": "npm:^1.4.14" + checksum: 10c0/4b30ec8cd56c5fd9a661f088230af01e0c1a3888d11ffb6b47639700f71225be21d1f7e168048d6d4f9449207b978a235c07c8f15c07705685d16dc06280e9d9 languageName: node linkType: hard -"@babel/plugin-syntax-flow@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-syntax-flow@npm:7.24.7" +"@keyv/bigmap@npm:^1.3.1": + version: 1.3.1 + resolution: "@keyv/bigmap@npm:1.3.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" + hashery: "npm:^1.4.0" + hookified: "npm:^1.15.0" peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/2f0cb7a78379029707e61f6665634a5b758c8b4ccb602a72d798e41d36b0647c2f2de59f90e0c1d522b026962918e54d82f3aee0c194dc87cd340455aa58562a + keyv: ^5.6.0 + checksum: 10c0/acc6a4a5edf462ce23e95672ab4bfaf7cd1941dff6bf3a2f671ce467961ace1fac9d3eb75a9ed9a8e92012e00a7d8b16ad1677bc539a52c3ad0cec31473e2349 languageName: node linkType: hard -"@babel/plugin-syntax-import-assertions@npm:^7.24.7": - version: 7.25.6 - resolution: "@babel/plugin-syntax-import-assertions@npm:7.25.6" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/55afa63b1b1355bcc1d85a9ad9d2c78983e27beee38e232d5c1ab59eac39127ce3c3817d6686e3ab1d0aff5edd8e38a6852885c65d3e518accdd183a445ef411 +"@keyv/serialize@npm:^1.1.1": + version: 1.1.1 + resolution: "@keyv/serialize@npm:1.1.1" + checksum: 10c0/b0008cae4a54400c3abf587b8cc2474c6f528ee58969ce6cf9cb07a04006f80c73c85971d6be6544408318a2bc40108236a19a82aea0a6de95aae49533317374 languageName: node linkType: hard -"@babel/plugin-syntax-import-attributes@npm:^7.24.7": - version: 7.25.6 - resolution: "@babel/plugin-syntax-import-attributes@npm:7.25.6" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/0e9359cf2d117476310961dfcfd7204ed692e933707da10d6194153d3996cd2ea5b7635fc90d720dce3612083af89966bb862561064a509c350320dc98644751 +"@mui/core-downloads-tracker@npm:^7.3.11": + version: 7.3.11 + resolution: "@mui/core-downloads-tracker@npm:7.3.11" + checksum: 10c0/7c184aba9ff673c44d21c72f4f553516e1a8ae14d7e2f648ab1e58dbb2ad74163e6cc31b1ec443cb232616b6cd650c932c44c30fc4aa22e154573c02289564a3 languageName: node linkType: hard -"@babel/plugin-syntax-import-meta@npm:^7.10.4": - version: 7.10.4 - resolution: "@babel/plugin-syntax-import-meta@npm:7.10.4" +"@mui/icons-material@npm:^7.3.11": + version: 7.3.11 + resolution: "@mui/icons-material@npm:7.3.11" dependencies: - "@babel/helper-plugin-utils": "npm:^7.10.4" + "@babel/runtime": "npm:^7.28.6" peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/0b08b5e4c3128523d8e346f8cfc86824f0da2697b1be12d71af50a31aff7a56ceb873ed28779121051475010c28d6146a6bfea8518b150b71eeb4e46190172ee + "@mui/material": ^7.3.11 + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/eb3ac873e9dcb40273909ef44b669a751dc5cee893088422ff373796c2cfe59c1338263b5a59d5a453d37b00c4ec494dbb836b275d778d20ea335f6d029b4742 languageName: node linkType: hard -"@babel/plugin-syntax-json-strings@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/plugin-syntax-json-strings@npm:7.8.3" +"@mui/material@npm:^7.3.11": + version: 7.3.11 + resolution: "@mui/material@npm:7.3.11" dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" + "@babel/runtime": "npm:^7.28.6" + "@mui/core-downloads-tracker": "npm:^7.3.11" + "@mui/system": "npm:^7.3.11" + "@mui/types": "npm:^7.4.12" + "@mui/utils": "npm:^7.3.11" + "@popperjs/core": "npm:^2.11.8" + "@types/react-transition-group": "npm:^4.4.12" + clsx: "npm:^2.1.1" + csstype: "npm:^3.2.3" + prop-types: "npm:^15.8.1" + react-is: "npm:^19.2.3" + react-transition-group: "npm:^4.4.5" peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/e98f31b2ec406c57757d115aac81d0336e8434101c224edd9a5c93cefa53faf63eacc69f3138960c8b25401315af03df37f68d316c151c4b933136716ed6906e + "@emotion/react": ^11.5.0 + "@emotion/styled": ^11.3.0 + "@mui/material-pigment-css": ^7.3.11 + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + "@mui/material-pigment-css": + optional: true + "@types/react": + optional: true + checksum: 10c0/ad2cca3f84a8c5f5b980d6126a0bf3dd35deb82438adc3bc29e125e19554c8fcf9f68cff5f471f18d8ca921524f3a312254041021ef49ee872ecc383da835805 languageName: node linkType: hard -"@babel/plugin-syntax-jsx@npm:^7.22.5": - version: 7.23.3 - resolution: "@babel/plugin-syntax-jsx@npm:7.23.3" +"@mui/private-theming@npm:^7.3.11": + version: 7.3.11 + resolution: "@mui/private-theming@npm:7.3.11" dependencies: - "@babel/helper-plugin-utils": "npm:^7.22.5" + "@babel/runtime": "npm:^7.28.6" + "@mui/utils": "npm:^7.3.11" + prop-types: "npm:^15.8.1" peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/563bb7599b868773f1c7c1d441ecc9bc53aeb7832775da36752c926fc402a1fa5421505b39e724f71eb217c13e4b93117e081cac39723b0e11dac4c897f33c3e + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/ffdeb58011d1b09df3dde7abf8e6664d3b4821d996526c73d72934086c05dec7f28eaeabcad00793a7c791ce4cbb001aa6756005be5ca5de3fd3fc4bf4f2491e languageName: node linkType: hard -"@babel/plugin-syntax-jsx@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-syntax-jsx@npm:7.24.7" +"@mui/styled-engine@npm:^7.3.10": + version: 7.3.10 + resolution: "@mui/styled-engine@npm:7.3.10" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/runtime": "npm:^7.28.6" + "@emotion/cache": "npm:^11.14.0" + "@emotion/serialize": "npm:^1.3.3" + "@emotion/sheet": "npm:^1.4.0" + csstype: "npm:^3.2.3" + prop-types: "npm:^15.8.1" peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/f44d927a9ae8d5ef016ff5b450e1671e56629ddc12e56b938e41fd46e141170d9dfc9a53d6cb2b9a20a7dd266a938885e6a3981c60c052a2e1daed602ac80e51 + "@emotion/react": ^11.4.1 + "@emotion/styled": ^11.3.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + checksum: 10c0/5a41b7c99381deb8c5e3296cf1dc5a3f6dceab87d3d78d28ce0407246fe44c56aad97fcc9e49636a846476effbdd590ae7d3d87d36ef126a49c746b5424d1535 languageName: node linkType: hard -"@babel/plugin-syntax-logical-assignment-operators@npm:^7.10.4": - version: 7.10.4 - resolution: "@babel/plugin-syntax-logical-assignment-operators@npm:7.10.4" +"@mui/system@npm:^7.3.11": + version: 7.3.11 + resolution: "@mui/system@npm:7.3.11" dependencies: - "@babel/helper-plugin-utils": "npm:^7.10.4" + "@babel/runtime": "npm:^7.28.6" + "@mui/private-theming": "npm:^7.3.11" + "@mui/styled-engine": "npm:^7.3.10" + "@mui/types": "npm:^7.4.12" + "@mui/utils": "npm:^7.3.11" + clsx: "npm:^2.1.1" + csstype: "npm:^3.2.3" + prop-types: "npm:^15.8.1" peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/2594cfbe29411ad5bc2ad4058de7b2f6a8c5b86eda525a993959438615479e59c012c14aec979e538d60a584a1a799b60d1b8942c3b18468cb9d99b8fd34cd0b + "@emotion/react": ^11.5.0 + "@emotion/styled": ^11.3.0 + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + "@types/react": + optional: true + checksum: 10c0/e023ad1d883d5d168969cb8ef8819ba856301e081457a5a131c36aa2eebe000119566968c72f38fe3bb5d0b5def776ce35b440ecb6cd93532de220e0053ecf53 languageName: node linkType: hard -"@babel/plugin-syntax-nullish-coalescing-operator@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/plugin-syntax-nullish-coalescing-operator@npm:7.8.3" +"@mui/types@npm:^7.4.12": + version: 7.4.12 + resolution: "@mui/types@npm:7.4.12" dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" + "@babel/runtime": "npm:^7.28.6" peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/2024fbb1162899094cfc81152449b12bd0cc7053c6d4bda8ac2852545c87d0a851b1b72ed9560673cbf3ef6248257262c3c04aabf73117215c1b9cc7dd2542ce + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/e2247f9c3b8ef08e2eb7494ad8fcaffd8d695655cb355b8c3f23ebecbf69e13fcf73411b0a209fc0750eb416f7d0f7da1aa82557beb9bd58f26f9645f3f2ce95 languageName: node linkType: hard -"@babel/plugin-syntax-numeric-separator@npm:^7.10.4": - version: 7.10.4 - resolution: "@babel/plugin-syntax-numeric-separator@npm:7.10.4" +"@mui/utils@npm:^7.3.11, @mui/utils@npm:^7.3.5": + version: 7.3.11 + resolution: "@mui/utils@npm:7.3.11" dependencies: - "@babel/helper-plugin-utils": "npm:^7.10.4" + "@babel/runtime": "npm:^7.28.6" + "@mui/types": "npm:^7.4.12" + "@types/prop-types": "npm:^15.7.15" + clsx: "npm:^2.1.1" + prop-types: "npm:^15.8.1" + react-is: "npm:^19.2.3" peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/c55a82b3113480942c6aa2fcbe976ff9caa74b7b1109ff4369641dfbc88d1da348aceb3c31b6ed311c84d1e7c479440b961906c735d0ab494f688bf2fd5b9bb9 + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/fdea802667ac724f5648637b22c4a7893135cfd0be80d2c4de1281cac5395b12c6e59ffd487932e3cb21fc628456b479abce8cc4f31c1223da8a1d125f0dd38b languageName: node linkType: hard -"@babel/plugin-syntax-object-rest-spread@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/plugin-syntax-object-rest-spread@npm:7.8.3" +"@mui/x-data-grid@npm:^8.29.5": + version: 8.29.5 + resolution: "@mui/x-data-grid@npm:8.29.5" dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/ee1eab52ea6437e3101a0a7018b0da698545230015fc8ab129d292980ec6dff94d265e9e90070e8ae5fed42f08f1622c14c94552c77bcac784b37f503a82ff26 + "@babel/runtime": "npm:^7.28.4" + "@mui/utils": "npm:^7.3.5" + "@mui/x-internals": "npm:8.29.3" + "@mui/x-virtualizer": "npm:0.4.2" + clsx: "npm:^2.1.1" + prop-types: "npm:^15.8.1" + use-sync-external-store: "npm:^1.6.0" + peerDependencies: + "@emotion/react": ^11.9.0 + "@emotion/styled": ^11.8.1 + "@mui/material": ^5.15.14 || ^6.0.0 || ^7.0.0 + "@mui/system": ^5.15.14 || ^6.0.0 || ^7.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + checksum: 10c0/4b8cd40a9208f5557d7ae2a5a44e7c58291330c4d8ad214d8806b51d34df10120f57107b3d0e1da64eb6968ec86fdb3cfdcdc5d73d79ec93483f7593a20bac8d languageName: node linkType: hard -"@babel/plugin-syntax-optional-catch-binding@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/plugin-syntax-optional-catch-binding@npm:7.8.3" +"@mui/x-internals@npm:8.29.3": + version: 8.29.3 + resolution: "@mui/x-internals@npm:8.29.3" dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" + "@babel/runtime": "npm:^7.28.4" + "@mui/utils": "npm:^7.3.5" + reselect: "npm:^5.1.1" + use-sync-external-store: "npm:^1.6.0" peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/27e2493ab67a8ea6d693af1287f7e9acec206d1213ff107a928e85e173741e1d594196f99fec50e9dde404b09164f39dec5864c767212154ffe1caa6af0bc5af + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10c0/62bf51b77ad89436b4f2c742b2e727d71d6acdf07a4aa92d6f1651c47945c2a640ba3224c54b329e504b9175c3dbdf9b47ca5e6bc80d8170b0f9e084d8bc2352 languageName: node linkType: hard -"@babel/plugin-syntax-optional-chaining@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/plugin-syntax-optional-chaining@npm:7.8.3" +"@mui/x-virtualizer@npm:0.4.2": + version: 0.4.2 + resolution: "@mui/x-virtualizer@npm:0.4.2" dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" + "@babel/runtime": "npm:^7.28.4" + "@mui/utils": "npm:^7.3.5" + "@mui/x-internals": "npm:8.29.3" peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/46edddf2faa6ebf94147b8e8540dfc60a5ab718e2de4d01b2c0bdf250a4d642c2bd47cbcbb739febcb2bf75514dbcefad3c52208787994b8d0f8822490f55e81 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10c0/2ac26b482b5f9ee179f79940b084cc515909cd782f18d013247d28ee061e05ecb45731dd81c7291c220c4dd13373d7865a9daa8b19cd6d48a9c789e3723f0781 languageName: node linkType: hard -"@babel/plugin-syntax-private-property-in-object@npm:^7.14.5": - version: 7.14.5 - resolution: "@babel/plugin-syntax-private-property-in-object@npm:7.14.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.14.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/69822772561706c87f0a65bc92d0772cea74d6bc0911537904a676d5ff496a6d3ac4e05a166d8125fce4a16605bace141afc3611074e170a994e66e5397787f3 +"@oxc-project/types@npm:=0.154.0": + version: 0.154.0 + resolution: "@oxc-project/types@npm:0.154.0" + checksum: 10c0/fa91599d087ea4d37c5a3b40dd8e2cb431a7d37730116069f2577dee0b104dc5abb06c96b931c517c8b86f618df1e0c7a37d24086f3cfe39d81eb6db59e70add languageName: node linkType: hard -"@babel/plugin-syntax-top-level-await@npm:^7.14.5": - version: 7.14.5 - resolution: "@babel/plugin-syntax-top-level-await@npm:7.14.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.14.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/14bf6e65d5bc1231ffa9def5f0ef30b19b51c218fcecaa78cd1bdf7939dfdf23f90336080b7f5196916368e399934ce5d581492d8292b46a2fb569d8b2da106f +"@pkgr/core@npm:^0.3.6": + version: 0.3.6 + resolution: "@pkgr/core@npm:0.3.6" + checksum: 10c0/153f0f4563f505faeba13c733efa0e05e467ce1c6b941055a5fd3b4560da60fbf1dff4b11da0075f034ddda11f2842b90395f60895dde5825875b616edccc11c languageName: node linkType: hard -"@babel/plugin-syntax-typescript@npm:^7.24.7": - version: 7.25.4 - resolution: "@babel/plugin-syntax-typescript@npm:7.25.4" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/199919d44c73e5edee9ffd311cf638f88d26a810189e32d338c46c7600441fd5c4a2e431f9be377707cbf318410895304e90b83bf8d9011d205150fa7f260e63 +"@popperjs/core@npm:^2.11.8": + version: 2.11.8 + resolution: "@popperjs/core@npm:2.11.8" + checksum: 10c0/4681e682abc006d25eb380d0cf3efc7557043f53b6aea7a5057d0d1e7df849a00e281cd8ea79c902a35a414d7919621fc2ba293ecec05f413598e0b23d5a1e63 languageName: node linkType: hard -"@babel/plugin-syntax-unicode-sets-regex@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/plugin-syntax-unicode-sets-regex@npm:7.18.6" - dependencies: - "@babel/helper-create-regexp-features-plugin": "npm:^7.18.6" - "@babel/helper-plugin-utils": "npm:^7.18.6" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10c0/9144e5b02a211a4fb9a0ce91063f94fbe1004e80bde3485a0910c9f14897cf83fabd8c21267907cff25db8e224858178df0517f14333cfcf3380ad9a4139cb50 +"@rolldown/binding-android-arm-eabi@npm:1.2.14": + version: 1.2.14 + resolution: "@rolldown/binding-android-arm-eabi@npm:1.2.14" + conditions: os=android & cpu=arm languageName: node linkType: hard -"@babel/plugin-transform-arrow-functions@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-arrow-functions@npm:7.24.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/6ac05a54e5582f34ac6d5dc26499e227227ec1c7fa6fc8de1f3d40c275f140d3907f79bbbd49304da2d7008a5ecafb219d0b71d78ee3290ca22020d878041245 +"@rolldown/binding-android-arm64@npm:1.2.14": + version: 1.2.14 + resolution: "@rolldown/binding-android-arm64@npm:1.2.14" + conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@babel/plugin-transform-async-generator-functions@npm:^7.25.4": - version: 7.25.4 - resolution: "@babel/plugin-transform-async-generator-functions@npm:7.25.4" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" - "@babel/helper-remap-async-to-generator": "npm:^7.25.0" - "@babel/plugin-syntax-async-generators": "npm:^7.8.4" - "@babel/traverse": "npm:^7.25.4" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/efed6f6be90b25ad77c15a622a0dc0b22dbf5d45599c207ab8fbc4e959aef21f574fa467d9cf872e45de664a46c32334e78dee2332d82f5f27e26249a34a0920 +"@rolldown/binding-darwin-arm64@npm:1.2.14": + version: 1.2.14 + resolution: "@rolldown/binding-darwin-arm64@npm:1.2.14" + conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@babel/plugin-transform-async-to-generator@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-async-to-generator@npm:7.24.7" - dependencies: - "@babel/helper-module-imports": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/helper-remap-async-to-generator": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/83c82e243898875af8457972a26ab29baf8a2078768ee9f35141eb3edff0f84b165582a2ff73e90a9e08f5922bf813dbf15a85c1213654385198f4591c0dc45d +"@rolldown/binding-darwin-x64@npm:1.2.14": + version: 1.2.14 + resolution: "@rolldown/binding-darwin-x64@npm:1.2.14" + conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@babel/plugin-transform-block-scoped-functions@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-block-scoped-functions@npm:7.24.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/113e86de4612ae91773ff5cb6b980f01e1da7e26ae6f6012127415d7ae144e74987bc23feb97f63ba4bc699331490ddea36eac004d76a20d5369e4cc6a7f61cd +"@rolldown/binding-freebsd-x64@npm:1.2.14": + version: 1.2.14 + resolution: "@rolldown/binding-freebsd-x64@npm:1.2.14" + conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@babel/plugin-transform-block-scoping@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/plugin-transform-block-scoping@npm:7.25.0" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/382931c75a5d0ea560387e76cb57b03461300527e4784efcb2fb62f36c1eb0ab331327b6034def256baa0cad9050925a61f9c0d56261b6afd6a29c3065fb0bd4 +"@rolldown/binding-linux-arm-gnueabihf@npm:1.2.14": + version: 1.2.14 + resolution: "@rolldown/binding-linux-arm-gnueabihf@npm:1.2.14" + conditions: os=linux & cpu=arm languageName: node linkType: hard -"@babel/plugin-transform-class-properties@npm:^7.25.4": - version: 7.25.4 - resolution: "@babel/plugin-transform-class-properties@npm:7.25.4" - dependencies: - "@babel/helper-create-class-features-plugin": "npm:^7.25.4" - "@babel/helper-plugin-utils": "npm:^7.24.8" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/0b41bc8a5920d3d17c7c06220b601cf43e0a32ac34f05f05cd0cdf08915e4521b1b707cb1e60942b4fc68a5dfac09f0444a8720e0c72ce76fb039e8ec5263115 +"@rolldown/binding-linux-arm64-gnu@npm:1.2.14": + version: 1.2.14 + resolution: "@rolldown/binding-linux-arm64-gnu@npm:1.2.14" + conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@babel/plugin-transform-class-static-block@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-class-static-block@npm:7.24.7" - dependencies: - "@babel/helper-create-class-features-plugin": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/plugin-syntax-class-static-block": "npm:^7.14.5" - peerDependencies: - "@babel/core": ^7.12.0 - checksum: 10c0/b0ade39a3d09dce886f79dbd5907c3d99b48167eddb6b9bbde24a0598129654d7017e611c20494cdbea48b07ac14397cd97ea34e3754bbb2abae4e698128eccb +"@rolldown/binding-linux-arm64-musl@npm:1.2.14": + version: 1.2.14 + resolution: "@rolldown/binding-linux-arm64-musl@npm:1.2.14" + conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@babel/plugin-transform-classes@npm:^7.25.4": - version: 7.25.4 - resolution: "@babel/plugin-transform-classes@npm:7.25.4" - dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.24.7" - "@babel/helper-compilation-targets": "npm:^7.25.2" - "@babel/helper-plugin-utils": "npm:^7.24.8" - "@babel/helper-replace-supers": "npm:^7.25.0" - "@babel/traverse": "npm:^7.25.4" - globals: "npm:^11.1.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/c68424d9dd64860825111aa4a4ed5caf29494b7a02ddb9c36351d768c41e8e05127d89274795cdfcade032d9d299e6c677418259df58c71e68f1741583dcf467 +"@rolldown/binding-linux-ppc64-gnu@npm:1.2.14": + version: 1.2.14 + resolution: "@rolldown/binding-linux-ppc64-gnu@npm:1.2.14" + conditions: os=linux & cpu=ppc64 & libc=glibc languageName: node linkType: hard -"@babel/plugin-transform-computed-properties@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-computed-properties@npm:7.24.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/template": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/25636dbc1f605c0b8bc60aa58628a916b689473d11551c9864a855142e36742fe62d4a70400ba3b74902338e77fb3d940376c0a0ba154b6b7ec5367175233b49 +"@rolldown/binding-linux-s390x-gnu@npm:1.2.14": + version: 1.2.14 + resolution: "@rolldown/binding-linux-s390x-gnu@npm:1.2.14" + conditions: os=linux & cpu=s390x & libc=glibc languageName: node linkType: hard -"@babel/plugin-transform-destructuring@npm:^7.24.8": - version: 7.24.8 - resolution: "@babel/plugin-transform-destructuring@npm:7.24.8" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/804968c1d5f5072c717505296c1e5d5ec33e90550423de66de82bbcb78157156e8470bbe77a04ab8c710a88a06360a30103cf223ac7eff4829adedd6150de5ce +"@rolldown/binding-linux-x64-gnu@npm:1.2.14": + version: 1.2.14 + resolution: "@rolldown/binding-linux-x64-gnu@npm:1.2.14" + conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@babel/plugin-transform-dotall-regex@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-dotall-regex@npm:7.24.7" - dependencies: - "@babel/helper-create-regexp-features-plugin": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/793f14c9494972d294b7e7b97b747f47874b6d57d7804d3443c701becf5db192c9311be6a1835c07664486df1f5c60d33196c36fb7e11a53015e476b4c145b33 +"@rolldown/binding-linux-x64-musl@npm:1.2.14": + version: 1.2.14 + resolution: "@rolldown/binding-linux-x64-musl@npm:1.2.14" + conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@babel/plugin-transform-duplicate-keys@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-duplicate-keys@npm:7.24.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/75ff7ec1117ac500e77bf20a144411d39c0fdd038f108eec061724123ce6d1bb8d5bd27968e466573ee70014f8be0043361cdb0ef388f8a182d1d97ad67e51b9 +"@rolldown/binding-openharmony-arm64@npm:1.2.14": + version: 1.2.14 + resolution: "@rolldown/binding-openharmony-arm64@npm:1.2.14" + conditions: os=openharmony & cpu=arm64 languageName: node linkType: hard -"@babel/plugin-transform-duplicate-named-capturing-groups-regex@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/plugin-transform-duplicate-named-capturing-groups-regex@npm:7.25.0" - dependencies: - "@babel/helper-create-regexp-features-plugin": "npm:^7.25.0" - "@babel/helper-plugin-utils": "npm:^7.24.8" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10c0/1c9b57ddd9b33696e88911d0e7975e1573ebc46219c4b30eb1dc746cbb71aedfac6f6dab7fdfdec54dd58f31468bf6ab56b157661ea4ffe58f906d71f89544c8 +"@rolldown/binding-win32-arm64-msvc@npm:1.2.14": + version: 1.2.14 + resolution: "@rolldown/binding-win32-arm64-msvc@npm:1.2.14" + conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@babel/plugin-transform-dynamic-import@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-dynamic-import@npm:7.24.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/plugin-syntax-dynamic-import": "npm:^7.8.3" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/eeda48372efd0a5103cb22dadb13563c975bce18ae85daafbb47d57bb9665d187da9d4fe8d07ac0a6e1288afcfcb73e4e5618bf75ff63fddf9736bfbf225203b +"@rolldown/binding-win32-x64-msvc@npm:1.2.14": + version: 1.2.14 + resolution: "@rolldown/binding-win32-x64-msvc@npm:1.2.14" + conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@babel/plugin-transform-exponentiation-operator@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-exponentiation-operator@npm:7.24.7" - dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/ace3e11c94041b88848552ba8feb39ae4d6cad3696d439ff51445bd2882d8b8775d85a26c2c0edb9b5e38c9e6013cc11b0dea89ec8f93c7d9d7ee95e3645078c +"@rolldown/pluginutils@npm:^1.0.0, @rolldown/pluginutils@npm:^1.0.1": + version: 1.0.1 + resolution: "@rolldown/pluginutils@npm:1.0.1" + checksum: 10c0/99d9b06d90196823e4d8c841f258db7a16e5dbba5824a2962b05d907b79f1ba929d56f22dd744fd530936e568c865ee56a719dc31e57e13bc0a8eb4764a8d8dd languageName: node linkType: hard -"@babel/plugin-transform-export-namespace-from@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-export-namespace-from@npm:7.24.7" +"@sinonjs/commons@npm:^3.0.1": + version: 3.0.1 + resolution: "@sinonjs/commons@npm:3.0.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/plugin-syntax-export-namespace-from": "npm:^7.8.3" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/4e144d7f1c57bc63b4899dbbbdfed0880f2daa75ea9c7251c7997f106e4b390dc362175ab7830f11358cb21f6b972ca10a43a2e56cd789065f7606b082674c0c + type-detect: "npm:4.0.8" + checksum: 10c0/1227a7b5bd6c6f9584274db996d7f8cee2c8c350534b9d0141fc662eaf1f292ea0ae3ed19e5e5271c8fd390d27e492ca2803acd31a1978be2cdc6be0da711403 languageName: node linkType: hard -"@babel/plugin-transform-flow-strip-types@npm:^7.16.0": - version: 7.25.2 - resolution: "@babel/plugin-transform-flow-strip-types@npm:7.25.2" +"@sinonjs/fake-timers@npm:^15.4.0": + version: 15.4.0 + resolution: "@sinonjs/fake-timers@npm:15.4.0" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" - "@babel/plugin-syntax-flow": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/821f5ccdb8104e09764d8a24d4c0dd4fe9e264d95e6477269c911e15240a63343d3fe71b6cf9382273766a0e86a015c2867d26fd75e5827134d990c93fa9e605 + "@sinonjs/commons": "npm:^3.0.1" + checksum: 10c0/de4522afe0699fa8d3ae9d1715cbaa4b47e518c707bb7988a9ec6c7c67557d9f6df451f6be0338598b984a86f65aab9fab38dd9ce75a3c0ffb801a9500d5b10d languageName: node linkType: hard -"@babel/plugin-transform-for-of@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-for-of@npm:7.24.7" +"@sinonjs/samsam@npm:^10.0.2": + version: 10.0.2 + resolution: "@sinonjs/samsam@npm:10.0.2" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/77629b1173e55d07416f05ba7353caa09d2c2149da2ca26721ab812209b63689d1be45116b68eadc011c49ced59daf5320835b15245eb7ae93ae0c5e8277cfc0 + "@sinonjs/commons": "npm:^3.0.1" + type-detect: "npm:^4.1.0" + checksum: 10c0/b590af28a093d30db9b84f32793b18388817c6555df2533666c43e257e8a7becf43d864489d87af1b6e18b3163cc8f92a5bb2b3bec62406e6661a21fea43275b languageName: node linkType: hard -"@babel/plugin-transform-function-name@npm:^7.25.1": - version: 7.25.1 - resolution: "@babel/plugin-transform-function-name@npm:7.25.1" - dependencies: - "@babel/helper-compilation-targets": "npm:^7.24.8" - "@babel/helper-plugin-utils": "npm:^7.24.8" - "@babel/traverse": "npm:^7.25.1" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/e74912174d5e33d1418b840443c2e226a7b76cc017c1ed20ee30a566e4f1794d4a123be03180da046241576e8b692731807ba1f52608922acf1cb2cb6957593f +"@socket.io/component-emitter@npm:~3.1.0": + version: 3.1.2 + resolution: "@socket.io/component-emitter@npm:3.1.2" + checksum: 10c0/c4242bad66f67e6f7b712733d25b43cbb9e19a595c8701c3ad99cbeb5901555f78b095e24852f862fffb43e96f1d8552e62def885ca82ae1bb05da3668fd87d7 languageName: node linkType: hard -"@babel/plugin-transform-json-strings@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-json-strings@npm:7.24.7" +"@stakingbrain/brain@workspace:^0.1.0, @stakingbrain/brain@workspace:packages/brain": + version: 0.0.0-use.local + resolution: "@stakingbrain/brain@workspace:packages/brain" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/plugin-syntax-json-strings": "npm:^7.8.3" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/17c72cd5bf3e90e722aabd333559275f3309e3fa0b9cea8c2944ab83ae01502c71a2be05da5101edc02b3fc8df15a8dbb9b861cbfcc8a52bf5e797cf01d3a40a - languageName: node - linkType: hard + "@stakingbrain/common": "workspace:^0.1.0" + "@types/cors": "npm:^2.8.19" + "@types/express": "npm:^5.0.6" + "@types/mocha": "npm:^10.0.10" + "@types/sinon": "npm:^22.0.0" + cors: "npm:^2.8.6" + dotenv: "npm:^18.0.8" + express: "npm:^5.3.0" + lowdb: "npm:^7.0.1" + mocha: "npm:^12.0.3" + postgres: "npm:^3.4.9" + rimraf: "npm:^6.1.3" + sinon: "npm:^22.1.0" + socket.io: "npm:^4.8.4" + typescript: "npm:~6.0.3" + languageName: unknown + linkType: soft -"@babel/plugin-transform-literals@npm:^7.25.2": - version: 7.25.2 - resolution: "@babel/plugin-transform-literals@npm:7.25.2" +"@stakingbrain/common@workspace:^0.1.0, @stakingbrain/common@workspace:packages/common": + version: 0.0.0-use.local + resolution: "@stakingbrain/common@workspace:packages/common" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/0796883217b0885d37e7f6d350773be349e469a812b6bf11ccf862a6edf65103d3e7c849529d65381b441685c12e756751d8c2489a0fd3f8139bb5ef93185f58 - languageName: node - linkType: hard + "@types/sinon": "npm:^22.0.0" + sinon: "npm:^22.1.0" + typescript: "npm:~6.0.3" + languageName: unknown + linkType: soft -"@babel/plugin-transform-logical-assignment-operators@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-logical-assignment-operators@npm:7.24.7" +"@stakingbrain/ui@workspace:packages/ui": + version: 0.0.0-use.local + resolution: "@stakingbrain/ui@workspace:packages/ui" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/plugin-syntax-logical-assignment-operators": "npm:^7.10.4" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/dbe882eb9053931f2ab332c50fc7c2a10ef507d6421bd9831adbb4cb7c9f8e1e5fbac4fbd2e007f6a1bf1df1843547559434012f118084dc0bf42cda3b106272 + "@emotion/react": "npm:^11.14.0" + "@emotion/styled": "npm:^11.14.1" + "@mui/icons-material": "npm:^7.3.11" + "@mui/material": "npm:^7.3.11" + "@mui/system": "npm:^7.3.11" + "@mui/x-data-grid": "npm:^8.29.5" + "@stakingbrain/brain": "workspace:^0.1.0" + "@stakingbrain/common": "workspace:^0.1.0" + "@types/react": "npm:^19.3.0" + "@types/react-dom": "npm:^19.3.0" + "@vitejs/plugin-react": "npm:^6.1.2" + react: "npm:^19.3.1" + react-dom: "npm:^19.3.1" + react-dropzone: "npm:^20.1.4" + react-router-dom: "npm:^7.18.4" + socket.io-client: "npm:^4.8.4" + vite: "npm:^8.3.4" + languageName: unknown + linkType: soft + +"@types/body-parser@npm:*": + version: 1.19.6 + resolution: "@types/body-parser@npm:1.19.6" + dependencies: + "@types/connect": "npm:*" + "@types/node": "npm:*" + checksum: 10c0/542da05c924dce58ee23f50a8b981fee36921850c82222e384931fda3e106f750f7880c47be665217d72dbe445129049db6eb1f44e7a06b09d62af8f3cca8ea7 languageName: node linkType: hard -"@babel/plugin-transform-member-expression-literals@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-member-expression-literals@npm:7.24.7" +"@types/chai@npm:^5.2.3": + version: 5.2.3 + resolution: "@types/chai@npm:5.2.3" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/e789ae359bdf2d20e90bedef18dfdbd965c9ebae1cee398474a0c349590fda7c8b874e1a2ceee62e47e5e6ec1730e76b0f24e502164357571854271fc12cc684 + "@types/deep-eql": "npm:*" + assertion-error: "npm:^2.0.1" + checksum: 10c0/e0ef1de3b6f8045a5e473e867c8565788c444271409d155588504840ad1a53611011f85072188c2833941189400228c1745d78323dac13fcede9c2b28bacfb2f languageName: node linkType: hard -"@babel/plugin-transform-modules-amd@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-modules-amd@npm:7.24.7" +"@types/connect@npm:*": + version: 3.4.38 + resolution: "@types/connect@npm:3.4.38" dependencies: - "@babel/helper-module-transforms": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/6df7de7fce34117ca4b2fa07949b12274c03668cbfe21481c4037b6300796d50ae40f4f170527b61b70a67f26db906747797e30dbd0d9809a441b6e220b5728f + "@types/node": "npm:*" + checksum: 10c0/2e1cdba2c410f25649e77856505cd60223250fa12dff7a503e492208dbfdd25f62859918f28aba95315251fd1f5e1ffbfca1e25e73037189ab85dd3f8d0a148c languageName: node linkType: hard -"@babel/plugin-transform-modules-commonjs@npm:^7.24.7, @babel/plugin-transform-modules-commonjs@npm:^7.24.8": - version: 7.24.8 - resolution: "@babel/plugin-transform-modules-commonjs@npm:7.24.8" +"@types/cors@npm:^2.8.12, @types/cors@npm:^2.8.19": + version: 2.8.19 + resolution: "@types/cors@npm:2.8.19" dependencies: - "@babel/helper-module-transforms": "npm:^7.24.8" - "@babel/helper-plugin-utils": "npm:^7.24.8" - "@babel/helper-simple-access": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/f1cf552307ebfced20d3907c1dd8be941b277f0364aa655e2b5fee828c84c54065745183104dae86f1f93ea0406db970a463ef7ceaaed897623748e99640e5a7 + "@types/node": "npm:*" + checksum: 10c0/b5dd407040db7d8aa1bd36e79e5f3f32292f6b075abc287529e9f48df1a25fda3e3799ba30b4656667ffb931d3b75690c1d6ca71e39f7337ea6dfda8581916d0 languageName: node linkType: hard -"@babel/plugin-transform-modules-systemjs@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/plugin-transform-modules-systemjs@npm:7.25.0" - dependencies: - "@babel/helper-module-transforms": "npm:^7.25.0" - "@babel/helper-plugin-utils": "npm:^7.24.8" - "@babel/helper-validator-identifier": "npm:^7.24.7" - "@babel/traverse": "npm:^7.25.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/fca6198da71237e4bb1274b3b67a0c81d56013c9535361242b6bfa87d70a9597854aadb45d4d8203369be4a655e158be2a5d20af0040b1f8d1bfc47db3ad7b68 +"@types/deep-eql@npm:*": + version: 4.0.2 + resolution: "@types/deep-eql@npm:4.0.2" + checksum: 10c0/bf3f811843117900d7084b9d0c852da9a044d12eb40e6de73b552598a6843c21291a8a381b0532644574beecd5e3491c5ff3a0365ab86b15d59862c025384844 languageName: node linkType: hard -"@babel/plugin-transform-modules-umd@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-modules-umd@npm:7.24.7" - dependencies: - "@babel/helper-module-transforms": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/7791d290121db210e4338b94b4a069a1a79e4c7a8d7638d8159a97b281851bbed3048dac87a4ae718ad963005e6c14a5d28e6db2eeb2b04e031cee92fb312f85 +"@types/esrecurse@npm:^4.3.1": + version: 4.3.1 + resolution: "@types/esrecurse@npm:4.3.1" + checksum: 10c0/90dad74d5da3ad27606d8e8e757322f33171cfeaa15ad558b615cf71bb2a516492d18f55f4816384685a3eb2412142e732bbae9a4a7cd2cf3deb7572aa4ebe03 languageName: node linkType: hard -"@babel/plugin-transform-named-capturing-groups-regex@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-named-capturing-groups-regex@npm:7.24.7" - dependencies: - "@babel/helper-create-regexp-features-plugin": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10c0/41a0b0f2d0886318237440aa3b489f6d0305361d8671121777d9ff89f9f6de9d0c02ce93625049061426c8994064ef64deae8b819d1b14c00374a6a2336fb5d9 +"@types/estree@npm:^1.0.6, @types/estree@npm:^1.0.8": + version: 1.0.9 + resolution: "@types/estree@npm:1.0.9" + checksum: 10c0/3ad3286ca2988cd550dafb8f2ad599c8474868e954fa601a36655bdfefd8039f7c714b8c1c7f2ae219ffbd58bd4660e66fa7479a0120fc02d4777057d4865387 languageName: node linkType: hard -"@babel/plugin-transform-new-target@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-new-target@npm:7.24.7" +"@types/express-serve-static-core@npm:^5.0.0": + version: 5.1.3 + resolution: "@types/express-serve-static-core@npm:5.1.3" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/2540808a35e1a978e537334c43dab439cf24c93e7beb213a2e71902f6710e60e0184316643790c0a6644e7a8021e52f7ab8165e6b3e2d6651be07bdf517b67df + "@types/node": "npm:*" + "@types/qs": "npm:*" + "@types/range-parser": "npm:*" + "@types/send": "npm:*" + checksum: 10c0/2312bf52f2c299fdaf3b0c60c6058dd102f7bb6ed721b6de94e334feead4a4e4b606717058400436aa841cd9499e6960a4e3d92dd2b00220a6c1435ea5fdb5e1 languageName: node linkType: hard -"@babel/plugin-transform-nullish-coalescing-operator@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-nullish-coalescing-operator@npm:7.24.7" +"@types/express@npm:^5.0.6": + version: 5.0.6 + resolution: "@types/express@npm:5.0.6" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/plugin-syntax-nullish-coalescing-operator": "npm:^7.8.3" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/7243c8ff734ed5ef759dd8768773c4b443c12e792727e759a1aec2c7fa2bfdd24f1ecb42e292a7b3d8bd3d7f7b861cf256a8eb4ba144fc9cc463892c303083d9 + "@types/body-parser": "npm:*" + "@types/express-serve-static-core": "npm:^5.0.0" + "@types/serve-static": "npm:^2" + checksum: 10c0/f1071e3389a955d4f9a38aae38634121c7cd9b3171ba4201ec9b56bd534aba07866839d278adc0dda05b942b05a901a02fd174201c3b1f70ce22b10b6c68f24b languageName: node linkType: hard -"@babel/plugin-transform-numeric-separator@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-numeric-separator@npm:7.24.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/plugin-syntax-numeric-separator": "npm:^7.10.4" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/e18e09ca5a6342645d00ede477731aa6e8714ff357efc9d7cda5934f1703b3b6fb7d3298dce3ce3ba53e9ff1158eab8f1aadc68874cc21a6099d33a1ca457789 +"@types/http-errors@npm:*": + version: 2.0.5 + resolution: "@types/http-errors@npm:2.0.5" + checksum: 10c0/00f8140fbc504f47356512bd88e1910c2f07e04233d99c88c854b3600ce0523c8cd0ba7d1897667243282eb44c59abb9245959e2428b9de004f93937f52f7c15 languageName: node linkType: hard -"@babel/plugin-transform-object-rest-spread@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-object-rest-spread@npm:7.24.7" - dependencies: - "@babel/helper-compilation-targets": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/plugin-syntax-object-rest-spread": "npm:^7.8.3" - "@babel/plugin-transform-parameters": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/9ad64bc003f583030f9da50614b485852f8edac93f8faf5d1cd855201a4852f37c5255ae4daf70dd4375bdd4874e16e39b91f680d4668ec219ba05441ce286eb +"@types/json-schema@npm:^7.0.15": + version: 7.0.15 + resolution: "@types/json-schema@npm:7.0.15" + checksum: 10c0/a996a745e6c5d60292f36731dd41341339d4eeed8180bb09226e5c8d23759067692b1d88e5d91d72ee83dfc00d3aca8e7bd43ea120516c17922cbcb7c3e252db languageName: node linkType: hard -"@babel/plugin-transform-object-super@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-object-super@npm:7.24.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/helper-replace-supers": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/770cebb4b4e1872c216b17069db9a13b87dfee747d359dc56d9fcdd66e7544f92dc6ab1861a4e7e0528196aaff2444e4f17dc84efd8eaf162d542b4ba0943869 +"@types/mocha@npm:^10.0.10": + version: 10.0.10 + resolution: "@types/mocha@npm:10.0.10" + checksum: 10c0/d2b8c48138cde6923493e42b38e839695eb42edd04629abe480a8f34c0e3f50dd82a55832c2e8d2b6e6f9e4deb492d7d733e600fbbdd5a0ceccbcfc6844ff9d5 languageName: node linkType: hard -"@babel/plugin-transform-optional-catch-binding@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-optional-catch-binding@npm:7.24.7" +"@types/node@npm:*, @types/node@npm:>=10.0.0": + version: 26.6.5 + resolution: "@types/node@npm:26.6.5" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/plugin-syntax-optional-catch-binding": "npm:^7.8.3" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/1e2f10a018f7d03b3bde6c0b70d063df8d5dd5209861d4467726cf834f5e3d354e2276079dc226aa8e6ece35f5c9b264d64b8229a8bb232829c01e561bcfb07a + undici-types: "npm:~8.9.0" + checksum: 10c0/4f791f69466ace7842c09e7bef96cb1ddbb65cf3db916ef7e84f0e2b2a17220b6b816dc7539d01520cadfb17ed7169b8214910900493e0a83be8bc7d124ec20a languageName: node linkType: hard -"@babel/plugin-transform-optional-chaining@npm:^7.24.7, @babel/plugin-transform-optional-chaining@npm:^7.24.8": - version: 7.24.8 - resolution: "@babel/plugin-transform-optional-chaining@npm:7.24.8" +"@types/node@npm:^24.19.2": + version: 24.19.2 + resolution: "@types/node@npm:24.19.2" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" - "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.24.7" - "@babel/plugin-syntax-optional-chaining": "npm:^7.8.3" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/4ffbe1aad7dec7c9aa2bf6ceb4b2f91f96815b2784f2879bde80e46934f59d64a12cb2c6262e40897c4754d77d2c35d8a5cfed63044fdebf94978b1ed3d14b17 + undici-types: "npm:>=7.24.0 <7.24.7" + checksum: 10c0/b51a7f8854007ad579dffdd0633d8addbfeb2c97cceb380f2eba0ed163638ec52e17565385a6cc04d54ac4ff99c70c64aee68a4ca45b6128c035e828794e29c5 languageName: node linkType: hard -"@babel/plugin-transform-parameters@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-parameters@npm:7.24.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/53bf190d6926771545d5184f1f5f3f5144d0f04f170799ad46a43f683a01fab8d5fe4d2196cf246774530990c31fe1f2b9f0def39f0a5ddbb2340b924f5edf01 +"@types/parse-json@npm:^4.0.0": + version: 4.0.2 + resolution: "@types/parse-json@npm:4.0.2" + checksum: 10c0/b1b863ac34a2c2172fbe0807a1ec4d5cb684e48d422d15ec95980b81475fac4fdb3768a8b13eef39130203a7c04340fc167bae057c7ebcafd7dec9fe6c36aeb1 languageName: node linkType: hard -"@babel/plugin-transform-private-methods@npm:^7.25.4": - version: 7.25.4 - resolution: "@babel/plugin-transform-private-methods@npm:7.25.4" - dependencies: - "@babel/helper-create-class-features-plugin": "npm:^7.25.4" - "@babel/helper-plugin-utils": "npm:^7.24.8" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/7abdb427c3984a2c8a2e9d806297d8509b02f78a3501b7760e544be532446e9df328b876daa8fc38718f3dce7ccc45083016ee7aeaab169b81c142bc18700794 +"@types/prop-types@npm:^15.7.15": + version: 15.7.15 + resolution: "@types/prop-types@npm:15.7.15" + checksum: 10c0/b59aad1ad19bf1733cf524fd4e618196c6c7690f48ee70a327eb450a42aab8e8a063fbe59ca0a5701aebe2d92d582292c0fb845ea57474f6a15f6994b0e260b2 languageName: node linkType: hard -"@babel/plugin-transform-private-property-in-object@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-private-property-in-object@npm:7.24.7" - dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.24.7" - "@babel/helper-create-class-features-plugin": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/plugin-syntax-private-property-in-object": "npm:^7.14.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/c6fa7defb90b1b0ed46f24ff94ff2e77f44c1f478d1090e81712f33cf992dda5ba347016f030082a2f770138bac6f4a9c2c1565e9f767a125901c77dd9c239ba +"@types/qs@npm:*": + version: 6.15.1 + resolution: "@types/qs@npm:6.15.1" + checksum: 10c0/1dfdbcb4cf2a8f66d57f0b9a9fe6b1c7091cb816687b6698c1351eaf31f62e412cea9b7453a9637b570cd5fad8dced527e5a9e69b4fcc6e318daacd8b749f094 languageName: node linkType: hard -"@babel/plugin-transform-property-literals@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-property-literals@npm:7.24.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/52564b58f3d111dc02d241d5892a4b01512e98dfdf6ef11b0ed62f8b11b0acacccef0fc229b44114fe8d1a57a8b70780b11bdd18b807d3754a781a07d8f57433 +"@types/range-parser@npm:*": + version: 1.2.7 + resolution: "@types/range-parser@npm:1.2.7" + checksum: 10c0/361bb3e964ec5133fa40644a0b942279ed5df1949f21321d77de79f48b728d39253e5ce0408c9c17e4e0fd95ca7899da36841686393b9f7a1e209916e9381a3c languageName: node linkType: hard -"@babel/plugin-transform-react-display-name@npm:^7.16.0, @babel/plugin-transform-react-display-name@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-react-display-name@npm:7.24.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" +"@types/react-dom@npm:^19.3.0": + version: 19.3.0 + resolution: "@types/react-dom@npm:19.3.0" peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/c14a07a9e75723c96f1a0a306b8a8e899ff1c6a0cc3d62bcda79bb1b54e4319127b258651c513a1a47da152cdc22e16525525a30ae5933a2980c7036fd0b4d24 + "@types/react": ^19.3.0 + checksum: 10c0/ce0066d28b62bc6afdd17ca84756b8fbe0f6ffc0c8b1e60739abc30e81510da2edbc1007e6ab3d974d30f5a8c25bb0f7de8d7ce09736c909fe84c0f9aed0b946 languageName: node linkType: hard -"@babel/plugin-transform-react-jsx-development@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-react-jsx-development@npm:7.24.7" - dependencies: - "@babel/plugin-transform-react-jsx": "npm:^7.24.7" +"@types/react-transition-group@npm:^4.4.12": + version: 4.4.12 + resolution: "@types/react-transition-group@npm:4.4.12" peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/fce647db50f90a5291681f0f97865d9dc76981262dff71d6d0332e724b85343de5860c26f9e9a79e448d61e1d70916b07ce91e8c7f2b80dceb4b16aee41794d8 + "@types/react": "*" + checksum: 10c0/0441b8b47c69312c89ec0760ba477ba1a0808a10ceef8dc1c64b1013ed78517332c30f18681b0ec0b53542731f1ed015169fed1d127cc91222638ed955478ec7 languageName: node linkType: hard -"@babel/plugin-transform-react-jsx-self@npm:^7.24.5": - version: 7.24.7 - resolution: "@babel/plugin-transform-react-jsx-self@npm:7.24.7" +"@types/react@npm:^19.3.0": + version: 19.3.0 + resolution: "@types/react@npm:19.3.0" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/dcf3b732401f47f06bb29d6016e48066f66de00029a0ded98ddd9983c770a00a109d91cd04d2700d15ee0bcec3ae3027a5f12d69e15ec56efc0bcbfac65e92cb + csstype: "npm:^3.2.2" + checksum: 10c0/fbcabf303f935ca9f36ed2dcf81751cf84744098c5a683c8a62a0fa7edd31714b48d5c46b12fd0014f7f2dfe4d9ec6de428a850748eec719a70294e51029551b languageName: node linkType: hard -"@babel/plugin-transform-react-jsx-source@npm:^7.24.1": - version: 7.24.7 - resolution: "@babel/plugin-transform-react-jsx-source@npm:7.24.7" +"@types/send@npm:*": + version: 1.2.1 + resolution: "@types/send@npm:1.2.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/970ef1264c7c6c416ab11610665d5309aec2bd2b9086ae394e1132e65138d97b060a7dc9d31054e050d6dc475b5a213938c9707c0202a5022d55dcb4c5abe28f + "@types/node": "npm:*" + checksum: 10c0/7673747f8c2d8e67f3b1b3b57e9d4d681801a4f7b526ecf09987bb9a84a61cf94aa411c736183884dc762c1c402a61681eb1ef200d8d45d7e5ec0ab67ea5f6c1 languageName: node linkType: hard -"@babel/plugin-transform-react-jsx@npm:^7.22.15": - version: 7.22.15 - resolution: "@babel/plugin-transform-react-jsx@npm:7.22.15" +"@types/serve-static@npm:^2": + version: 2.2.0 + resolution: "@types/serve-static@npm:2.2.0" dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.22.5" - "@babel/helper-module-imports": "npm:^7.22.15" - "@babel/helper-plugin-utils": "npm:^7.22.5" - "@babel/plugin-syntax-jsx": "npm:^7.22.5" - "@babel/types": "npm:^7.22.15" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/db37491e3eea5530521e177380312f308f01f806866fa0ce08d48fc5a8c9eaf9a954f778fa1ff477248afb72e916eb66ab3d35254bb6a8979f8b8e74a0fd8873 + "@types/http-errors": "npm:*" + "@types/node": "npm:*" + checksum: 10c0/a3c6126bdbf9685e6c7dc03ad34639666eff32754e912adeed9643bf3dd3aa0ff043002a7f69039306e310d233eb8e160c59308f95b0a619f32366bbc48ee094 languageName: node linkType: hard -"@babel/plugin-transform-react-jsx@npm:^7.24.7": - version: 7.25.2 - resolution: "@babel/plugin-transform-react-jsx@npm:7.25.2" +"@types/sinon@npm:^22.0.0": + version: 22.0.0 + resolution: "@types/sinon@npm:22.0.0" dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.24.7" - "@babel/helper-module-imports": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.8" - "@babel/plugin-syntax-jsx": "npm:^7.24.7" - "@babel/types": "npm:^7.25.2" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/8c5b515f38118471197605e02bea54a8a4283010e3c55bad8cfb78de59ad63612b14d40baca63689afdc9d57b147aac4c7794fe5f7736c9e1ed6dd38784be624 + "@types/sinonjs__fake-timers": "npm:*" + checksum: 10c0/ee665a33b5fb639e1d552b1064cbfc911ccdf8523d48ee53098e82b099279d6f18b473da858740374b8a9dd607f65884c67923db822be21681249d2ac418e3cd languageName: node linkType: hard -"@babel/plugin-transform-react-pure-annotations@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-react-pure-annotations@npm:7.24.7" - dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/fae517d293d9c93b7b920458c3e4b91cb0400513889af41ba184a5f3acc8bfef27242cc262741bb8f87870df376f1733a0d0f52b966d342e2aaaf5607af8f73d +"@types/sinonjs__fake-timers@npm:*": + version: 15.0.1 + resolution: "@types/sinonjs__fake-timers@npm:15.0.1" + checksum: 10c0/be8daa4e19746f4c249ce4926b83676bb2e5500a8c33b61d81cd303495b56697c28c910f7c57c0ecfd1d7c8ead87449570586d9568b0b5c6fd94c45f116197f4 languageName: node linkType: hard -"@babel/plugin-transform-regenerator@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-regenerator@npm:7.24.7" +"@types/ws@npm:^8.5.12": + version: 8.18.2 + resolution: "@types/ws@npm:8.18.2" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - regenerator-transform: "npm:^0.15.2" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/d2dc2c788fdae9d97217e70d46ba8ca9db0035c398dc3e161552b0c437113719a75c04f201f9c91ddc8d28a1da60d0b0853f616dead98a396abb9c845c44892b + "@types/node": "npm:*" + checksum: 10c0/8cf79499d26df06ffc27109fc86f1400371a5932082a4c1269be3f11e22b9e5a403a4ba2c1a3245d5fae420c270571aa833142a3d7fdce8f6cd24a45c52a98e4 languageName: node linkType: hard -"@babel/plugin-transform-reserved-words@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-reserved-words@npm:7.24.7" +"@typescript-eslint/eslint-plugin@npm:8.71.2": + version: 8.71.2 + resolution: "@typescript-eslint/eslint-plugin@npm:8.71.2" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@eslint-community/regexpp": "npm:^4.12.2" + "@typescript-eslint/scope-manager": "npm:8.71.2" + "@typescript-eslint/type-utils": "npm:8.71.2" + "@typescript-eslint/utils": "npm:8.71.2" + "@typescript-eslint/visitor-keys": "npm:8.71.2" + ignore: "npm:^7.0.5" + natural-compare: "npm:^1.4.0" + ts-api-utils: "npm:^2.5.0" peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/2229de2768615e7f5dc0bbc55bc121b5678fd6d2febd46c74a58e42bb894d74cd5955c805880f4e02d0e1cf94f6886270eda7fafc1be9305a1ec3b9fd1d063f5 + "@typescript-eslint/parser": ^8.71.2 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/977add368d9fdb30d175b15fcf68d0b4ab721aeb89b185a2dc2a30b95e90a08d9def66573944f49fd472f87beb73edda38b98e0d25ab1078d7cb5e56802588d7 languageName: node linkType: hard -"@babel/plugin-transform-runtime@npm:^7.16.4": - version: 7.25.4 - resolution: "@babel/plugin-transform-runtime@npm:7.25.4" +"@typescript-eslint/parser@npm:8.71.2": + version: 8.71.2 + resolution: "@typescript-eslint/parser@npm:8.71.2" dependencies: - "@babel/helper-module-imports": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.8" - babel-plugin-polyfill-corejs2: "npm:^0.4.10" - babel-plugin-polyfill-corejs3: "npm:^0.10.6" - babel-plugin-polyfill-regenerator: "npm:^0.6.1" - semver: "npm:^6.3.1" + "@typescript-eslint/scope-manager": "npm:8.71.2" + "@typescript-eslint/types": "npm:8.71.2" + "@typescript-eslint/typescript-estree": "npm:8.71.2" + "@typescript-eslint/visitor-keys": "npm:8.71.2" + debug: "npm:^4.4.3" peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/c08698276596d58bf49e222ead3c414c35d099a7e5a6174b11e2db9b74420e94783ada596820437622c3eccc8852c0e750ad053bd8e775f0050839479ba76e6a + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/8c94eb5464beda9bcbd58e63a402fac10ab382dbc2e4506473902bc1e957f425f4ca9d59916b3f2d86f835d36cbbd27d362a5c2977b0778d83203111be7bcf69 languageName: node linkType: hard -"@babel/plugin-transform-shorthand-properties@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-shorthand-properties@npm:7.24.7" +"@typescript-eslint/project-service@npm:8.71.2": + version: 8.71.2 + resolution: "@typescript-eslint/project-service@npm:8.71.2" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@typescript-eslint/tsconfig-utils": "npm:^8.71.2" + "@typescript-eslint/types": "npm:^8.71.2" + debug: "npm:^4.4.3" peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/41b155bdbb3be66618358488bf7731b3b2e8fff2de3dbfd541847720a9debfcec14db06a117abedd03c9cd786db20a79e2a86509a4f19513f6e1b610520905cf + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/4fa4f978ee748b1045ca5945b17cd8106066ee3d1695ce6b4e5f0bc8ffdb553fd36bf7103e2a5e86a2bf6cf9a92a08d9a7d77422e6716f8532c386bec122d4ee languageName: node linkType: hard -"@babel/plugin-transform-spread@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-spread@npm:7.24.7" +"@typescript-eslint/scope-manager@npm:8.71.2": + version: 8.71.2 + resolution: "@typescript-eslint/scope-manager@npm:8.71.2" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/facba1553035f76b0d2930d4ada89a8cd0f45b79579afd35baefbfaf12e3b86096995f4b0c402cf9ee23b3f2ea0a4460c3b1ec0c192d340962c948bb223d4e66 + "@typescript-eslint/types": "npm:8.71.2" + "@typescript-eslint/visitor-keys": "npm:8.71.2" + checksum: 10c0/03096fad20a672916c1231d2d472da1e3d7e65f99b17048cc6bdd20c13c88b27f4a26c5712788cbac2130e2e44cb9dcb5c8fb39bbb8f974ee5d4691c8fcefce2 languageName: node linkType: hard -"@babel/plugin-transform-sticky-regex@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-sticky-regex@npm:7.24.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" +"@typescript-eslint/tsconfig-utils@npm:8.71.2, @typescript-eslint/tsconfig-utils@npm:^8.71.2": + version: 8.71.2 + resolution: "@typescript-eslint/tsconfig-utils@npm:8.71.2" peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/5a74ed2ed0a3ab51c3d15fcaf09d9e2fe915823535c7a4d7b019813177d559b69677090e189ec3d5d08b619483eb5ad371fbcfbbff5ace2a76ba33ee566a1109 + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/a7ad3f656c8dc30e1181d3c2a301db5f82869ad5b77fcb9ae3ba529162c8b7937dbe8342e93829a3231fa2cc8d7d1a9fef13bfdb74f2449ce606db38ec23bf1d languageName: node linkType: hard -"@babel/plugin-transform-template-literals@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-template-literals@npm:7.24.7" +"@typescript-eslint/type-utils@npm:8.71.2": + version: 8.71.2 + resolution: "@typescript-eslint/type-utils@npm:8.71.2" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@typescript-eslint/types": "npm:8.71.2" + "@typescript-eslint/typescript-estree": "npm:8.71.2" + "@typescript-eslint/utils": "npm:8.71.2" + debug: "npm:^4.4.3" + ts-api-utils: "npm:^2.5.0" peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/3630f966257bcace122f04d3157416a09d40768c44c3a800855da81146b009187daa21859d1c3b7d13f4e19e8888e60613964b175b2275d451200fb6d8d6cfe6 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/85cf2d5f8c85226f478f96d71d031aa9aa3714409661448520cc5438084705e3078e4e4a32044cd180443b9a31ef9e1257ebc11cf5589a314c2106ebbc577b51 languageName: node linkType: hard -"@babel/plugin-transform-typeof-symbol@npm:^7.24.8": - version: 7.24.8 - resolution: "@babel/plugin-transform-typeof-symbol@npm:7.24.8" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.8" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/2f570a4fbbdc5fd85f48165a97452826560051e3b8efb48c3bb0a0a33ee8485633439e7b71bfe3ef705583a1df43f854f49125bd759abdedc195b2cf7e60012a +"@typescript-eslint/types@npm:8.71.2, @typescript-eslint/types@npm:^8.71.2": + version: 8.71.2 + resolution: "@typescript-eslint/types@npm:8.71.2" + checksum: 10c0/aa8044572cef1eb38a2c4cc2410989cf9869464673e99c3adadbdc89f4af9c6e6e87e874eb90993c51e60e6309a0b60f88d6a7c7e2783d480379814878b68062 languageName: node linkType: hard -"@babel/plugin-transform-typescript@npm:^7.24.7": - version: 7.25.2 - resolution: "@babel/plugin-transform-typescript@npm:7.25.2" +"@typescript-eslint/typescript-estree@npm:8.71.2": + version: 8.71.2 + resolution: "@typescript-eslint/typescript-estree@npm:8.71.2" dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.24.7" - "@babel/helper-create-class-features-plugin": "npm:^7.25.0" - "@babel/helper-plugin-utils": "npm:^7.24.8" - "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.24.7" - "@babel/plugin-syntax-typescript": "npm:^7.24.7" + "@typescript-eslint/project-service": "npm:8.71.2" + "@typescript-eslint/tsconfig-utils": "npm:8.71.2" + "@typescript-eslint/types": "npm:8.71.2" + "@typescript-eslint/visitor-keys": "npm:8.71.2" + debug: "npm:^4.4.3" + minimatch: "npm:^10.2.2" + semver: "npm:^7.7.3" + tinyglobby: "npm:^0.2.15" + ts-api-utils: "npm:^2.5.0" peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/b3c941da39ee7ecf72df1b78a01d4108160438245f2ab61befe182f51d17fd0034733c6d079b7efad81e03a66438aa3881a671cd68c5eb0fc775df86b88df996 + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/1120f9b57cc38824722cd47123307591b543693dbdef7fa69fd89ecaa7ae5ebb450989630db785387e5bc1bf20363de34e4cf105be95a11e01122befff8921fb languageName: node linkType: hard -"@babel/plugin-transform-unicode-escapes@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-unicode-escapes@npm:7.24.7" +"@typescript-eslint/utils@npm:8.71.2": + version: 8.71.2 + resolution: "@typescript-eslint/utils@npm:8.71.2" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@eslint-community/eslint-utils": "npm:^4.9.1" + "@typescript-eslint/scope-manager": "npm:8.71.2" + "@typescript-eslint/types": "npm:8.71.2" + "@typescript-eslint/typescript-estree": "npm:8.71.2" peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/8b18e2e66af33471a6971289492beff5c240e56727331db1d34c4338a6a368a82a7ed6d57ec911001b6d65643aed76531e1e7cac93265fb3fb2717f54d845e69 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/557bfe8d459bc23714bdb0d622d6e5ddf44446d3bbd3423efb14cc0a55bebf7bf2eb8bced090be27243d4f84900d161157bef4265e0271b05421c7b72d7dae81 languageName: node linkType: hard -"@babel/plugin-transform-unicode-property-regex@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-unicode-property-regex@npm:7.24.7" +"@typescript-eslint/visitor-keys@npm:8.71.2": + version: 8.71.2 + resolution: "@typescript-eslint/visitor-keys@npm:8.71.2" dependencies: - "@babel/helper-create-regexp-features-plugin": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/bc57656eb94584d1b74a385d378818ac2b3fca642e3f649fead8da5fb3f9de22f8461185936915dfb33d5a9104e62e7a47828331248b09d28bb2d59e9276de3e + "@typescript-eslint/types": "npm:8.71.2" + eslint-visitor-keys: "npm:^5.0.0" + checksum: 10c0/0ba949d5a4e951ce6f361b686d14c38639debbfa450beb4261f6bef53af797d6339ff320086e69fe857c5c91ba68bf46943b51a0369f9436bad1565e407b3e7e languageName: node linkType: hard -"@babel/plugin-transform-unicode-regex@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-unicode-regex@npm:7.24.7" +"@vitejs/plugin-react@npm:^6.1.2": + version: 6.1.2 + resolution: "@vitejs/plugin-react@npm:6.1.2" dependencies: - "@babel/helper-create-regexp-features-plugin": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@rolldown/pluginutils": "npm:^1.0.1" peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/83f72a345b751566b601dc4d07e9f2c8f1bc0e0c6f7abb56ceb3095b3c9d304de73f85f2f477a09f8cc7edd5e65afd0ff9e376cdbcbea33bc0c28f3705b38fd9 + "@rolldown/plugin-babel": ^0.1.7 || ^0.2.0 + babel-plugin-react-compiler: ^1.0.0 + oxc-transform-react: ^0.152.0 + vite: ^8.0.0 + peerDependenciesMeta: + "@rolldown/plugin-babel": + optional: true + babel-plugin-react-compiler: + optional: true + oxc-transform-react: + optional: true + checksum: 10c0/5c49e9098455e40662c60e7e3daf81088f8f3dc94dbd55746c310fc08a47962321c432dab202d5c240c91bf951c6a62ea64784b183e7ed47e6f9378d6e00e565 languageName: node linkType: hard -"@babel/plugin-transform-unicode-sets-regex@npm:^7.25.4": - version: 7.25.4 - resolution: "@babel/plugin-transform-unicode-sets-regex@npm:7.25.4" - dependencies: - "@babel/helper-create-regexp-features-plugin": "npm:^7.25.2" - "@babel/helper-plugin-utils": "npm:^7.24.8" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10c0/f65749835a98d8d6242e961f9276bdcdb09020e791d151ccc145acaca9a66f025b2c7cb761104f139180d35eb066a429596ee6edece81f5fd9244e0edb97d7ec - languageName: node - linkType: hard - -"@babel/preset-env@npm:^7.16.4": - version: 7.25.4 - resolution: "@babel/preset-env@npm:7.25.4" - dependencies: - "@babel/compat-data": "npm:^7.25.4" - "@babel/helper-compilation-targets": "npm:^7.25.2" - "@babel/helper-plugin-utils": "npm:^7.24.8" - "@babel/helper-validator-option": "npm:^7.24.8" - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "npm:^7.25.3" - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "npm:^7.25.0" - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "npm:^7.25.0" - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "npm:^7.24.7" - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "npm:^7.25.0" - "@babel/plugin-proposal-private-property-in-object": "npm:7.21.0-placeholder-for-preset-env.2" - "@babel/plugin-syntax-async-generators": "npm:^7.8.4" - "@babel/plugin-syntax-class-properties": "npm:^7.12.13" - "@babel/plugin-syntax-class-static-block": "npm:^7.14.5" - "@babel/plugin-syntax-dynamic-import": "npm:^7.8.3" - "@babel/plugin-syntax-export-namespace-from": "npm:^7.8.3" - "@babel/plugin-syntax-import-assertions": "npm:^7.24.7" - "@babel/plugin-syntax-import-attributes": "npm:^7.24.7" - "@babel/plugin-syntax-import-meta": "npm:^7.10.4" - "@babel/plugin-syntax-json-strings": "npm:^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators": "npm:^7.10.4" - "@babel/plugin-syntax-nullish-coalescing-operator": "npm:^7.8.3" - "@babel/plugin-syntax-numeric-separator": "npm:^7.10.4" - "@babel/plugin-syntax-object-rest-spread": "npm:^7.8.3" - "@babel/plugin-syntax-optional-catch-binding": "npm:^7.8.3" - "@babel/plugin-syntax-optional-chaining": "npm:^7.8.3" - "@babel/plugin-syntax-private-property-in-object": "npm:^7.14.5" - "@babel/plugin-syntax-top-level-await": "npm:^7.14.5" - "@babel/plugin-syntax-unicode-sets-regex": "npm:^7.18.6" - "@babel/plugin-transform-arrow-functions": "npm:^7.24.7" - "@babel/plugin-transform-async-generator-functions": "npm:^7.25.4" - "@babel/plugin-transform-async-to-generator": "npm:^7.24.7" - "@babel/plugin-transform-block-scoped-functions": "npm:^7.24.7" - "@babel/plugin-transform-block-scoping": "npm:^7.25.0" - "@babel/plugin-transform-class-properties": "npm:^7.25.4" - "@babel/plugin-transform-class-static-block": "npm:^7.24.7" - "@babel/plugin-transform-classes": "npm:^7.25.4" - "@babel/plugin-transform-computed-properties": "npm:^7.24.7" - "@babel/plugin-transform-destructuring": "npm:^7.24.8" - "@babel/plugin-transform-dotall-regex": "npm:^7.24.7" - "@babel/plugin-transform-duplicate-keys": "npm:^7.24.7" - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "npm:^7.25.0" - "@babel/plugin-transform-dynamic-import": "npm:^7.24.7" - "@babel/plugin-transform-exponentiation-operator": "npm:^7.24.7" - "@babel/plugin-transform-export-namespace-from": "npm:^7.24.7" - "@babel/plugin-transform-for-of": "npm:^7.24.7" - "@babel/plugin-transform-function-name": "npm:^7.25.1" - "@babel/plugin-transform-json-strings": "npm:^7.24.7" - "@babel/plugin-transform-literals": "npm:^7.25.2" - "@babel/plugin-transform-logical-assignment-operators": "npm:^7.24.7" - "@babel/plugin-transform-member-expression-literals": "npm:^7.24.7" - "@babel/plugin-transform-modules-amd": "npm:^7.24.7" - "@babel/plugin-transform-modules-commonjs": "npm:^7.24.8" - "@babel/plugin-transform-modules-systemjs": "npm:^7.25.0" - "@babel/plugin-transform-modules-umd": "npm:^7.24.7" - "@babel/plugin-transform-named-capturing-groups-regex": "npm:^7.24.7" - "@babel/plugin-transform-new-target": "npm:^7.24.7" - "@babel/plugin-transform-nullish-coalescing-operator": "npm:^7.24.7" - "@babel/plugin-transform-numeric-separator": "npm:^7.24.7" - "@babel/plugin-transform-object-rest-spread": "npm:^7.24.7" - "@babel/plugin-transform-object-super": "npm:^7.24.7" - "@babel/plugin-transform-optional-catch-binding": "npm:^7.24.7" - "@babel/plugin-transform-optional-chaining": "npm:^7.24.8" - "@babel/plugin-transform-parameters": "npm:^7.24.7" - "@babel/plugin-transform-private-methods": "npm:^7.25.4" - "@babel/plugin-transform-private-property-in-object": "npm:^7.24.7" - "@babel/plugin-transform-property-literals": "npm:^7.24.7" - "@babel/plugin-transform-regenerator": "npm:^7.24.7" - "@babel/plugin-transform-reserved-words": "npm:^7.24.7" - "@babel/plugin-transform-shorthand-properties": "npm:^7.24.7" - "@babel/plugin-transform-spread": "npm:^7.24.7" - "@babel/plugin-transform-sticky-regex": "npm:^7.24.7" - "@babel/plugin-transform-template-literals": "npm:^7.24.7" - "@babel/plugin-transform-typeof-symbol": "npm:^7.24.8" - "@babel/plugin-transform-unicode-escapes": "npm:^7.24.7" - "@babel/plugin-transform-unicode-property-regex": "npm:^7.24.7" - "@babel/plugin-transform-unicode-regex": "npm:^7.24.7" - "@babel/plugin-transform-unicode-sets-regex": "npm:^7.25.4" - "@babel/preset-modules": "npm:0.1.6-no-external-plugins" - babel-plugin-polyfill-corejs2: "npm:^0.4.10" - babel-plugin-polyfill-corejs3: "npm:^0.10.6" - babel-plugin-polyfill-regenerator: "npm:^0.6.1" - core-js-compat: "npm:^3.37.1" - semver: "npm:^6.3.1" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/ed210a1974b5a1e7f80a933c87253907ec869457cea900bc97892642fa9a690c47627a9bac08a7c9495deb992a2b15f308ffca2741e1876ba47172c96fa27e14 +"abbrev@npm:^5.0.0": + version: 5.0.0 + resolution: "abbrev@npm:5.0.0" + checksum: 10c0/8e88f5c798ea4562d28c5a3e9ad69e3879890bc5d695d8f2dffb8609be4c890aacc8f80ef4553fdd2c6a62d70c2ce8bc57b38074e383beb7487bdafa9ed42ea5 languageName: node linkType: hard -"@babel/preset-modules@npm:0.1.6-no-external-plugins": - version: 0.1.6-no-external-plugins - resolution: "@babel/preset-modules@npm:0.1.6-no-external-plugins" +"accepts@npm:^2.0.0": + version: 2.0.0 + resolution: "accepts@npm:2.0.0" dependencies: - "@babel/helper-plugin-utils": "npm:^7.0.0" - "@babel/types": "npm:^7.4.4" - esutils: "npm:^2.0.2" - peerDependencies: - "@babel/core": ^7.0.0-0 || ^8.0.0-0 <8.0.0 - checksum: 10c0/9d02f70d7052446c5f3a4fb39e6b632695fb6801e46d31d7f7c5001f7c18d31d1ea8369212331ca7ad4e7877b73231f470b0d559162624128f1b80fe591409e6 + mime-types: "npm:^3.0.0" + negotiator: "npm:^1.0.0" + checksum: 10c0/98374742097e140891546076215f90c32644feacf652db48412329de4c2a529178a81aa500fbb13dd3e6cbf6e68d829037b123ac037fc9a08bcec4b87b358eef languageName: node linkType: hard -"@babel/preset-react@npm:^7.16.0": - version: 7.24.7 - resolution: "@babel/preset-react@npm:7.24.7" +"accepts@npm:~1.3.4": + version: 1.3.8 + resolution: "accepts@npm:1.3.8" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/helper-validator-option": "npm:^7.24.7" - "@babel/plugin-transform-react-display-name": "npm:^7.24.7" - "@babel/plugin-transform-react-jsx": "npm:^7.24.7" - "@babel/plugin-transform-react-jsx-development": "npm:^7.24.7" - "@babel/plugin-transform-react-pure-annotations": "npm:^7.24.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/9658b685b25cedaadd0b65c4e663fbc7f57394b5036ddb4c99b1a75b0711fb83292c1c625d605c05b73413fc7a6dc20e532627f6a39b6dc8d4e00415479b054c + mime-types: "npm:~2.1.34" + negotiator: "npm:0.6.3" + checksum: 10c0/3a35c5f5586cfb9a21163ca47a5f77ac34fa8ceb5d17d2fa2c0d81f41cbd7f8c6fa52c77e2c039acc0f4d09e71abdc51144246900f6bef5e3c4b333f77d89362 languageName: node linkType: hard -"@babel/preset-typescript@npm:^7.16.0": - version: 7.24.7 - resolution: "@babel/preset-typescript@npm:7.24.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" - "@babel/helper-validator-option": "npm:^7.24.7" - "@babel/plugin-syntax-jsx": "npm:^7.24.7" - "@babel/plugin-transform-modules-commonjs": "npm:^7.24.7" - "@babel/plugin-transform-typescript": "npm:^7.24.7" +"acorn-jsx@npm:^5.3.2": + version: 5.3.2 + resolution: "acorn-jsx@npm:5.3.2" peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/986bc0978eedb4da33aba8e1e13a3426dd1829515313b7e8f4ba5d8c18aff1663b468939d471814e7acf4045d326ae6cff37239878d169ac3fe53a8fde71f8ee + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + checksum: 10c0/4c54868fbef3b8d58927d5e33f0a4de35f59012fe7b12cf9dfbb345fb8f46607709e1c4431be869a23fb63c151033d84c4198fa9f79385cec34fcb1dd53974c1 languageName: node linkType: hard -"@babel/regjsgen@npm:^0.8.0": - version: 0.8.0 - resolution: "@babel/regjsgen@npm:0.8.0" - checksum: 10c0/4f3ddd8c7c96d447e05c8304c1d5ba3a83fcabd8a716bc1091c2f31595cdd43a3a055fff7cb5d3042b8cb7d402d78820fcb4e05d896c605a7d8bcf30f2424c4a +"acorn@npm:^8.16.0": + version: 8.19.0 + resolution: "acorn@npm:8.19.0" + bin: + acorn: bin/acorn + checksum: 10c0/21007e0ca638dd13d44a0ad26856b7a885cc6c4319cd2bc71694bc8ec3f0606c23ecd12b157e167f909d9d2b2d68d5b50fe3f54211b1aab7c3451a7c801fe62e languageName: node linkType: hard -"@babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.9, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.7": - version: 7.21.0 - resolution: "@babel/runtime@npm:7.21.0" +"ajv@npm:^6.14.0": + version: 6.15.0 + resolution: "ajv@npm:6.15.0" dependencies: - regenerator-runtime: "npm:^0.13.11" - checksum: 10c0/8fc28acf3b353390a8188a63d443719847b24b66028fdc8bb301c08e2ee013b52aaeb9d0e9783fa5dcd72bb3c0172fb647419db32392101001738356bdc1f4ab + fast-deep-equal: "npm:^3.1.1" + fast-json-stable-stringify: "npm:^2.0.0" + json-schema-traverse: "npm:^0.4.1" + uri-js: "npm:^4.2.2" + checksum: 10c0/67966499dd272ecde1c2e467084411132891523d057487587879d39ac04207f4351b7b2324c83198013967fbfa632c1612adc960114a30770fbe07a0773b32c2 languageName: node linkType: hard -"@babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.8.4": - version: 7.25.6 - resolution: "@babel/runtime@npm:7.25.6" - dependencies: - regenerator-runtime: "npm:^0.14.0" - checksum: 10c0/d6143adf5aa1ce79ed374e33fdfd74fa975055a80bc6e479672ab1eadc4e4bfd7484444e17dd063a1d180e051f3ec62b357c7a2b817e7657687b47313158c3d2 +"argparse@npm:^3.0.3": + version: 3.0.3 + resolution: "argparse@npm:3.0.3" + checksum: 10c0/8d715f1c4bb0aa14a8b8ee749d6ab6f12d380caba33139d527cac79988af152cfd160f152c3964c76c7ea473f1899bd0989bd1e9cfdba22e68f50b6a4b5c9d0d languageName: node linkType: hard -"@babel/template@npm:^7.20.7": - version: 7.20.7 - resolution: "@babel/template@npm:7.20.7" - dependencies: - "@babel/code-frame": "npm:^7.18.6" - "@babel/parser": "npm:^7.20.7" - "@babel/types": "npm:^7.20.7" - checksum: 10c0/1c6dcf9ac92769e6ab5e3d9048975537d26ab00b869646462ab4583d45e419c01db5144715ec0d70548835a3098c5d5416148c4a0b996a95e8e0b9dc8d042dd3 +"assertion-error@npm:^2.0.1": + version: 2.0.1 + resolution: "assertion-error@npm:2.0.1" + checksum: 10c0/bbbcb117ac6480138f8c93cf7f535614282dea9dc828f540cdece85e3c665e8f78958b96afac52f29ff883c72638e6a87d469ecc9fe5bc902df03ed24a55dba8 languageName: node linkType: hard -"@babel/template@npm:^7.24.7, @babel/template@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/template@npm:7.25.0" - dependencies: - "@babel/code-frame": "npm:^7.24.7" - "@babel/parser": "npm:^7.25.0" - "@babel/types": "npm:^7.25.0" - checksum: 10c0/4e31afd873215744c016e02b04f43b9fa23205d6d0766fb2e93eb4091c60c1b88897936adb895fb04e3c23de98dfdcbe31bc98daaa1a4e0133f78bb948e1209b +"async-function@npm:^1.0.0": + version: 1.0.0 + resolution: "async-function@npm:1.0.0" + checksum: 10c0/669a32c2cb7e45091330c680e92eaeb791bc1d4132d827591e499cd1f776ff5a873e77e5f92d0ce795a8d60f10761dec9ddfe7225a5de680f5d357f67b1aac73 languageName: node linkType: hard -"@babel/traverse@npm:^7.21.0": - version: 7.21.0 - resolution: "@babel/traverse@npm:7.21.0" - dependencies: - "@babel/code-frame": "npm:^7.18.6" - "@babel/generator": "npm:^7.21.0" - "@babel/helper-environment-visitor": "npm:^7.18.9" - "@babel/helper-function-name": "npm:^7.21.0" - "@babel/helper-hoist-variables": "npm:^7.18.6" - "@babel/helper-split-export-declaration": "npm:^7.18.6" - "@babel/parser": "npm:^7.21.0" - "@babel/types": "npm:^7.21.0" - debug: "npm:^4.1.0" - globals: "npm:^11.1.0" - checksum: 10c0/959274ca82786733fba7e484a7840139b7d0731caf666e24f3b00c99bde45b3eebe7bc1c7140c6f2c4a8935ed51aa86a525b40db2d6883e7a581160fe2c27c32 +"async-generator-function@npm:^1.0.0": + version: 1.0.0 + resolution: "async-generator-function@npm:1.0.0" + checksum: 10c0/2c50ef856c543ad500d8d8777d347e3c1ba623b93e99c9263ecc5f965c1b12d2a140e2ab6e43c3d0b85366110696f28114649411cbcd10b452a92a2318394186 languageName: node linkType: hard -"@babel/traverse@npm:^7.24.7, @babel/traverse@npm:^7.24.8, @babel/traverse@npm:^7.25.0, @babel/traverse@npm:^7.25.1, @babel/traverse@npm:^7.25.2, @babel/traverse@npm:^7.25.3, @babel/traverse@npm:^7.25.4": - version: 7.25.6 - resolution: "@babel/traverse@npm:7.25.6" - dependencies: - "@babel/code-frame": "npm:^7.24.7" - "@babel/generator": "npm:^7.25.6" - "@babel/parser": "npm:^7.25.6" - "@babel/template": "npm:^7.25.0" - "@babel/types": "npm:^7.25.6" - debug: "npm:^4.3.1" - globals: "npm:^11.1.0" - checksum: 10c0/964304c6fa46bd705428ba380bf73177eeb481c3f26d82ea3d0661242b59e0dd4329d23886035e9ca9a4ceb565c03a76fd615109830687a27bcd350059d6377e +"attr-accept@npm:^4.0.0": + version: 4.0.1 + resolution: "attr-accept@npm:4.0.1" + checksum: 10c0/a97d7a188a461edaf4584322c33103d8cbdfde6b1465f92e0a7a93fe354fde9764e9e491720dbd4bacc0e7bcc6ec21c6b93eac5d7b9982fb57a922c15256e62a languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.6, @babel/types@npm:^7.20.2, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.0, @babel/types@npm:^7.3.0": - version: 7.21.0 - resolution: "@babel/types@npm:7.21.0" +"babel-plugin-macros@npm:^3.1.0": + version: 3.1.0 + resolution: "babel-plugin-macros@npm:3.1.0" dependencies: - "@babel/helper-string-parser": "npm:^7.19.4" - "@babel/helper-validator-identifier": "npm:^7.19.1" - to-fast-properties: "npm:^2.0.0" - checksum: 10c0/b6c81eb7c1570d0c4882f8ce3a1c01033e39bd614efde97efc5b4716ac192425e865a728cb0a0a8709f90ff0d9defb3c8b7a99ed54f1a4923affa3de3d0f7f33 + "@babel/runtime": "npm:^7.12.5" + cosmiconfig: "npm:^7.0.0" + resolve: "npm:^1.19.0" + checksum: 10c0/c6dfb15de96f67871d95bd2e8c58b0c81edc08b9b087dc16755e7157f357dc1090a8dc60ebab955e92587a9101f02eba07e730adc253a1e4cf593ca3ebd3839c languageName: node linkType: hard -"@babel/types@npm:^7.22.15, @babel/types@npm:^7.22.5": - version: 7.23.3 - resolution: "@babel/types@npm:7.23.3" - dependencies: - "@babel/helper-string-parser": "npm:^7.22.5" - "@babel/helper-validator-identifier": "npm:^7.22.20" - to-fast-properties: "npm:^2.0.0" - checksum: 10c0/371a10dd9c8d8ebf48fc5d9e1b327dafd74453f8ea582dcbddd1cee5ae34e8881b743e783a86c08c04dcd1849b1842455472a911ae8a1c185484fe9b7b5f1595 +"balanced-match@npm:^4.0.2": + version: 4.0.4 + resolution: "balanced-match@npm:4.0.4" + checksum: 10c0/07e86102a3eb2ee2a6a1a89164f29d0dbaebd28f2ca3f5ca786f36b8b23d9e417eb3be45a4acf754f837be5ac0a2317de90d3fcb7f4f4dc95720a1f36b26a17b languageName: node linkType: hard -"@babel/types@npm:^7.24.7, @babel/types@npm:^7.24.8, @babel/types@npm:^7.25.0, @babel/types@npm:^7.25.2, @babel/types@npm:^7.25.6, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3": - version: 7.25.6 - resolution: "@babel/types@npm:7.25.6" +"body-parser@npm:^2.3.0": + version: 2.3.0 + resolution: "body-parser@npm:2.3.0" dependencies: - "@babel/helper-string-parser": "npm:^7.24.8" - "@babel/helper-validator-identifier": "npm:^7.24.7" - to-fast-properties: "npm:^2.0.0" - checksum: 10c0/89d45fbee24e27a05dca2d08300a26b905bd384a480448823f6723c72d3a30327c517476389b7280ce8cb9a2c48ef8f47da7f9f6d326faf6f53fd6b68237bdc4 + bytes: "npm:^3.1.2" + content-type: "npm:^2.0.0" + debug: "npm:^4.4.3" + http-errors: "npm:^2.0.1" + iconv-lite: "npm:^0.7.2" + on-finished: "npm:^2.4.1" + qs: "npm:^6.15.2" + raw-body: "npm:^3.0.2" + type-is: "npm:^2.1.0" + checksum: 10c0/2a8fbbdc471b588338555a3e1a597d1eb0ad0c21cf20fdc3bac5d3f8d9c3a4b19b4163575ab852a43a5dfc0df7770ced5284f979e561f1a24c2f851ce89e695a languageName: node linkType: hard -"@emotion/babel-plugin@npm:^11.10.6": - version: 11.10.6 - resolution: "@emotion/babel-plugin@npm:11.10.6" +"brace-expansion@npm:^5.0.8": + version: 5.0.12 + resolution: "brace-expansion@npm:5.0.12" dependencies: - "@babel/helper-module-imports": "npm:^7.16.7" - "@babel/runtime": "npm:^7.18.3" - "@emotion/hash": "npm:^0.9.0" - "@emotion/memoize": "npm:^0.8.0" - "@emotion/serialize": "npm:^1.1.1" - babel-plugin-macros: "npm:^3.1.0" - convert-source-map: "npm:^1.5.0" - escape-string-regexp: "npm:^4.0.0" - find-root: "npm:^1.1.0" - source-map: "npm:^0.5.7" - stylis: "npm:4.1.3" - checksum: 10c0/734ab5d59f8a64ec2cb140f71483f74910ada115bf78ce5b645a9e3e379554ffd79045edb74efb8c1d8f856ef4d302bf8ac59b969b1cc28dedcd5000072e63ce + balanced-match: "npm:^4.0.2" + checksum: 10c0/a40ac64d3bdb5bb222456514d3633c1f627f03b844f509c69e70f98b78be0ce85e21b44bbfecdd0ddd4cc0855ca4242b41ea96ddccf88c7c3c7a84405474bb3a languageName: node linkType: hard -"@emotion/cache@npm:^11.10.5": - version: 11.10.5 - resolution: "@emotion/cache@npm:11.10.5" - dependencies: - "@emotion/memoize": "npm:^0.8.0" - "@emotion/sheet": "npm:^1.2.1" - "@emotion/utils": "npm:^1.2.0" - "@emotion/weak-memoize": "npm:^0.3.0" - stylis: "npm:4.1.3" - checksum: 10c0/eeb6891ab04cf17ace0e175742550b97c30df777d6c5b145e91c4c9fbd783c29b4dabe12a8c786b78f37176313a8295c9b90c69d875e6caab5f7e4677a18be91 +"browser-stdout@npm:^1.3.1": + version: 1.3.1 + resolution: "browser-stdout@npm:1.3.1" + checksum: 10c0/c40e482fd82be872b6ea7b9f7591beafbf6f5ba522fe3dade98ba1573a1c29a11101564993e4eb44e5488be8f44510af072df9a9637c739217eb155ceb639205 languageName: node linkType: hard -"@emotion/hash@npm:^0.9.0": - version: 0.9.0 - resolution: "@emotion/hash@npm:0.9.0" - checksum: 10c0/0910d3e9ec46cc780f691c96fb6f6f67b4f080b50ecf4f441bc4b33b5906e28099f530a368fe0b31c6bad38a857ac44df3c36f8978be603789d71330ac01af12 +"bytes@npm:^3.1.2, bytes@npm:~3.1.2": + version: 3.1.2 + resolution: "bytes@npm:3.1.2" + checksum: 10c0/76d1c43cbd602794ad8ad2ae94095cddeb1de78c5dddaa7005c51af10b0176c69971a6d88e805a90c2b6550d76636e43c40d8427a808b8645ede885de4a0358e languageName: node linkType: hard -"@emotion/is-prop-valid@npm:^1.2.0": - version: 1.2.0 - resolution: "@emotion/is-prop-valid@npm:1.2.0" +"cacheable@npm:^2.5.0": + version: 2.5.0 + resolution: "cacheable@npm:2.5.0" dependencies: - "@emotion/memoize": "npm:^0.8.0" - checksum: 10c0/098bfde166ddbc3ad635157dff8dd8b90ceb5ee2804b3dbc9ffbffcac33955390d6c6e94dc36b1fde8c90f49dc1e1359dfdcd967906b006a3966382dbe8cc90b + "@cacheable/memory": "npm:^2.2.0" + "@cacheable/utils": "npm:^2.5.0" + hookified: "npm:^1.15.0" + keyv: "npm:^5.6.0" + qified: "npm:^0.10.1" + checksum: 10c0/ad4086a60ed327196901c16360312705e35bfde5118457449cfabaf4ece0fecad046985ea8b66b3ece46dd469cb33798d87e1dac3ed2c00746578054bccbf74d languageName: node linkType: hard -"@emotion/memoize@npm:^0.8.0": - version: 0.8.0 - resolution: "@emotion/memoize@npm:0.8.0" - checksum: 10c0/246087ec09b32b295af67a094253831f398aabd953d03d14f186acb8607ed2a755e944f5e20b5ccebb461f15c2e5ccbf8fe977bcf3be951cf10961c504e1e65b +"call-bind-apply-helpers@npm:^1.0.1, call-bind-apply-helpers@npm:^1.0.2": + version: 1.0.2 + resolution: "call-bind-apply-helpers@npm:1.0.2" + dependencies: + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + checksum: 10c0/47bd9901d57b857590431243fea704ff18078b16890a6b3e021e12d279bbf211d039155e27d7566b374d49ee1f8189344bac9833dec7a20cdec370506361c938 languageName: node linkType: hard -"@emotion/react@npm:^11.7.0": - version: 11.10.6 - resolution: "@emotion/react@npm:11.10.6" +"call-bound@npm:^1.0.2": + version: 1.0.4 + resolution: "call-bound@npm:1.0.4" dependencies: - "@babel/runtime": "npm:^7.18.3" - "@emotion/babel-plugin": "npm:^11.10.6" - "@emotion/cache": "npm:^11.10.5" - "@emotion/serialize": "npm:^1.1.1" - "@emotion/use-insertion-effect-with-fallbacks": "npm:^1.0.0" - "@emotion/utils": "npm:^1.2.0" - "@emotion/weak-memoize": "npm:^0.3.0" - hoist-non-react-statics: "npm:^3.3.1" - peerDependencies: - react: ">=16.8.0" - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10c0/4c5ce8ef279a8ce0d371414720d9b2b195ce6b30abf82d99a856ef3b7dc8643f8a32b9b01fded35d94ab9159e4982f7eb0ffa3c4b1aabb102180383e56232bcf + call-bind-apply-helpers: "npm:^1.0.2" + get-intrinsic: "npm:^1.3.0" + checksum: 10c0/f4796a6a0941e71c766aea672f63b72bc61234c4f4964dc6d7606e3664c307e7d77845328a8f3359ce39ddb377fed67318f9ee203dea1d47e46165dcf2917644 languageName: node linkType: hard -"@emotion/serialize@npm:^1.1.1": - version: 1.1.1 - resolution: "@emotion/serialize@npm:1.1.1" - dependencies: - "@emotion/hash": "npm:^0.9.0" - "@emotion/memoize": "npm:^0.8.0" - "@emotion/unitless": "npm:^0.8.0" - "@emotion/utils": "npm:^1.2.0" - csstype: "npm:^3.0.2" - checksum: 10c0/ea353abbf530ede8b74fe4df30eb626f245f710ce0bfcb9d34e72630a1dede688fddf02b1143f33a1a4ef5b66b70715a3c1cd6a12ec43f5b585ed60d4f3e8712 +"callsites@npm:^3.0.0": + version: 3.1.0 + resolution: "callsites@npm:3.1.0" + checksum: 10c0/fff92277400eb06c3079f9e74f3af120db9f8ea03bad0e84d9aede54bbe2d44a56cccb5f6cf12211f93f52306df87077ecec5b712794c5a9b5dac6d615a3f301 languageName: node linkType: hard -"@emotion/sheet@npm:^1.2.1": - version: 1.2.1 - resolution: "@emotion/sheet@npm:1.2.1" - checksum: 10c0/88268c00005d310df3ebb249b839ad0b234943da5a0cc614b232b9bd4ae600292dca9b0f61c45cde3a592c77459e880d77a2aa73af20ec3c0d579afccc3f71af +"chai@npm:^6.3.0": + version: 6.3.0 + resolution: "chai@npm:6.3.0" + checksum: 10c0/fd1bf7fc41295ba1eba18a44d68e204b3cb11b79c9ad51fd071de3ee85323c20d94afc07a386f1f5ff44a5da4a116614d419fc44a2d459e4b5b574aef9af4431 languageName: node linkType: hard -"@emotion/styled@npm:^11.6.0": - version: 11.10.6 - resolution: "@emotion/styled@npm:11.10.6" +"chokidar@npm:^5.0.0": + version: 5.0.0 + resolution: "chokidar@npm:5.0.0" dependencies: - "@babel/runtime": "npm:^7.18.3" - "@emotion/babel-plugin": "npm:^11.10.6" - "@emotion/is-prop-valid": "npm:^1.2.0" - "@emotion/serialize": "npm:^1.1.1" - "@emotion/use-insertion-effect-with-fallbacks": "npm:^1.0.0" - "@emotion/utils": "npm:^1.2.0" - peerDependencies: - "@emotion/react": ^11.0.0-rc.0 - react: ">=16.8.0" - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10c0/69e968bb359758ec454c547d6f1d5b1a50a8c149c13274119f63d81fbb76db46b2df53c236ecbc9cb15696f9834647156d715e5e6ccc84ff7b1852617e367386 + readdirp: "npm:^5.0.0" + checksum: 10c0/42fc907cb2a7ff5c9e220f84dae75380a77997f851c2a5e7865a2cf9ae45dd407a23557208cdcdbf3ac8c93341135a1748e4c48c31855f3bfa095e5159b6bdec languageName: node linkType: hard -"@emotion/unitless@npm:^0.8.0": - version: 0.8.0 - resolution: "@emotion/unitless@npm:0.8.0" - checksum: 10c0/1f2cfb7c0ccb83c20b1c6d8d92a74a93da4b2a440f9a0d49ded08647faf299065a2ffde17e1335920fa10397b85f8635bbfe14f3cd29222a59ea81d978478072 +"chownr@npm:^3.0.0": + version: 3.0.0 + resolution: "chownr@npm:3.0.0" + checksum: 10c0/43925b87700f7e3893296c8e9c56cc58f926411cce3a6e5898136daaf08f08b9a8eb76d37d3267e707d0dcc17aed2e2ebdf5848c0c3ce95cf910a919935c1b10 languageName: node linkType: hard -"@emotion/use-insertion-effect-with-fallbacks@npm:^1.0.0": - version: 1.0.0 - resolution: "@emotion/use-insertion-effect-with-fallbacks@npm:1.0.0" - peerDependencies: - react: ">=16.8.0" - checksum: 10c0/0c5fbd36a4f416a5abaf428ba3dca6e79018c4c74016ecb4e3991a11cf8b5dbd306d7770fee09692971335e33f97e3b555deda595e5ae7831841505078bd07d7 +"clsx@npm:^2.1.1": + version: 2.1.1 + resolution: "clsx@npm:2.1.1" + checksum: 10c0/c4c8eb865f8c82baab07e71bfa8897c73454881c4f99d6bc81585aecd7c441746c1399d08363dc096c550cceaf97bd4ce1e8854e1771e9998d9f94c4fe075839 languageName: node linkType: hard -"@emotion/utils@npm:^1.2.0": - version: 1.2.0 - resolution: "@emotion/utils@npm:1.2.0" - checksum: 10c0/7051cec83bb49688549667484058d3a19a30001fa3692c23f7a2e727c05121f952854e1196feb9ece4fa36914705ebf474edba833a2178bdc133c654b5e3ca7d +"content-disposition@npm:^2.0.1": + version: 2.0.1 + resolution: "content-disposition@npm:2.0.1" + checksum: 10c0/8ba0e1f95102343a69a9a4b6f17728209d4ff0402b830b7cda91dcdfbb12eed0201dcd02aaa1e56f9bcf1c5b8ccef4a70e83b58292cf26588d8d5f7d05a0c006 languageName: node linkType: hard -"@emotion/weak-memoize@npm:^0.3.0": - version: 0.3.0 - resolution: "@emotion/weak-memoize@npm:0.3.0" - checksum: 10c0/1771687cc3b3280371de12698f1b78756c64654fc7d15ce76e1fb5d4adf9fd49d4411e41276bbfd5b521ef9cef647196aa9dca26f936c466fb80bf48491fa844 +"content-type@npm:^2.0.0, content-type@npm:^2.1.0": + version: 2.1.0 + resolution: "content-type@npm:2.1.0" + checksum: 10c0/f5d420f55fd0c7a7f7cbae9637777ce67a074aa79b489d8d9fee8599089592b5f8bb194e1d6885741fee20271034baca0d68d5419535b52d070c4f7e39f4b448 languageName: node linkType: hard -"@esbuild/aix-ppc64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/aix-ppc64@npm:0.21.5" - conditions: os=aix & cpu=ppc64 +"convert-source-map@npm:^1.5.0": + version: 1.9.0 + resolution: "convert-source-map@npm:1.9.0" + checksum: 10c0/281da55454bf8126cbc6625385928c43479f2060984180c42f3a86c8b8c12720a24eac260624a7d1e090004028d2dee78602330578ceec1a08e27cb8bb0a8a5b languageName: node linkType: hard -"@esbuild/aix-ppc64@npm:0.23.1": - version: 0.23.1 - resolution: "@esbuild/aix-ppc64@npm:0.23.1" - conditions: os=aix & cpu=ppc64 +"cookie-signature@npm:^1.2.1": + version: 1.2.2 + resolution: "cookie-signature@npm:1.2.2" + checksum: 10c0/54e05df1a293b3ce81589b27dddc445f462f6fa6812147c033350cd3561a42bc14481674e05ed14c7bd0ce1e8bb3dc0e40851bad75415733711294ddce0b7bc6 languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/android-arm64@npm:0.21.5" - conditions: os=android & cpu=arm64 +"cookie@npm:^0.7.1, cookie@npm:~0.7.2": + version: 0.7.2 + resolution: "cookie@npm:0.7.2" + checksum: 10c0/9596e8ccdbf1a3a88ae02cf5ee80c1c50959423e1022e4e60b91dd87c622af1da309253d8abdb258fb5e3eacb4f08e579dc58b4897b8087574eee0fd35dfa5d2 languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.23.1": - version: 0.23.1 - resolution: "@esbuild/android-arm64@npm:0.23.1" - conditions: os=android & cpu=arm64 +"cookie@npm:^1.0.1": + version: 1.1.1 + resolution: "cookie@npm:1.1.1" + checksum: 10c0/79c4ddc0fcad9c4f045f826f42edf54bcc921a29586a4558b0898277fa89fb47be95bc384c2253f493af7b29500c830da28341274527328f18eba9f58afa112c languageName: node linkType: hard -"@esbuild/android-arm@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/android-arm@npm:0.21.5" - conditions: os=android & cpu=arm +"cors@npm:^2.8.6, cors@npm:~2.8.5": + version: 2.8.6 + resolution: "cors@npm:2.8.6" + dependencies: + object-assign: "npm:^4" + vary: "npm:^1" + checksum: 10c0/ab2bc57b8af8ef8476682a59647f7c55c1a7d406b559ac06119aa1c5f70b96d35036864d197b24cf86e228e4547231088f1f94ca05061dbb14d89cc0bc9d4cab languageName: node linkType: hard -"@esbuild/android-arm@npm:0.23.1": - version: 0.23.1 - resolution: "@esbuild/android-arm@npm:0.23.1" - conditions: os=android & cpu=arm +"cosmiconfig@npm:^7.0.0": + version: 7.1.0 + resolution: "cosmiconfig@npm:7.1.0" + dependencies: + "@types/parse-json": "npm:^4.0.0" + import-fresh: "npm:^3.2.1" + parse-json: "npm:^5.0.0" + path-type: "npm:^4.0.0" + yaml: "npm:^1.10.0" + checksum: 10c0/b923ff6af581638128e5f074a5450ba12c0300b71302398ea38dbeabd33bbcaa0245ca9adbedfcf284a07da50f99ede5658c80bb3e39e2ce770a99d28a21ef03 languageName: node linkType: hard -"@esbuild/android-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/android-x64@npm:0.21.5" - conditions: os=android & cpu=x64 +"cross-spawn@npm:^7.0.6": + version: 7.0.6 + resolution: "cross-spawn@npm:7.0.6" + dependencies: + path-key: "npm:^3.1.0" + shebang-command: "npm:^2.0.0" + which: "npm:^2.0.1" + checksum: 10c0/053ea8b2135caff68a9e81470e845613e374e7309a47731e81639de3eaeb90c3d01af0e0b44d2ab9d50b43467223b88567dfeb3262db942dc063b9976718ffc1 languageName: node linkType: hard -"@esbuild/android-x64@npm:0.23.1": - version: 0.23.1 - resolution: "@esbuild/android-x64@npm:0.23.1" - conditions: os=android & cpu=x64 +"csstype@npm:^3.0.2, csstype@npm:^3.2.2, csstype@npm:^3.2.3": + version: 3.2.3 + resolution: "csstype@npm:3.2.3" + checksum: 10c0/cd29c51e70fa822f1cecd8641a1445bed7063697469d35633b516e60fe8c1bde04b08f6c5b6022136bb669b64c63d4173af54864510fbb4ee23281801841a3ce languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/darwin-arm64@npm:0.21.5" - conditions: os=darwin & cpu=arm64 +"debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.5, debug@npm:^4.4.0, debug@npm:^4.4.3, debug@npm:~4.4.1": + version: 4.4.3 + resolution: "debug@npm:4.4.3" + dependencies: + ms: "npm:^2.1.3" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 10c0/d79136ec6c83ecbefd0f6a5593da6a9c91ec4d7ddc4b54c883d6e71ec9accb5f67a1a5e96d00a328196b5b5c86d365e98d8a3a70856aaf16b4e7b1985e67f5a6 languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.23.1": - version: 0.23.1 - resolution: "@esbuild/darwin-arm64@npm:0.23.1" - conditions: os=darwin & cpu=arm64 +"deep-is@npm:^0.1.3": + version: 0.1.4 + resolution: "deep-is@npm:0.1.4" + checksum: 10c0/7f0ee496e0dff14a573dc6127f14c95061b448b87b995fc96c017ce0a1e66af1675e73f1d6064407975bc4ea6ab679497a29fff7b5b9c4e99cb10797c1ad0b4c languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/darwin-x64@npm:0.21.5" - conditions: os=darwin & cpu=x64 +"depd@npm:^2.0.0, depd@npm:~2.0.0": + version: 2.0.0 + resolution: "depd@npm:2.0.0" + checksum: 10c0/58bd06ec20e19529b06f7ad07ddab60e504d9e0faca4bd23079fac2d279c3594334d736508dc350e06e510aba5e22e4594483b3a6562ce7c17dd797f4cc4ad2c languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.23.1": - version: 0.23.1 - resolution: "@esbuild/darwin-x64@npm:0.23.1" - conditions: os=darwin & cpu=x64 +"detect-libc@npm:^2.0.3": + version: 2.1.2 + resolution: "detect-libc@npm:2.1.2" + checksum: 10c0/acc675c29a5649fa1fb6e255f993b8ee829e510b6b56b0910666949c80c364738833417d0edb5f90e4e46be17228b0f2b66a010513984e18b15deeeac49369c4 languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/freebsd-arm64@npm:0.21.5" - conditions: os=freebsd & cpu=arm64 +"diff@npm:^9.0.0": + version: 9.0.0 + resolution: "diff@npm:9.0.0" + checksum: 10c0/a971cc88f66071a33bd3942db2f51b57d484e9856265ae45cf44fb28ab2fde91f132d9c26d8be0fb6082d2be94d29e1295c1adb251b7461935d6a2dd304cd161 languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.23.1": - version: 0.23.1 - resolution: "@esbuild/freebsd-arm64@npm:0.23.1" - conditions: os=freebsd & cpu=arm64 +"dom-helpers@npm:^5.0.1": + version: 5.2.1 + resolution: "dom-helpers@npm:5.2.1" + dependencies: + "@babel/runtime": "npm:^7.8.7" + csstype: "npm:^3.0.2" + checksum: 10c0/f735074d66dd759b36b158fa26e9d00c9388ee0e8c9b16af941c38f014a37fc80782de83afefd621681b19ac0501034b4f1c4a3bff5caa1b8667f0212b5e124c languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/freebsd-x64@npm:0.21.5" - conditions: os=freebsd & cpu=x64 +"dotenv@npm:^18.0.8": + version: 18.0.8 + resolution: "dotenv@npm:18.0.8" + bin: + dotenv: dist/index.cjs + checksum: 10c0/88a6a81afe58aaad7f9ae5d97422731c6e7b9efa4db752ea5add7fb6035f5866ce6e10a76fb7ad2d7f606bdf0afc9507ffd7e04d86d9b347b38d4614a3a0c2ac languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.23.1": - version: 0.23.1 - resolution: "@esbuild/freebsd-x64@npm:0.23.1" - conditions: os=freebsd & cpu=x64 +"dunder-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "dunder-proto@npm:1.0.1" + dependencies: + call-bind-apply-helpers: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + gopd: "npm:^1.2.0" + checksum: 10c0/199f2a0c1c16593ca0a145dbf76a962f8033ce3129f01284d48c45ed4e14fea9bbacd7b3610b6cdc33486cef20385ac054948fefc6272fcce645c09468f93031 languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-arm64@npm:0.21.5" - conditions: os=linux & cpu=arm64 +"ee-first@npm:1.1.1": + version: 1.1.1 + resolution: "ee-first@npm:1.1.1" + checksum: 10c0/b5bb125ee93161bc16bfe6e56c6b04de5ad2aa44234d8f644813cc95d861a6910903132b05093706de2b706599367c4130eb6d170f6b46895686b95f87d017b7 languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.23.1": - version: 0.23.1 - resolution: "@esbuild/linux-arm64@npm:0.23.1" - conditions: os=linux & cpu=arm64 +"encodeurl@npm:^2.0.0": + version: 2.0.0 + resolution: "encodeurl@npm:2.0.0" + checksum: 10c0/5d317306acb13e6590e28e27924c754163946a2480de11865c991a3a7eed4315cd3fba378b543ca145829569eefe9b899f3d84bb09870f675ae60bc924b01ceb languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-arm@npm:0.21.5" - conditions: os=linux & cpu=arm +"engine.io-client@npm:~6.6.1": + version: 6.6.7 + resolution: "engine.io-client@npm:6.6.7" + dependencies: + "@socket.io/component-emitter": "npm:~3.1.0" + debug: "npm:~4.4.1" + engine.io-parser: "npm:~5.2.1" + ws: "npm:~8.21.0" + xmlhttprequest-ssl: "npm:~2.1.1" + checksum: 10c0/64892158a23537f31f38b8eac40c6cdc28cfe141add2872e1f337d42f3e8d0d5dc4ad92684f45e6b89d17ddb62cc4743ef54a575918d2220d68008b3d4d4bd6e languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.23.1": - version: 0.23.1 - resolution: "@esbuild/linux-arm@npm:0.23.1" - conditions: os=linux & cpu=arm +"engine.io-parser@npm:~5.2.1": + version: 5.2.3 + resolution: "engine.io-parser@npm:5.2.3" + checksum: 10c0/ed4900d8dbef470ab3839ccf3bfa79ee518ea8277c7f1f2759e8c22a48f64e687ea5e474291394d0c94f84054749fd93f3ef0acb51fa2f5f234cc9d9d8e7c536 languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-ia32@npm:0.21.5" - conditions: os=linux & cpu=ia32 +"engine.io@npm:~6.6.0": + version: 6.6.11 + resolution: "engine.io@npm:6.6.11" + dependencies: + "@types/cors": "npm:^2.8.12" + "@types/node": "npm:>=10.0.0" + "@types/ws": "npm:^8.5.12" + accepts: "npm:~1.3.4" + cookie: "npm:~0.7.2" + cors: "npm:~2.8.5" + debug: "npm:~4.4.1" + engine.io-parser: "npm:~5.2.1" + ws: "npm:~8.21.0" + checksum: 10c0/b15b395dbbb8ea98942786f738794d2c2a38b9aa79d2d0eb861e26e20a9ac27beaf178e5bcf9b725bc85625748f7eb66158407932c25b89ef41e783bdbf22bc6 languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.23.1": - version: 0.23.1 - resolution: "@esbuild/linux-ia32@npm:0.23.1" - conditions: os=linux & cpu=ia32 +"env-paths@npm:^2.2.0": + version: 2.2.1 + resolution: "env-paths@npm:2.2.1" + checksum: 10c0/285325677bf00e30845e330eec32894f5105529db97496ee3f598478e50f008c5352a41a30e5e72ec9de8a542b5a570b85699cd63bd2bc646dbcb9f311d83bc4 languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-loong64@npm:0.21.5" - conditions: os=linux & cpu=loong64 +"error-ex@npm:^1.3.1": + version: 1.3.4 + resolution: "error-ex@npm:1.3.4" + dependencies: + is-arrayish: "npm:^0.2.1" + checksum: 10c0/b9e34ff4778b8f3b31a8377e1c654456f4c41aeaa3d10a1138c3b7635d8b7b2e03eb2475d46d8ae055c1f180a1063e100bffabf64ea7e7388b37735df5328664 languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.23.1": - version: 0.23.1 - resolution: "@esbuild/linux-loong64@npm:0.23.1" - conditions: os=linux & cpu=loong64 +"es-define-property@npm:^1.0.1": + version: 1.0.1 + resolution: "es-define-property@npm:1.0.1" + checksum: 10c0/3f54eb49c16c18707949ff25a1456728c883e81259f045003499efba399c08bad00deebf65cccde8c0e07908c1a225c9d472b7107e558f2a48e28d530e34527c languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-mips64el@npm:0.21.5" - conditions: os=linux & cpu=mips64el +"es-errors@npm:^1.3.0": + version: 1.3.0 + resolution: "es-errors@npm:1.3.0" + checksum: 10c0/0a61325670072f98d8ae3b914edab3559b6caa980f08054a3b872052640d91da01d38df55df797fcc916389d77fc92b8d5906cf028f4db46d7e3003abecbca85 languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.23.1": - version: 0.23.1 - resolution: "@esbuild/linux-mips64el@npm:0.23.1" - conditions: os=linux & cpu=mips64el +"es-object-atoms@npm:^1.0.0, es-object-atoms@npm:^1.1.1": + version: 1.1.2 + resolution: "es-object-atoms@npm:1.1.2" + dependencies: + es-errors: "npm:^1.3.0" + checksum: 10c0/1772861f094f739d6f41b579cfb9a18579daffeb434552a370a5fbef50a32d22227e27b63fdbb757b7ddd429d1b42fe52ccae7966d9302a2ec221b6f1b41bbc4 + languageName: node + linkType: hard + +"esbuild@npm:~0.28.0": + version: 0.28.2 + resolution: "esbuild@npm:0.28.2" + dependencies: + "@esbuild/aix-ppc64": "npm:0.28.2" + "@esbuild/android-arm": "npm:0.28.2" + "@esbuild/android-arm64": "npm:0.28.2" + "@esbuild/android-x64": "npm:0.28.2" + "@esbuild/darwin-arm64": "npm:0.28.2" + "@esbuild/darwin-x64": "npm:0.28.2" + "@esbuild/freebsd-arm64": "npm:0.28.2" + "@esbuild/freebsd-x64": "npm:0.28.2" + "@esbuild/linux-arm": "npm:0.28.2" + "@esbuild/linux-arm64": "npm:0.28.2" + "@esbuild/linux-ia32": "npm:0.28.2" + "@esbuild/linux-loong64": "npm:0.28.2" + "@esbuild/linux-mips64el": "npm:0.28.2" + "@esbuild/linux-ppc64": "npm:0.28.2" + "@esbuild/linux-riscv64": "npm:0.28.2" + "@esbuild/linux-s390x": "npm:0.28.2" + "@esbuild/linux-x64": "npm:0.28.2" + "@esbuild/netbsd-arm64": "npm:0.28.2" + "@esbuild/netbsd-x64": "npm:0.28.2" + "@esbuild/openbsd-arm64": "npm:0.28.2" + "@esbuild/openbsd-x64": "npm:0.28.2" + "@esbuild/openharmony-arm64": "npm:0.28.2" + "@esbuild/sunos-x64": "npm:0.28.2" + "@esbuild/win32-arm64": "npm:0.28.2" + "@esbuild/win32-ia32": "npm:0.28.2" + "@esbuild/win32-x64": "npm:0.28.2" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-arm64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/openharmony-arm64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c0/9b19edb63bd7780fd2e8e65a1394a0e8a05a17d697f73f0647ffe3c134709d8dbe744ab3fd57b35c9470db268cae7678fafd3dcd3ce1f34fc590568c2433f5d2 languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-ppc64@npm:0.21.5" - conditions: os=linux & cpu=ppc64 +"escape-html@npm:^1.0.3": + version: 1.0.3 + resolution: "escape-html@npm:1.0.3" + checksum: 10c0/524c739d776b36c3d29fa08a22e03e8824e3b2fd57500e5e44ecf3cc4707c34c60f9ca0781c0e33d191f2991161504c295e98f68c78fe7baa6e57081ec6ac0a3 languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.23.1": - version: 0.23.1 - resolution: "@esbuild/linux-ppc64@npm:0.23.1" - conditions: os=linux & cpu=ppc64 +"escape-string-regexp@npm:^4.0.0": + version: 4.0.0 + resolution: "escape-string-regexp@npm:4.0.0" + checksum: 10c0/9497d4dd307d845bd7f75180d8188bb17ea8c151c1edbf6b6717c100e104d629dc2dfb687686181b0f4b7d732c7dfdc4d5e7a8ff72de1b0ca283a75bbb3a9cd9 languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-riscv64@npm:0.21.5" - conditions: os=linux & cpu=riscv64 +"eslint-config-prettier@npm:^10.1.8": + version: 10.1.8 + resolution: "eslint-config-prettier@npm:10.1.8" + peerDependencies: + eslint: ">=7.0.0" + bin: + eslint-config-prettier: bin/cli.js + checksum: 10c0/e1bcfadc9eccd526c240056b1e59c5cd26544fe59feb85f38f4f1f116caed96aea0b3b87868e68b3099e55caaac3f2e5b9f58110f85db893e83a332751192682 languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.23.1": - version: 0.23.1 - resolution: "@esbuild/linux-riscv64@npm:0.23.1" - conditions: os=linux & cpu=riscv64 +"eslint-plugin-prettier@npm:^5.5.7": + version: 5.5.7 + resolution: "eslint-plugin-prettier@npm:5.5.7" + dependencies: + prettier-linter-helpers: "npm:^1.0.1" + synckit: "npm:^0.12.3" + 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 + checksum: 10c0/76a1a99ace86471a320d99371cc34b0eee179f39bd383e46244b65b7b08b980b7a8e72f9d7eaaa74e92ede8032bf04ba0b05ba7c648c44a7c49d121df07eb58f languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-s390x@npm:0.21.5" - conditions: os=linux & cpu=s390x +"eslint-scope@npm:^9.1.2": + version: 9.1.2 + resolution: "eslint-scope@npm:9.1.2" + dependencies: + "@types/esrecurse": "npm:^4.3.1" + "@types/estree": "npm:^1.0.8" + esrecurse: "npm:^4.3.0" + estraverse: "npm:^5.2.0" + checksum: 10c0/9fb8bca5a73e5741efb6cec84467027b6cb6f4203ff9b43a938e272c5cd30800bde46a5c20dfd1609f840225f0b62b7673be391b20acadf8658ca9fa4729b3dd languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.23.1": - version: 0.23.1 - resolution: "@esbuild/linux-s390x@npm:0.23.1" - conditions: os=linux & cpu=s390x +"eslint-visitor-keys@npm:^3.4.3": + version: 3.4.3 + resolution: "eslint-visitor-keys@npm:3.4.3" + checksum: 10c0/92708e882c0a5ffd88c23c0b404ac1628cf20104a108c745f240a13c332a11aac54f49a22d5762efbffc18ecbc9a580d1b7ad034bf5f3cc3307e5cbff2ec9820 languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-x64@npm:0.21.5" - conditions: os=linux & cpu=x64 +"eslint-visitor-keys@npm:^5.0.0, eslint-visitor-keys@npm:^5.0.1": + version: 5.0.1 + resolution: "eslint-visitor-keys@npm:5.0.1" + checksum: 10c0/16190bdf2cbae40a1109384c94450c526a79b0b9c3cb21e544256ed85ac48a4b84db66b74a6561d20fe6ab77447f150d711c2ad5ad74df4fcc133736bce99678 languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.23.1": - version: 0.23.1 - resolution: "@esbuild/linux-x64@npm:0.23.1" - conditions: os=linux & cpu=x64 +"eslint@npm:^10.13.0": + version: 10.13.0 + resolution: "eslint@npm:10.13.0" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.8.0" + "@eslint-community/regexpp": "npm:^4.12.2" + "@eslint/config-array": "npm:^0.23.5" + "@eslint/config-helpers": "npm:^0.7.0" + "@eslint/core": "npm:^1.2.1" + "@eslint/plugin-kit": "npm:^0.7.3" + "@humanfs/node": "npm:^0.16.6" + "@humanwhocodes/module-importer": "npm:^1.0.1" + "@humanwhocodes/retry": "npm:^0.4.2" + "@types/estree": "npm:^1.0.6" + ajv: "npm:^6.14.0" + cross-spawn: "npm:^7.0.6" + debug: "npm:^4.3.2" + escape-string-regexp: "npm:^4.0.0" + eslint-scope: "npm:^9.1.2" + eslint-visitor-keys: "npm:^5.0.1" + espree: "npm:^11.2.0" + esquery: "npm:^1.7.0" + esutils: "npm:^2.0.2" + fast-deep-equal: "npm:^3.1.3" + file-entry-cache: "npm:11.1.5 || >11.1.6 <12" + find-up: "npm:^5.0.0" + glob-parent: "npm:^6.0.2" + ignore: "npm:^5.2.0" + imurmurhash: "npm:^0.1.4" + is-glob: "npm:^4.0.0" + json-stable-stringify-without-jsonify: "npm:^1.0.1" + minimatch: "npm:^10.2.5" + natural-compare: "npm:^1.4.0" + optionator: "npm:^0.9.3" + peerDependencies: + jiti: "*" + peerDependenciesMeta: + jiti: + optional: true + bin: + eslint: bin/eslint.js + checksum: 10c0/4a28e85de458f13306d30f07df21b58109056bd8bd691b6158952d95e126cf742db2eb09562e3f1c1cc0154ee44dedc789f8ced8d0d8da48e82a689aa9cb1c0d languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/netbsd-x64@npm:0.21.5" - conditions: os=netbsd & cpu=x64 +"espree@npm:^11.2.0": + version: 11.2.0 + resolution: "espree@npm:11.2.0" + dependencies: + acorn: "npm:^8.16.0" + acorn-jsx: "npm:^5.3.2" + eslint-visitor-keys: "npm:^5.0.1" + checksum: 10c0/cf87e18ffd9dc113eb8d16588e7757701bc10c9934a71cce8b89c2611d51672681a918307bd6b19ac3ccd0e7ba1cbccc2f815b36b52fa7e73097b251014c3d81 languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.23.1": - version: 0.23.1 - resolution: "@esbuild/netbsd-x64@npm:0.23.1" - conditions: os=netbsd & cpu=x64 +"esquery@npm:^1.7.0": + version: 1.7.0 + resolution: "esquery@npm:1.7.0" + dependencies: + estraverse: "npm:^5.1.0" + checksum: 10c0/77d5173db450b66f3bc685d11af4c90cffeedb340f34a39af96d43509a335ce39c894fd79233df32d38f5e4e219fa0f7076f6ec90bae8320170ba082c0db4793 languageName: node linkType: hard -"@esbuild/openbsd-arm64@npm:0.23.1": - version: 0.23.1 - resolution: "@esbuild/openbsd-arm64@npm:0.23.1" - conditions: os=openbsd & cpu=arm64 +"esrecurse@npm:^4.3.0": + version: 4.3.0 + resolution: "esrecurse@npm:4.3.0" + dependencies: + estraverse: "npm:^5.2.0" + checksum: 10c0/81a37116d1408ded88ada45b9fb16dbd26fba3aadc369ce50fcaf82a0bac12772ebd7b24cd7b91fc66786bf2c1ac7b5f196bc990a473efff972f5cb338877cf5 languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/openbsd-x64@npm:0.21.5" - conditions: os=openbsd & cpu=x64 +"estraverse@npm:^5.1.0, estraverse@npm:^5.2.0": + version: 5.3.0 + resolution: "estraverse@npm:5.3.0" + checksum: 10c0/1ff9447b96263dec95d6d67431c5e0771eb9776427421260a3e2f0fdd5d6bd4f8e37a7338f5ad2880c9f143450c9b1e4fc2069060724570a49cf9cf0312bd107 languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.23.1": - version: 0.23.1 - resolution: "@esbuild/openbsd-x64@npm:0.23.1" - conditions: os=openbsd & cpu=x64 +"esutils@npm:^2.0.2": + version: 2.0.3 + resolution: "esutils@npm:2.0.3" + checksum: 10c0/9a2fe69a41bfdade834ba7c42de4723c97ec776e40656919c62cbd13607c45e127a003f05f724a1ea55e5029a4cf2de444b13009f2af71271e42d93a637137c7 languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/sunos-x64@npm:0.21.5" - conditions: os=sunos & cpu=x64 +"etag@npm:^1.8.1": + version: 1.8.1 + resolution: "etag@npm:1.8.1" + checksum: 10c0/12be11ef62fb9817314d790089a0a49fae4e1b50594135dcb8076312b7d7e470884b5100d249b28c18581b7fd52f8b485689ffae22a11ed9ec17377a33a08f84 languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.23.1": - version: 0.23.1 - resolution: "@esbuild/sunos-x64@npm:0.23.1" - conditions: os=sunos & cpu=x64 +"exponential-backoff@npm:^3.1.1": + version: 3.1.3 + resolution: "exponential-backoff@npm:3.1.3" + checksum: 10c0/77e3ae682b7b1f4972f563c6dbcd2b0d54ac679e62d5d32f3e5085feba20483cf28bd505543f520e287a56d4d55a28d7874299941faf637e779a1aa5994d1267 languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/win32-arm64@npm:0.21.5" - conditions: os=win32 & cpu=arm64 +"express@npm:^5.3.0": + version: 5.3.0 + resolution: "express@npm:5.3.0" + dependencies: + accepts: "npm:^2.0.0" + body-parser: "npm:^2.3.0" + content-disposition: "npm:^2.0.1" + content-type: "npm:^2.0.0" + cookie: "npm:^0.7.1" + cookie-signature: "npm:^1.2.1" + debug: "npm:^4.4.0" + depd: "npm:^2.0.0" + encodeurl: "npm:^2.0.0" + escape-html: "npm:^1.0.3" + etag: "npm:^1.8.1" + finalhandler: "npm:^2.1.0" + fresh: "npm:^2.0.0" + http-errors: "npm:^2.0.0" + merge-descriptors: "npm:^2.0.0" + mime-types: "npm:^3.0.0" + on-finished: "npm:^2.4.1" + once: "npm:^1.4.0" + parseurl: "npm:^1.3.3" + proxy-addr: "npm:^2.0.8" + qs: "npm:^6.16.0" + range-parser: "npm:^1.2.1" + router: "npm:^2.2.0" + send: "npm:^1.1.0" + serve-static: "npm:^2.2.0" + statuses: "npm:^2.0.1" + type-is: "npm:^2.1.0" + vary: "npm:^1.1.2" + checksum: 10c0/7d6c1a3e404db0a26f7ed1143ad6bb0bcf33fec8dc66b3e56b59f94f4660e7d3d8395bf03f80e51f6b74ce976856a3e97133a371ec4d012409729fff9233eb15 languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.23.1": - version: 0.23.1 - resolution: "@esbuild/win32-arm64@npm:0.23.1" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/win32-ia32@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/win32-ia32@npm:0.21.5" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - -"@esbuild/win32-ia32@npm:0.23.1": - version: 0.23.1 - resolution: "@esbuild/win32-ia32@npm:0.23.1" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - -"@esbuild/win32-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/win32-x64@npm:0.21.5" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/win32-x64@npm:0.23.1": - version: 0.23.1 - resolution: "@esbuild/win32-x64@npm:0.23.1" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - -"@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0": - version: 4.4.0 - resolution: "@eslint-community/eslint-utils@npm:4.4.0" - dependencies: - eslint-visitor-keys: "npm:^3.3.0" - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - checksum: 10c0/7e559c4ce59cd3a06b1b5a517b593912e680a7f981ae7affab0d01d709e99cd5647019be8fafa38c350305bc32f1f7d42c7073edde2ab536c745e365f37b607e - languageName: node - linkType: hard - -"@eslint-community/regexpp@npm:^4.10.0, @eslint-community/regexpp@npm:^4.11.0, @eslint-community/regexpp@npm:^4.4.0": - version: 4.11.0 - resolution: "@eslint-community/regexpp@npm:4.11.0" - checksum: 10c0/0f6328869b2741e2794da4ad80beac55cba7de2d3b44f796a60955b0586212ec75e6b0253291fd4aad2100ad471d1480d8895f2b54f1605439ba4c875e05e523 - languageName: node - linkType: hard - -"@eslint/config-array@npm:^0.18.0": - version: 0.18.0 - resolution: "@eslint/config-array@npm:0.18.0" - dependencies: - "@eslint/object-schema": "npm:^2.1.4" - debug: "npm:^4.3.1" - minimatch: "npm:^3.1.2" - checksum: 10c0/0234aeb3e6b052ad2402a647d0b4f8a6aa71524bafe1adad0b8db1dfe94d7f5f26d67c80f79bb37ac61361a1d4b14bb8fb475efe501de37263cf55eabb79868f - languageName: node - linkType: hard - -"@eslint/eslintrc@npm:^3.1.0": - version: 3.1.0 - resolution: "@eslint/eslintrc@npm:3.1.0" - dependencies: - ajv: "npm:^6.12.4" - debug: "npm:^4.3.2" - espree: "npm:^10.0.1" - globals: "npm:^14.0.0" - ignore: "npm:^5.2.0" - import-fresh: "npm:^3.2.1" - js-yaml: "npm:^4.1.0" - minimatch: "npm:^3.1.2" - strip-json-comments: "npm:^3.1.1" - checksum: 10c0/5b7332ed781edcfc98caa8dedbbb843abfb9bda2e86538529c843473f580e40c69eb894410eddc6702f487e9ee8f8cfa8df83213d43a8fdb549f23ce06699167 - languageName: node - linkType: hard - -"@eslint/js@npm:9.9.1": - version: 9.9.1 - resolution: "@eslint/js@npm:9.9.1" - checksum: 10c0/a3a91de2ce78469f7c4eee78c1eba77360706e1d0fa0ace2e19102079bcf237b851217c85ea501dc92c4c3719d60d9df966977abc8554d4c38e3638c1f53dcb2 - languageName: node - linkType: hard - -"@eslint/object-schema@npm:^2.1.4": - version: 2.1.4 - resolution: "@eslint/object-schema@npm:2.1.4" - checksum: 10c0/e9885532ea70e483fb007bf1275968b05bb15ebaa506d98560c41a41220d33d342e19023d5f2939fed6eb59676c1bda5c847c284b4b55fce521d282004da4dda - languageName: node - linkType: hard - -"@humanwhocodes/module-importer@npm:^1.0.1": - version: 1.0.1 - resolution: "@humanwhocodes/module-importer@npm:1.0.1" - checksum: 10c0/909b69c3b86d482c26b3359db16e46a32e0fb30bd306a3c176b8313b9e7313dba0f37f519de6aa8b0a1921349e505f259d19475e123182416a506d7f87e7f529 - languageName: node - linkType: hard - -"@humanwhocodes/retry@npm:^0.3.0": - version: 0.3.0 - resolution: "@humanwhocodes/retry@npm:0.3.0" - checksum: 10c0/7111ec4e098b1a428459b4e3be5a5d2a13b02905f805a2468f4fa628d072f0de2da26a27d04f65ea2846f73ba51f4204661709f05bfccff645e3cedef8781bb6 - languageName: node - linkType: hard - -"@isaacs/cliui@npm:^8.0.2": - version: 8.0.2 - resolution: "@isaacs/cliui@npm:8.0.2" - dependencies: - string-width: "npm:^5.1.2" - string-width-cjs: "npm:string-width@^4.2.0" - strip-ansi: "npm:^7.0.1" - strip-ansi-cjs: "npm:strip-ansi@^6.0.1" - wrap-ansi: "npm:^8.1.0" - wrap-ansi-cjs: "npm:wrap-ansi@^7.0.0" - checksum: 10c0/b1bf42535d49f11dc137f18d5e4e63a28c5569de438a221c369483731e9dac9fb797af554e8bf02b6192d1e5eba6e6402cf93900c3d0ac86391d00d04876789e - languageName: node - linkType: hard - -"@jridgewell/gen-mapping@npm:^0.1.0": - version: 0.1.1 - resolution: "@jridgewell/gen-mapping@npm:0.1.1" - dependencies: - "@jridgewell/set-array": "npm:^1.0.0" - "@jridgewell/sourcemap-codec": "npm:^1.4.10" - checksum: 10c0/3d784d87aee604bc4d48d3d9e547e0466d9f4a432cd9b3a4f3e55d104313bf3945e7e970cd5fa767bc145df11f1d568a01ab6659696be41f0ed2a817f3b583a3 - languageName: node - linkType: hard - -"@jridgewell/gen-mapping@npm:^0.3.2": - version: 0.3.2 - resolution: "@jridgewell/gen-mapping@npm:0.3.2" - dependencies: - "@jridgewell/set-array": "npm:^1.0.1" - "@jridgewell/sourcemap-codec": "npm:^1.4.10" - "@jridgewell/trace-mapping": "npm:^0.3.9" - checksum: 10c0/82685c8735c63fe388badee45e2970a6bc83eed1c84d46d8652863bafeca22a6c6cc15812f5999a4535366f4668ccc9ba6d5c67dfb72e846fa8a063806f10afd - languageName: node - linkType: hard - -"@jridgewell/gen-mapping@npm:^0.3.5": - version: 0.3.5 - resolution: "@jridgewell/gen-mapping@npm:0.3.5" - dependencies: - "@jridgewell/set-array": "npm:^1.2.1" - "@jridgewell/sourcemap-codec": "npm:^1.4.10" - "@jridgewell/trace-mapping": "npm:^0.3.24" - checksum: 10c0/1be4fd4a6b0f41337c4f5fdf4afc3bd19e39c3691924817108b82ffcb9c9e609c273f936932b9fba4b3a298ce2eb06d9bff4eb1cc3bd81c4f4ee1b4917e25feb - languageName: node - linkType: hard - -"@jridgewell/resolve-uri@npm:3.1.0": - version: 3.1.0 - resolution: "@jridgewell/resolve-uri@npm:3.1.0" - checksum: 10c0/78055e2526108331126366572045355051a930f017d1904a4f753d3f4acee8d92a14854948095626f6163cffc24ea4e3efa30637417bb866b84743dec7ef6fd9 - languageName: node - linkType: hard - -"@jridgewell/resolve-uri@npm:^3.1.0": - version: 3.1.2 - resolution: "@jridgewell/resolve-uri@npm:3.1.2" - checksum: 10c0/d502e6fb516b35032331406d4e962c21fe77cdf1cbdb49c6142bcbd9e30507094b18972778a6e27cbad756209cfe34b1a27729e6fa08a2eb92b33943f680cf1e - languageName: node - linkType: hard - -"@jridgewell/set-array@npm:^1.0.0, @jridgewell/set-array@npm:^1.0.1": - version: 1.1.2 - resolution: "@jridgewell/set-array@npm:1.1.2" - checksum: 10c0/bc7ab4c4c00470de4e7562ecac3c0c84f53e7ee8a711e546d67c47da7febe7c45cd67d4d84ee3c9b2c05ae8e872656cdded8a707a283d30bd54fbc65aef821ab - languageName: node - linkType: hard - -"@jridgewell/set-array@npm:^1.2.1": - version: 1.2.1 - resolution: "@jridgewell/set-array@npm:1.2.1" - checksum: 10c0/2a5aa7b4b5c3464c895c802d8ae3f3d2b92fcbe84ad12f8d0bfbb1f5ad006717e7577ee1fd2eac00c088abe486c7adb27976f45d2941ff6b0b92b2c3302c60f4 - languageName: node - linkType: hard - -"@jridgewell/sourcemap-codec@npm:1.4.14, @jridgewell/sourcemap-codec@npm:^1.4.10": - version: 1.4.14 - resolution: "@jridgewell/sourcemap-codec@npm:1.4.14" - checksum: 10c0/3fbaff1387c1338b097eeb6ff92890d7838f7de0dde259e4983763b44540bfd5ca6a1f7644dc8ad003a57f7e80670d5b96a8402f1386ba9aee074743ae9bad51 - languageName: node - linkType: hard - -"@jridgewell/sourcemap-codec@npm:^1.4.14": - version: 1.5.0 - resolution: "@jridgewell/sourcemap-codec@npm:1.5.0" - checksum: 10c0/2eb864f276eb1096c3c11da3e9bb518f6d9fc0023c78344cdc037abadc725172c70314bdb360f2d4b7bffec7f5d657ce006816bc5d4ecb35e61b66132db00c18 - languageName: node - linkType: hard - -"@jridgewell/trace-mapping@npm:^0.3.17, @jridgewell/trace-mapping@npm:^0.3.9": - version: 0.3.17 - resolution: "@jridgewell/trace-mapping@npm:0.3.17" - dependencies: - "@jridgewell/resolve-uri": "npm:3.1.0" - "@jridgewell/sourcemap-codec": "npm:1.4.14" - checksum: 10c0/40b65fcbdd7cc5a60dbe0a2780b6670ebbc1a31c96e43833e0bf2fee0773b1ba5137ab7d137b28fc3f215567bd5f9d06b7b30634ba15636c13bd8a863c20ae9a - languageName: node - linkType: hard - -"@jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25": - version: 0.3.25 - resolution: "@jridgewell/trace-mapping@npm:0.3.25" - dependencies: - "@jridgewell/resolve-uri": "npm:^3.1.0" - "@jridgewell/sourcemap-codec": "npm:^1.4.14" - checksum: 10c0/3d1ce6ebc69df9682a5a8896b414c6537e428a1d68b02fcc8363b04284a8ca0df04d0ee3013132252ab14f2527bc13bea6526a912ecb5658f0e39fd2860b4df4 - languageName: node - linkType: hard - -"@mui/base@npm:5.0.0-alpha.118": - version: 5.0.0-alpha.118 - resolution: "@mui/base@npm:5.0.0-alpha.118" - dependencies: - "@babel/runtime": "npm:^7.20.13" - "@emotion/is-prop-valid": "npm:^1.2.0" - "@mui/types": "npm:^7.2.3" - "@mui/utils": "npm:^5.11.9" - "@popperjs/core": "npm:^2.11.6" - clsx: "npm:^1.2.1" - prop-types: "npm:^15.8.1" - react-is: "npm:^18.2.0" - peerDependencies: - "@types/react": ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - react-dom: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10c0/16fdee347452478f8d889a472b3fe95f7d5b804f7af0035b129c6c780fcf16cd6284af3cd2bc2567f91dcf7542378e427981d132f4cfddc762efeed8f1b081df - languageName: node - linkType: hard - -"@mui/core-downloads-tracker@npm:^5.11.9": - version: 5.11.9 - resolution: "@mui/core-downloads-tracker@npm:5.11.9" - checksum: 10c0/72e93cac3c7527b7a19b2bd4c4ed1deb39f3b3bd9c327a7de472b7088fc4f3d1a4b1afb36e5463ad9ba59383045f4bd0417f88668251b54ccb7e4da795472d9b - languageName: node - linkType: hard - -"@mui/icons-material@npm:^5.2.4": - version: 5.11.9 - resolution: "@mui/icons-material@npm:5.11.9" - dependencies: - "@babel/runtime": "npm:^7.20.13" - peerDependencies: - "@mui/material": ^5.0.0 - "@types/react": ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10c0/e92b8b265765d1e405cb89884ff2dabd7a4ca60f8f913f462f63eb50a985ab3c0014b7ca767c2e95d538f02f1197ce266ed534d391efb88fccb2e1f4ed499d3d - languageName: node - linkType: hard - -"@mui/material@npm:^5.2.3": - version: 5.11.10 - resolution: "@mui/material@npm:5.11.10" - dependencies: - "@babel/runtime": "npm:^7.20.13" - "@mui/base": "npm:5.0.0-alpha.118" - "@mui/core-downloads-tracker": "npm:^5.11.9" - "@mui/system": "npm:^5.11.9" - "@mui/types": "npm:^7.2.3" - "@mui/utils": "npm:^5.11.9" - "@types/react-transition-group": "npm:^4.4.5" - clsx: "npm:^1.2.1" - csstype: "npm:^3.1.1" - prop-types: "npm:^15.8.1" - react-is: "npm:^18.2.0" - react-transition-group: "npm:^4.4.5" - peerDependencies: - "@emotion/react": ^11.5.0 - "@emotion/styled": ^11.3.0 - "@types/react": ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - react-dom: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - "@emotion/react": - optional: true - "@emotion/styled": - optional: true - "@types/react": - optional: true - checksum: 10c0/58e6bab42e9a28e0dfcd1cd6dc2db3a9476d3d690420a816caebcaae7e9ce6dd13b6d6b905c1693f5ba79eae4183777510ae6a5c19fc3de3eb58621bf185d17f +"fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": + version: 3.1.3 + resolution: "fast-deep-equal@npm:3.1.3" + checksum: 10c0/40dedc862eb8992c54579c66d914635afbec43350afbbe991235fdcb4e3a8d5af1b23ae7e79bef7d4882d0ecee06c3197488026998fb19f72dc95acff1d1b1d0 languageName: node linkType: hard -"@mui/private-theming@npm:^5.11.9": - version: 5.11.9 - resolution: "@mui/private-theming@npm:5.11.9" - dependencies: - "@babel/runtime": "npm:^7.20.13" - "@mui/utils": "npm:^5.11.9" - prop-types: "npm:^15.8.1" - peerDependencies: - "@types/react": ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10c0/c35081699ade0e9ae67771b6ce993eca4e3f58fcfb549b6a51c2931b13bb9ceb0d6a22d2164973b15dd22344a9904934ee271a126efd75a9fa0c78fa338cc302 +"fast-diff@npm:^1.1.2": + version: 1.3.0 + resolution: "fast-diff@npm:1.3.0" + checksum: 10c0/5c19af237edb5d5effda008c891a18a585f74bf12953be57923f17a3a4d0979565fc64dbc73b9e20926b9d895f5b690c618cbb969af0cf022e3222471220ad29 languageName: node linkType: hard -"@mui/styled-engine@npm:^5.11.9": - version: 5.11.9 - resolution: "@mui/styled-engine@npm:5.11.9" - dependencies: - "@babel/runtime": "npm:^7.20.13" - "@emotion/cache": "npm:^11.10.5" - csstype: "npm:^3.1.1" - prop-types: "npm:^15.8.1" - peerDependencies: - "@emotion/react": ^11.4.1 - "@emotion/styled": ^11.3.0 - react: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - "@emotion/react": - optional: true - "@emotion/styled": - optional: true - checksum: 10c0/7561541f447f032fd75ecdb15e1395c080250f4abf2a57d21b707e7a76b0a60d35d7970ba96f428223073dcf2f1a63a25084e8ee85488b8dd32d899bbad7953f +"fast-json-stable-stringify@npm:^2.0.0": + version: 2.1.0 + resolution: "fast-json-stable-stringify@npm:2.1.0" + checksum: 10c0/7f081eb0b8a64e0057b3bb03f974b3ef00135fbf36c1c710895cd9300f13c94ba809bb3a81cf4e1b03f6e5285610a61abbd7602d0652de423144dfee5a389c9b languageName: node linkType: hard -"@mui/system@npm:^5.11.9, @mui/system@npm:^5.4.1": - version: 5.11.9 - resolution: "@mui/system@npm:5.11.9" - dependencies: - "@babel/runtime": "npm:^7.20.13" - "@mui/private-theming": "npm:^5.11.9" - "@mui/styled-engine": "npm:^5.11.9" - "@mui/types": "npm:^7.2.3" - "@mui/utils": "npm:^5.11.9" - clsx: "npm:^1.2.1" - csstype: "npm:^3.1.1" - prop-types: "npm:^15.8.1" - peerDependencies: - "@emotion/react": ^11.5.0 - "@emotion/styled": ^11.3.0 - "@types/react": ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - "@emotion/react": - optional: true - "@emotion/styled": - optional: true - "@types/react": - optional: true - checksum: 10c0/7761035a636756723d6f94758d30ce77b5de5c95fe8304283cae370bb9fe401a5c30d4df83c62087a47347a03c8f776e63dfb17cbc0d0e89c691fd21dd6dc594 +"fast-levenshtein@npm:^2.0.6": + version: 2.0.6 + resolution: "fast-levenshtein@npm:2.0.6" + checksum: 10c0/111972b37338bcb88f7d9e2c5907862c280ebf4234433b95bc611e518d192ccb2d38119c4ac86e26b668d75f7f3894f4ff5c4982899afced7ca78633b08287c4 languageName: node linkType: hard -"@mui/types@npm:^7.2.3": - version: 7.2.3 - resolution: "@mui/types@npm:7.2.3" +"fdir@npm:^6.5.0": + version: 6.5.0 + resolution: "fdir@npm:6.5.0" peerDependencies: - "@types/react": "*" + picomatch: ^3 || ^4 peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10c0/d66575b6e5b37462bc4182ee5eae7f9f10792d4cdbe407fdb055136c62c3e05c6fa8969680373a723fbede0d1b1c17941c2cccd5a0409751a09aa22a712eafc0 - languageName: node - linkType: hard - -"@mui/utils@npm:^5.10.3, @mui/utils@npm:^5.11.9": - version: 5.11.9 - resolution: "@mui/utils@npm:5.11.9" - dependencies: - "@babel/runtime": "npm:^7.20.13" - "@types/prop-types": "npm:^15.7.5" - "@types/react-is": "npm:^16.7.1 || ^17.0.0" - prop-types: "npm:^15.8.1" - react-is: "npm:^18.2.0" - peerDependencies: - react: ^17.0.0 || ^18.0.0 - checksum: 10c0/1ecd8d5505e071edb856149dada78452df1993dca67419f444ffda014319f79211e82b050cb7f758a5f596acea386c727d88817dc4775a8c92ee103b42f32395 - languageName: node - linkType: hard - -"@mui/x-data-grid@npm:^5.1.0": - version: 5.17.24 - resolution: "@mui/x-data-grid@npm:5.17.24" - dependencies: - "@babel/runtime": "npm:^7.18.9" - "@mui/utils": "npm:^5.10.3" - clsx: "npm:^1.2.1" - prop-types: "npm:^15.8.1" - reselect: "npm:^4.1.6" - peerDependencies: - "@mui/material": ^5.4.1 - "@mui/system": ^5.4.1 - react: ^17.0.2 || ^18.0.0 - react-dom: ^17.0.2 || ^18.0.0 - checksum: 10c0/cfa98f2ba180dd35ff28539569858ed5360c2f78e58ffd8a4ba6b756fad67696c72d1dba9e30620a14d7ed23c1f83b3bc015373422352360efdbbd920adb81d1 - languageName: node - linkType: hard - -"@nicolo-ribaudo/eslint-scope-5-internals@npm:5.1.1-v1": - version: 5.1.1-v1 - resolution: "@nicolo-ribaudo/eslint-scope-5-internals@npm:5.1.1-v1" - dependencies: - eslint-scope: "npm:5.1.1" - checksum: 10c0/75dda3e623b8ad7369ca22552d6beee337a814b2d0e8a32d23edd13fcb65c8082b32c5d86e436f3860dd7ade30d91d5db55d4ef9a08fb5a976c718ecc0d88a74 - languageName: node - linkType: hard - -"@nodelib/fs.scandir@npm:2.1.5": - version: 2.1.5 - resolution: "@nodelib/fs.scandir@npm:2.1.5" - dependencies: - "@nodelib/fs.stat": "npm:2.0.5" - run-parallel: "npm:^1.1.9" - checksum: 10c0/732c3b6d1b1e967440e65f284bd06e5821fedf10a1bea9ed2bb75956ea1f30e08c44d3def9d6a230666574edbaf136f8cfd319c14fd1f87c66e6a44449afb2eb - languageName: node - linkType: hard - -"@nodelib/fs.stat@npm:2.0.5, @nodelib/fs.stat@npm:^2.0.2": - version: 2.0.5 - resolution: "@nodelib/fs.stat@npm:2.0.5" - checksum: 10c0/88dafe5e3e29a388b07264680dc996c17f4bda48d163a9d4f5c1112979f0ce8ec72aa7116122c350b4e7976bc5566dc3ddb579be1ceaacc727872eb4ed93926d - languageName: node - linkType: hard - -"@nodelib/fs.walk@npm:^1.2.3, @nodelib/fs.walk@npm:^1.2.8": - version: 1.2.8 - resolution: "@nodelib/fs.walk@npm:1.2.8" - dependencies: - "@nodelib/fs.scandir": "npm:2.1.5" - fastq: "npm:^1.6.0" - checksum: 10c0/db9de047c3bb9b51f9335a7bb46f4fcfb6829fb628318c12115fbaf7d369bfce71c15b103d1fc3b464812d936220ee9bc1c8f762d032c9f6be9acc99249095b1 - languageName: node - linkType: hard - -"@npmcli/agent@npm:^2.0.0": - version: 2.2.2 - resolution: "@npmcli/agent@npm:2.2.2" - dependencies: - agent-base: "npm:^7.1.0" - http-proxy-agent: "npm:^7.0.0" - https-proxy-agent: "npm:^7.0.1" - lru-cache: "npm:^10.0.1" - socks-proxy-agent: "npm:^8.0.3" - checksum: 10c0/325e0db7b287d4154ecd164c0815c08007abfb07653cc57bceded17bb7fd240998a3cbdbe87d700e30bef494885eccc725ab73b668020811d56623d145b524ae - languageName: node - linkType: hard - -"@npmcli/fs@npm:^3.1.0": - version: 3.1.1 - resolution: "@npmcli/fs@npm:3.1.1" - dependencies: - semver: "npm:^7.3.5" - checksum: 10c0/c37a5b4842bfdece3d14dfdb054f73fe15ed2d3da61b34ff76629fb5b1731647c49166fd2a8bf8b56fcfa51200382385ea8909a3cbecdad612310c114d3f6c99 - languageName: node - linkType: hard - -"@pkgjs/parseargs@npm:^0.11.0": - version: 0.11.0 - resolution: "@pkgjs/parseargs@npm:0.11.0" - checksum: 10c0/5bd7576bb1b38a47a7fc7b51ac9f38748e772beebc56200450c4a817d712232b8f1d3ef70532c80840243c657d491cf6a6be1e3a214cff907645819fdc34aadd - languageName: node - linkType: hard - -"@pkgr/core@npm:^0.1.0": - version: 0.1.1 - resolution: "@pkgr/core@npm:0.1.1" - checksum: 10c0/3f7536bc7f57320ab2cf96f8973664bef624710c403357429fbf680a5c3b4843c1dbd389bb43daa6b1f6f1f007bb082f5abcb76bb2b5dc9f421647743b71d3d8 - languageName: node - linkType: hard - -"@popperjs/core@npm:^2.11.6": - version: 2.11.6 - resolution: "@popperjs/core@npm:2.11.6" - checksum: 10c0/90b1361ab1a19cd351e482a88cb6cf13cf25973e20797bf9b97223e64b87bde8d9668ab2f33be24475e2a092d362ac256c361b16ea4e4ab5b21f2aeaef5f9bb7 - languageName: node - linkType: hard - -"@remix-run/router@npm:1.19.1": - version: 1.19.1 - resolution: "@remix-run/router@npm:1.19.1" - checksum: 10c0/9101fc96646e5107b6b0ef248d4c93bd965590c37ac02d35bcc57d1902467db7fc6eeec0a1fb97d0ce5bc96fae58e75239555e44a983239a61badba18e82d3b8 - languageName: node - linkType: hard - -"@rollup/rollup-android-arm-eabi@npm:4.21.2": - version: 4.21.2 - resolution: "@rollup/rollup-android-arm-eabi@npm:4.21.2" - conditions: os=android & cpu=arm - languageName: node - linkType: hard - -"@rollup/rollup-android-arm64@npm:4.21.2": - version: 4.21.2 - resolution: "@rollup/rollup-android-arm64@npm:4.21.2" - conditions: os=android & cpu=arm64 - languageName: node - linkType: hard - -"@rollup/rollup-darwin-arm64@npm:4.21.2": - version: 4.21.2 - resolution: "@rollup/rollup-darwin-arm64@npm:4.21.2" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - -"@rollup/rollup-darwin-x64@npm:4.21.2": - version: 4.21.2 - resolution: "@rollup/rollup-darwin-x64@npm:4.21.2" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - -"@rollup/rollup-linux-arm-gnueabihf@npm:4.21.2": - version: 4.21.2 - resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.21.2" - conditions: os=linux & cpu=arm & libc=glibc - languageName: node - linkType: hard - -"@rollup/rollup-linux-arm-musleabihf@npm:4.21.2": - version: 4.21.2 - resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.21.2" - conditions: os=linux & cpu=arm & libc=musl - languageName: node - linkType: hard - -"@rollup/rollup-linux-arm64-gnu@npm:4.21.2": - version: 4.21.2 - resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.21.2" - conditions: os=linux & cpu=arm64 & libc=glibc - languageName: node - linkType: hard - -"@rollup/rollup-linux-arm64-musl@npm:4.21.2": - version: 4.21.2 - resolution: "@rollup/rollup-linux-arm64-musl@npm:4.21.2" - conditions: os=linux & cpu=arm64 & libc=musl - languageName: node - linkType: hard - -"@rollup/rollup-linux-powerpc64le-gnu@npm:4.21.2": - version: 4.21.2 - resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.21.2" - conditions: os=linux & cpu=ppc64 & libc=glibc - languageName: node - linkType: hard - -"@rollup/rollup-linux-riscv64-gnu@npm:4.21.2": - version: 4.21.2 - resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.21.2" - conditions: os=linux & cpu=riscv64 & libc=glibc - languageName: node - linkType: hard - -"@rollup/rollup-linux-s390x-gnu@npm:4.21.2": - version: 4.21.2 - resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.21.2" - conditions: os=linux & cpu=s390x & libc=glibc - languageName: node - linkType: hard - -"@rollup/rollup-linux-x64-gnu@npm:4.21.2": - version: 4.21.2 - resolution: "@rollup/rollup-linux-x64-gnu@npm:4.21.2" - conditions: os=linux & cpu=x64 & libc=glibc - languageName: node - linkType: hard - -"@rollup/rollup-linux-x64-musl@npm:4.21.2": - version: 4.21.2 - resolution: "@rollup/rollup-linux-x64-musl@npm:4.21.2" - conditions: os=linux & cpu=x64 & libc=musl - languageName: node - linkType: hard - -"@rollup/rollup-win32-arm64-msvc@npm:4.21.2": - version: 4.21.2 - resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.21.2" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - -"@rollup/rollup-win32-ia32-msvc@npm:4.21.2": - version: 4.21.2 - resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.21.2" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - -"@rollup/rollup-win32-x64-msvc@npm:4.21.2": - version: 4.21.2 - resolution: "@rollup/rollup-win32-x64-msvc@npm:4.21.2" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - -"@rtsao/scc@npm:^1.1.0": - version: 1.1.0 - resolution: "@rtsao/scc@npm:1.1.0" - checksum: 10c0/b5bcfb0d87f7d1c1c7c0f7693f53b07866ed9fec4c34a97a8c948fb9a7c0082e416ce4d3b60beb4f5e167cbe04cdeefbf6771320f3ede059b9ce91188c409a5b - languageName: node - linkType: hard - -"@rushstack/eslint-patch@npm:^1.1.0": - version: 1.10.4 - resolution: "@rushstack/eslint-patch@npm:1.10.4" - checksum: 10c0/de312bd7a3cb0f313c9720029eb719d8762fe54946cce2d33ac142b1cbb5817c4a5a92518dfa476c26311602d37f5a8f7caa90a0c73e3d6a56f9a05d2799c172 - languageName: node - linkType: hard - -"@sinonjs/commons@npm:^2.0.0": - version: 2.0.0 - resolution: "@sinonjs/commons@npm:2.0.0" - dependencies: - type-detect: "npm:4.0.8" - checksum: 10c0/babe3fdfc7dfb810f6918f2ae055032a1c7c18910595f1c6bfda87bb1737c1a57268d4ca78c3d8ad2fa4aae99ff79796fad76be735a5a38ab763c0b3cfad1ae7 - languageName: node - linkType: hard - -"@sinonjs/fake-timers@npm:10.0.2, @sinonjs/fake-timers@npm:^10.0.2": - version: 10.0.2 - resolution: "@sinonjs/fake-timers@npm:10.0.2" - dependencies: - "@sinonjs/commons": "npm:^2.0.0" - checksum: 10c0/24555ed94053319fa18d4efa0923b295a445a00d2515d260b9e4e2b5943bd8b5b55fee85baabb2819a13ca1f57dbc1949265a350f592eef9e2535ec9de711ebc - languageName: node - linkType: hard - -"@sinonjs/samsam@npm:^7.0.1": - version: 7.0.1 - resolution: "@sinonjs/samsam@npm:7.0.1" - dependencies: - "@sinonjs/commons": "npm:^2.0.0" - lodash.get: "npm:^4.4.2" - type-detect: "npm:^4.0.8" - checksum: 10c0/cc5e61e102dfa837ea3dd0102c924636de8a43e39d3e5ea009b57095be15fad807535feb8b2a3e90686f022451ab2dfa49c99167d68c661e7fab19a07227d467 - languageName: node - linkType: hard - -"@sinonjs/text-encoding@npm:^0.7.1": - version: 0.7.2 - resolution: "@sinonjs/text-encoding@npm:0.7.2" - checksum: 10c0/583a45bf3643169e313ff9d4395aff28b0c4f330d3697e252c3effc13d4303ee30f83df542732c1a68617720e4ea6fc08d48a3d9151c9b354a7fc356a8e9b162 - languageName: node - linkType: hard - -"@socket.io/component-emitter@npm:~3.1.0": - version: 3.1.0 - resolution: "@socket.io/component-emitter@npm:3.1.0" - checksum: 10c0/b838ccccf74c36fa7d3ed89a7efb5858cba1a84db4d08250c2fc44d8235140f10d31875bde71517d8503cb3fb08fcd34d3b7a3d0d89058ca3f74f7c816f0fb9c - languageName: node - linkType: hard - -"@stakingbrain/brain@workspace:^0.1.0, @stakingbrain/brain@workspace:packages/brain": - version: 0.0.0-use.local - resolution: "@stakingbrain/brain@workspace:packages/brain" - dependencies: - "@stakingbrain/common": "workspace:^0.1.0" - "@types/cors": "npm:^2.8.13" - "@types/express": "npm:^5.0.0" - "@types/lodash-es": "npm:^4.17.12" - "@types/lowdb": "npm:^1.0.15" - "@types/mocha": "npm:^10.0.7" - "@types/sinon": "npm:^10.0.13" - ajv: "npm:^8.12.0" - cors: "npm:^2.8.5" - dotenv: "npm:^16.0.3" - express: "npm:^5.0.0" - lodash-es: "npm:^4.17.21" - lowdb: "npm:^7.0.1" - mocha: "npm:^10.7.3" - postgres: "npm:^3.4.4" - rimraf: "npm:^4.1.1" - sinon: "npm:^15.0.1" - socket.io: "npm:^4.7.5" - typescript: "npm:^5.5.4" - languageName: unknown - linkType: soft - -"@stakingbrain/common@workspace:^0.1.0, @stakingbrain/common@workspace:packages/common": - version: 0.0.0-use.local - resolution: "@stakingbrain/common@workspace:packages/common" - dependencies: - "@types/sinon": "npm:^10.0.13" - sinon: "npm:^15.0.1" - typescript: "npm:^5.5.4" - languageName: unknown - linkType: soft - -"@stakingbrain/ui@workspace:packages/ui": - version: 0.0.0-use.local - resolution: "@stakingbrain/ui@workspace:packages/ui" - dependencies: - "@babel/core": "npm:^7.0.0" - "@babel/plugin-syntax-flow": "npm:^7.23.3" - "@babel/plugin-transform-react-jsx": "npm:^7.22.15" - "@emotion/react": "npm:^11.7.0" - "@emotion/styled": "npm:^11.6.0" - "@mui/icons-material": "npm:^5.2.4" - "@mui/material": "npm:^5.2.3" - "@mui/system": "npm:^5.4.1" - "@mui/x-data-grid": "npm:^5.1.0" - "@stakingbrain/brain": "workspace:^0.1.0" - "@stakingbrain/common": "workspace:^0.1.0" - "@types/lodash-es": "npm:^4.17.6" - "@types/react": "npm:^17.0.0" - "@types/react-dom": "npm:^17.0.0" - "@vitejs/plugin-react": "npm:^4.3.1" - history: "npm:5" - lodash-es: "npm:^4.17.21" - react: "npm:^18.3.1" - react-dom: "npm:^18.3.1" - react-dropzone: "npm:^11.4.2" - react-router-dom: "npm:^6.26.1" - socket.io-client: "npm:^4.7.5" - vite: "npm:^5.4.2" - vite-tsconfig-paths: "npm:^5.0.1" - languageName: unknown - linkType: soft - -"@types/babel__core@npm:^7.20.5": - version: 7.20.5 - resolution: "@types/babel__core@npm:7.20.5" - dependencies: - "@babel/parser": "npm:^7.20.7" - "@babel/types": "npm:^7.20.7" - "@types/babel__generator": "npm:*" - "@types/babel__template": "npm:*" - "@types/babel__traverse": "npm:*" - checksum: 10c0/bdee3bb69951e833a4b811b8ee9356b69a61ed5b7a23e1a081ec9249769117fa83aaaf023bb06562a038eb5845155ff663e2d5c75dd95c1d5ccc91db012868ff - languageName: node - linkType: hard - -"@types/babel__generator@npm:*": - version: 7.6.4 - resolution: "@types/babel__generator@npm:7.6.4" - dependencies: - "@babel/types": "npm:^7.0.0" - checksum: 10c0/e0051b450e4ba2df0a7e386f08df902a4e920f6f8d6f185d69ddbe9b0e2e2d3ae434bb51e437bc0fca2a9a0f5dc4ca44d3a1941ef75e74371e8be5bf64416fe4 - languageName: node - linkType: hard - -"@types/babel__template@npm:*": - version: 7.4.1 - resolution: "@types/babel__template@npm:7.4.1" - dependencies: - "@babel/parser": "npm:^7.1.0" - "@babel/types": "npm:^7.0.0" - checksum: 10c0/6f180e96c39765487f27e861d43eebed341ec7a2fc06cdf5a52c22872fae67f474ca165d149c708f4fd9d5482beb66c0a92f77411b234bb30262ed2303e50b1a - languageName: node - linkType: hard - -"@types/babel__traverse@npm:*": - version: 7.18.3 - resolution: "@types/babel__traverse@npm:7.18.3" - dependencies: - "@babel/types": "npm:^7.3.0" - checksum: 10c0/4214fd3e95925d9a7efa01142969a310263430d4f5de89be6c9c193110666677415161b474fa627d751dfd0f1eb7dc1c84c48f8b53098625c6bc78917683215a - languageName: node - linkType: hard - -"@types/body-parser@npm:*": - version: 1.19.5 - resolution: "@types/body-parser@npm:1.19.5" - dependencies: - "@types/connect": "npm:*" - "@types/node": "npm:*" - checksum: 10c0/aebeb200f25e8818d8cf39cd0209026750d77c9b85381cdd8deeb50913e4d18a1ebe4b74ca9b0b4d21952511eeaba5e9fbbf739b52731a2061e206ec60d568df - languageName: node - linkType: hard - -"@types/chai@npm:^4.3.19": - version: 4.3.19 - resolution: "@types/chai@npm:4.3.19" - checksum: 10c0/8fd573192e486803c4d04185f2b0fab554660d9a1300dbed5bde9747ab8bef15f462a226f560ed5ca48827eecaf8d71eed64aa653ff9aec72fb2eae272e43a84 - languageName: node - linkType: hard - -"@types/connect@npm:*": - version: 3.4.38 - resolution: "@types/connect@npm:3.4.38" - dependencies: - "@types/node": "npm:*" - checksum: 10c0/2e1cdba2c410f25649e77856505cd60223250fa12dff7a503e492208dbfdd25f62859918f28aba95315251fd1f5e1ffbfca1e25e73037189ab85dd3f8d0a148c - languageName: node - linkType: hard - -"@types/cookie@npm:^0.4.1": - version: 0.4.1 - resolution: "@types/cookie@npm:0.4.1" - checksum: 10c0/f96afe12bd51be1ec61410b0641243d93fa3a494702407c787a4c872b5c8bcd39b224471452055e44a9ce42af1a636e87d161994226eaf4c2be9c30f60418409 - languageName: node - linkType: hard - -"@types/cors@npm:^2.8.12, @types/cors@npm:^2.8.13": - version: 2.8.13 - resolution: "@types/cors@npm:2.8.13" - dependencies: - "@types/node": "npm:*" - checksum: 10c0/7ef49f6a26daa98781f0cb1b6095cef1da66387bbba8046270bad203fdcab2accdc5a65c439c2a2ef91d1f6d521d78d80b73886926e19e99fc06a513e3e0bdb3 - languageName: node - linkType: hard - -"@types/estree@npm:1.0.5": - version: 1.0.5 - resolution: "@types/estree@npm:1.0.5" - checksum: 10c0/b3b0e334288ddb407c7b3357ca67dbee75ee22db242ca7c56fe27db4e1a31989cb8af48a84dd401deb787fe10cc6b2ab1ee82dc4783be87ededbe3d53c79c70d - languageName: node - linkType: hard - -"@types/express-serve-static-core@npm:^5.0.0": - version: 5.0.0 - resolution: "@types/express-serve-static-core@npm:5.0.0" - dependencies: - "@types/node": "npm:*" - "@types/qs": "npm:*" - "@types/range-parser": "npm:*" - "@types/send": "npm:*" - checksum: 10c0/671a67a5b367e19aa634dcd515364212490f10efb938fc1097082085a883ccb11c81ec96a3c2b5cc67d5756e5cb1ccbf1de192806f8193bb7de341994beb4ea6 - languageName: node - linkType: hard - -"@types/express@npm:^5.0.0": - version: 5.0.0 - resolution: "@types/express@npm:5.0.0" - dependencies: - "@types/body-parser": "npm:*" - "@types/express-serve-static-core": "npm:^5.0.0" - "@types/qs": "npm:*" - "@types/serve-static": "npm:*" - checksum: 10c0/0d74b53aefa69c3b3817ee9b5145fd50d7dbac52a8986afc2d7500085c446656d0b6dc13158c04e2d9f18f4324d4d93b0452337c5ff73dd086dca3e4ff11f47b - languageName: node - linkType: hard - -"@types/http-errors@npm:*": - version: 2.0.4 - resolution: "@types/http-errors@npm:2.0.4" - checksum: 10c0/494670a57ad4062fee6c575047ad5782506dd35a6b9ed3894cea65830a94367bd84ba302eb3dde331871f6d70ca287bfedb1b2cf658e6132cd2cbd427ab56836 - languageName: node - linkType: hard - -"@types/json-schema@npm:^7.0.9": - version: 7.0.11 - resolution: "@types/json-schema@npm:7.0.11" - checksum: 10c0/bd1f9a7b898ff15c4bb494eb19124f2d688b804c39f07cbf135ac73f35324970e9e8329b72aae1fb543d925ea295a1568b23056c26658cecec4741fa28c3b81a - languageName: node - linkType: hard - -"@types/json5@npm:^0.0.29": - version: 0.0.29 - resolution: "@types/json5@npm:0.0.29" - checksum: 10c0/6bf5337bc447b706bb5b4431d37686aa2ea6d07cfd6f79cc31de80170d6ff9b1c7384a9c0ccbc45b3f512bae9e9f75c2e12109806a15331dc94e8a8db6dbb4ac - languageName: node - linkType: hard - -"@types/lodash-es@npm:^4.17.12": - version: 4.17.12 - resolution: "@types/lodash-es@npm:4.17.12" - dependencies: - "@types/lodash": "npm:*" - checksum: 10c0/5d12d2cede07f07ab067541371ed1b838a33edb3c35cb81b73284e93c6fd0c4bbeaefee984e69294bffb53f62d7272c5d679fdba8e595ff71e11d00f2601dde0 - languageName: node - linkType: hard - -"@types/lodash-es@npm:^4.17.6": - version: 4.17.6 - resolution: "@types/lodash-es@npm:4.17.6" - dependencies: - "@types/lodash": "npm:*" - checksum: 10c0/bf7a8b0d16e05c60aad117717771cc2a9fa2d6205f2e138c52656e889dbfb2c76d469991a990fa46989804daf2f06e075d2d8e62c7199b0d2f574a290c8c39d6 - languageName: node - linkType: hard - -"@types/lodash@npm:*": - version: 4.14.191 - resolution: "@types/lodash@npm:4.14.191" - checksum: 10c0/cca0bc3b5f1193a50a39a9c6e14892e7cf57ab81ca05985e1ccee0d732dd3b22a8d669fa87560757051c6d73f4f63a2e2248ce9a7d9c159d0d5e3f331baf6a62 - languageName: node - linkType: hard - -"@types/lowdb@npm:^1.0.15": - version: 1.0.15 - resolution: "@types/lowdb@npm:1.0.15" - dependencies: - "@types/lodash": "npm:*" - checksum: 10c0/09a64a4ce23afb7b36f3cd4fd1091cc2dac6792d5c89e8381def5e1453b2eeeb6b06f8eb9620c528fcfee5da7acc83ae8d1c00fe882e08e0e8c97d379c595b74 - languageName: node - linkType: hard - -"@types/mime@npm:^1": - version: 1.3.5 - resolution: "@types/mime@npm:1.3.5" - checksum: 10c0/c2ee31cd9b993804df33a694d5aa3fa536511a49f2e06eeab0b484fef59b4483777dbb9e42a4198a0809ffbf698081fdbca1e5c2218b82b91603dfab10a10fbc - languageName: node - linkType: hard - -"@types/mocha@npm:^10.0.7": - version: 10.0.7 - resolution: "@types/mocha@npm:10.0.7" - checksum: 10c0/48a2df4dd02b6e66a11129dca6a23cf0cc3995faf8525286eb851043685bd8b7444780f4bb29a1c42df7559ed63294e5308bfce3a6b862ad2e0359cb21c21329 - languageName: node - linkType: hard - -"@types/node@npm:*, @types/node@npm:>=10.0.0": - version: 18.14.0 - resolution: "@types/node@npm:18.14.0" - checksum: 10c0/c4b19a3e0a5d97fea4e70edffc095455aec4662883a9fc4bea5d39808995989a58501f2c7e716cfdefe82bc00edeaa93a40ede2a69c9459bd86ad61adab35c55 - languageName: node - linkType: hard - -"@types/node@npm:^22.5.3": - version: 22.5.3 - resolution: "@types/node@npm:22.5.3" - dependencies: - undici-types: "npm:~6.19.2" - checksum: 10c0/a068e31dd424a0eadfba7d9a5c5b415b76cfd729f3caa299674ad257f59df85c5fe77f1d0a343e811864c790baefb9003d7627618ee3cf85400af338481ba29f - languageName: node - linkType: hard - -"@types/parse-json@npm:^4.0.0": - version: 4.0.0 - resolution: "@types/parse-json@npm:4.0.0" - checksum: 10c0/1d3012ab2fcdad1ba313e1d065b737578f6506c8958e2a7a5bdbdef517c7e930796cb1599ee067d5dee942fb3a764df64b5eef7e9ae98548d776e86dcffba985 - languageName: node - linkType: hard - -"@types/prop-types@npm:*, @types/prop-types@npm:^15.7.5": - version: 15.7.5 - resolution: "@types/prop-types@npm:15.7.5" - checksum: 10c0/648aae41423821c61c83823ae36116c8d0f68258f8b609bdbc257752dcd616438d6343d554262aa9a7edaee5a19aca2e028a74fa2d0f40fffaf2816bc7056857 - languageName: node - linkType: hard - -"@types/qs@npm:*": - version: 6.9.16 - resolution: "@types/qs@npm:6.9.16" - checksum: 10c0/a4e871b80fff623755e356fd1f225aea45ff7a29da30f99fddee1a05f4f5f33485b314ab5758145144ed45708f97e44595aa9a8368e9bbc083932f931b12dbb6 - languageName: node - linkType: hard - -"@types/range-parser@npm:*": - version: 1.2.7 - resolution: "@types/range-parser@npm:1.2.7" - checksum: 10c0/361bb3e964ec5133fa40644a0b942279ed5df1949f21321d77de79f48b728d39253e5ce0408c9c17e4e0fd95ca7899da36841686393b9f7a1e209916e9381a3c - languageName: node - linkType: hard - -"@types/react-dom@npm:^17.0.0": - version: 17.0.19 - resolution: "@types/react-dom@npm:17.0.19" - dependencies: - "@types/react": "npm:^17" - checksum: 10c0/e70ed030e20293eebe8f1ada9e8bd5e1fc6a8c4a0b2ba00e94a3c15af733fe8a1c25a8d7068978480719451024833c6195129a2821adf8485fe7ec1adbc12a04 - languageName: node - linkType: hard - -"@types/react-is@npm:^16.7.1 || ^17.0.0": - version: 17.0.3 - resolution: "@types/react-is@npm:17.0.3" - dependencies: - "@types/react": "npm:*" - checksum: 10c0/839382b66b2b2e3023647f5ba0c382ddc6aa01c1bc9f64608f82bbc871a905ba9b988838619914d8348c2a511717c6bd3701cb866bb9e4abfabdbe544efb695b - languageName: node - linkType: hard - -"@types/react-transition-group@npm:^4.4.5": - version: 4.4.5 - resolution: "@types/react-transition-group@npm:4.4.5" - dependencies: - "@types/react": "npm:*" - checksum: 10c0/c0d81634ca5e1efac3ca6f6f006245976d584833ab9e933edf08b66551c1c7b9f0bc7878897f57ba44b137d3754583d623c932fe4b7721840ae5218ec2414942 - languageName: node - linkType: hard - -"@types/react@npm:*": - version: 18.0.28 - resolution: "@types/react@npm:18.0.28" - dependencies: - "@types/prop-types": "npm:*" - "@types/scheduler": "npm:*" - csstype: "npm:^3.0.2" - checksum: 10c0/6d94ec25c18b39aac687814b3a717e767f2fba3289c3b682caa4d0182160a72a9816a612b5a207c8ea73f1d288019f56f3d08742ac7cfff91719abb77da9d11a - languageName: node - linkType: hard - -"@types/react@npm:^17, @types/react@npm:^17.0.0": - version: 17.0.53 - resolution: "@types/react@npm:17.0.53" - dependencies: - "@types/prop-types": "npm:*" - "@types/scheduler": "npm:*" - csstype: "npm:^3.0.2" - checksum: 10c0/4ff3fa58ab32deefacdbd0ee002e294548391238ce2dea2cad213e11b4cc61480be48cb2b7ed3c53220ee1a5b9cadf3417a20db817ab2ca4669c9a4a45042b92 - languageName: node - linkType: hard - -"@types/scheduler@npm:*": - version: 0.16.2 - resolution: "@types/scheduler@npm:0.16.2" - checksum: 10c0/89a3a922f03609b61c270d534226791edeedcb1b06f0225d5543ac17830254624ef9d8a97ad05418e4ce549dd545bddf1ff28cb90658ff10721ad14556ca68a5 - languageName: node - linkType: hard - -"@types/semver@npm:^7.3.12": - version: 7.5.8 - resolution: "@types/semver@npm:7.5.8" - checksum: 10c0/8663ff927234d1c5fcc04b33062cb2b9fcfbe0f5f351ed26c4d1e1581657deebd506b41ff7fdf89e787e3d33ce05854bc01686379b89e9c49b564c4cfa988efa - languageName: node - linkType: hard - -"@types/send@npm:*": - version: 0.17.4 - resolution: "@types/send@npm:0.17.4" - dependencies: - "@types/mime": "npm:^1" - "@types/node": "npm:*" - checksum: 10c0/7f17fa696cb83be0a104b04b424fdedc7eaba1c9a34b06027239aba513b398a0e2b7279778af521f516a397ced417c96960e5f50fcfce40c4bc4509fb1a5883c - languageName: node - linkType: hard - -"@types/serve-static@npm:*": - version: 1.15.7 - resolution: "@types/serve-static@npm:1.15.7" - dependencies: - "@types/http-errors": "npm:*" - "@types/node": "npm:*" - "@types/send": "npm:*" - checksum: 10c0/26ec864d3a626ea627f8b09c122b623499d2221bbf2f470127f4c9ebfe92bd8a6bb5157001372d4c4bd0dd37a1691620217d9dc4df5aa8f779f3fd996b1c60ae - languageName: node - linkType: hard - -"@types/sinon@npm:^10.0.13": - version: 10.0.13 - resolution: "@types/sinon@npm:10.0.13" - dependencies: - "@types/sinonjs__fake-timers": "npm:*" - checksum: 10c0/5f9f27d362f6ec1964802a23a85ac2ed4479271a95848fbb63aa1babe7dc9a60414d345faff3311e921dd3e5e17b835cf121b3a55f45a7470ecd70ba081a2a96 - languageName: node - linkType: hard - -"@types/sinonjs__fake-timers@npm:*": - version: 8.1.2 - resolution: "@types/sinonjs__fake-timers@npm:8.1.2" - checksum: 10c0/631c55f3ac4caacc7f7a1c84b4597fdf7e162492cc5fc50e9440917efb9b506b0706ad8591e41e83487d9c3688163ef13720767b9821408fb4be6f525f829be7 - languageName: node - linkType: hard - -"@typescript-eslint/eslint-plugin@npm:^5.5.0": - version: 5.62.0 - resolution: "@typescript-eslint/eslint-plugin@npm:5.62.0" - dependencies: - "@eslint-community/regexpp": "npm:^4.4.0" - "@typescript-eslint/scope-manager": "npm:5.62.0" - "@typescript-eslint/type-utils": "npm:5.62.0" - "@typescript-eslint/utils": "npm:5.62.0" - debug: "npm:^4.3.4" - graphemer: "npm:^1.4.0" - ignore: "npm:^5.2.0" - natural-compare-lite: "npm:^1.4.0" - semver: "npm:^7.3.7" - tsutils: "npm:^3.21.0" - peerDependencies: - "@typescript-eslint/parser": ^5.0.0 - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - peerDependenciesMeta: - typescript: - optional: true - checksum: 10c0/3f40cb6bab5a2833c3544e4621b9fdacd8ea53420cadc1c63fac3b89cdf5c62be1e6b7bcf56976dede5db4c43830de298ced3db60b5494a3b961ca1b4bff9f2a - languageName: node - linkType: hard - -"@typescript-eslint/eslint-plugin@npm:^8.4.0": - version: 8.4.0 - resolution: "@typescript-eslint/eslint-plugin@npm:8.4.0" - dependencies: - "@eslint-community/regexpp": "npm:^4.10.0" - "@typescript-eslint/scope-manager": "npm:8.4.0" - "@typescript-eslint/type-utils": "npm:8.4.0" - "@typescript-eslint/utils": "npm:8.4.0" - "@typescript-eslint/visitor-keys": "npm:8.4.0" - graphemer: "npm:^1.4.0" - ignore: "npm:^5.3.1" - natural-compare: "npm:^1.4.0" - ts-api-utils: "npm:^1.3.0" - peerDependencies: - "@typescript-eslint/parser": ^8.0.0 || ^8.0.0-alpha.0 - eslint: ^8.57.0 || ^9.0.0 - peerDependenciesMeta: - typescript: - optional: true - checksum: 10c0/c75e9bb176e9e0277c9f9c4c006bc2c31ac91984e555de1390a9bbe876e3b6787d59d96015b3f0cd083fd22c814aea4ed4858910d3afdd24d64ab79815da31e5 - languageName: node - linkType: hard - -"@typescript-eslint/experimental-utils@npm:^5.0.0": - version: 5.62.0 - resolution: "@typescript-eslint/experimental-utils@npm:5.62.0" - dependencies: - "@typescript-eslint/utils": "npm:5.62.0" - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - checksum: 10c0/f7037977e00849cd8c03677a88b0659a4f0e0b1e0151aebb47c49c92b8e57408578142df598eac08b364623d926343c724f42494f87662e437b1c89f0b2e815b - languageName: node - linkType: hard - -"@typescript-eslint/parser@npm:^5.5.0": - version: 5.62.0 - resolution: "@typescript-eslint/parser@npm:5.62.0" - dependencies: - "@typescript-eslint/scope-manager": "npm:5.62.0" - "@typescript-eslint/types": "npm:5.62.0" - "@typescript-eslint/typescript-estree": "npm:5.62.0" - debug: "npm:^4.3.4" - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - peerDependenciesMeta: - typescript: - optional: true - checksum: 10c0/315194b3bf39beb9bd16c190956c46beec64b8371e18d6bb72002108b250983eb1e186a01d34b77eb4045f4941acbb243b16155fbb46881105f65e37dc9e24d4 - languageName: node - linkType: hard - -"@typescript-eslint/parser@npm:^8.4.0": - version: 8.4.0 - resolution: "@typescript-eslint/parser@npm:8.4.0" - dependencies: - "@typescript-eslint/scope-manager": "npm:8.4.0" - "@typescript-eslint/types": "npm:8.4.0" - "@typescript-eslint/typescript-estree": "npm:8.4.0" - "@typescript-eslint/visitor-keys": "npm:8.4.0" - debug: "npm:^4.3.4" - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - peerDependenciesMeta: - typescript: - optional: true - checksum: 10c0/19f3358e5bc4bbad693183eefe1a90ea64be054a934bc2c8a972ff4738b94580b55ad4955af5797db42298628caa59b3ba3f9fd960582b5fc2c836da3a4578a5 - languageName: node - linkType: hard - -"@typescript-eslint/scope-manager@npm:5.62.0": - version: 5.62.0 - resolution: "@typescript-eslint/scope-manager@npm:5.62.0" - dependencies: - "@typescript-eslint/types": "npm:5.62.0" - "@typescript-eslint/visitor-keys": "npm:5.62.0" - checksum: 10c0/861253235576c1c5c1772d23cdce1418c2da2618a479a7de4f6114a12a7ca853011a1e530525d0931c355a8fd237b9cd828fac560f85f9623e24054fd024726f - languageName: node - linkType: hard - -"@typescript-eslint/scope-manager@npm:8.4.0": - version: 8.4.0 - resolution: "@typescript-eslint/scope-manager@npm:8.4.0" - dependencies: - "@typescript-eslint/types": "npm:8.4.0" - "@typescript-eslint/visitor-keys": "npm:8.4.0" - checksum: 10c0/95188c663df7db106529c6b93c4c7c61647ed34ab6dd48114e41ddf49140ff606c5501ce2ae451a988ec49b5d3874ea96ff212fc102802327b10affd2ff80a37 - languageName: node - linkType: hard - -"@typescript-eslint/type-utils@npm:5.62.0": - version: 5.62.0 - resolution: "@typescript-eslint/type-utils@npm:5.62.0" - dependencies: - "@typescript-eslint/typescript-estree": "npm:5.62.0" - "@typescript-eslint/utils": "npm:5.62.0" - debug: "npm:^4.3.4" - tsutils: "npm:^3.21.0" - peerDependencies: - eslint: "*" - peerDependenciesMeta: - typescript: - optional: true - checksum: 10c0/93112e34026069a48f0484b98caca1c89d9707842afe14e08e7390af51cdde87378df29d213d3bbd10a7cfe6f91b228031b56218515ce077bdb62ddea9d9f474 - languageName: node - linkType: hard - -"@typescript-eslint/type-utils@npm:8.4.0": - version: 8.4.0 - resolution: "@typescript-eslint/type-utils@npm:8.4.0" - dependencies: - "@typescript-eslint/typescript-estree": "npm:8.4.0" - "@typescript-eslint/utils": "npm:8.4.0" - debug: "npm:^4.3.4" - ts-api-utils: "npm:^1.3.0" - peerDependenciesMeta: - typescript: - optional: true - checksum: 10c0/ae51100594d9ca61c7577b5aed0bd10c1959725df5c38cd9653eed1fd3dbdfff9146b6e48f3409994b4c8d781b9d95025c36b30f73a5a1b3dbdee6d142cecc87 - languageName: node - linkType: hard - -"@typescript-eslint/types@npm:5.62.0": - version: 5.62.0 - resolution: "@typescript-eslint/types@npm:5.62.0" - checksum: 10c0/7febd3a7f0701c0b927e094f02e82d8ee2cada2b186fcb938bc2b94ff6fbad88237afc304cbaf33e82797078bbbb1baf91475f6400912f8b64c89be79bfa4ddf - languageName: node - linkType: hard - -"@typescript-eslint/types@npm:8.4.0": - version: 8.4.0 - resolution: "@typescript-eslint/types@npm:8.4.0" - checksum: 10c0/15e09ced84827c349553530a31822f06ae5bad456c03d561b7d0c64b6ad9b5d7ca795e030bd93e65d5a2cd41bfde36ed08dcd2ff9feaa8b60a67080827f47ecb - languageName: node - linkType: hard - -"@typescript-eslint/typescript-estree@npm:5.62.0": - version: 5.62.0 - resolution: "@typescript-eslint/typescript-estree@npm:5.62.0" - dependencies: - "@typescript-eslint/types": "npm:5.62.0" - "@typescript-eslint/visitor-keys": "npm:5.62.0" - debug: "npm:^4.3.4" - globby: "npm:^11.1.0" - is-glob: "npm:^4.0.3" - semver: "npm:^7.3.7" - tsutils: "npm:^3.21.0" - peerDependenciesMeta: - typescript: - optional: true - checksum: 10c0/d7984a3e9d56897b2481940ec803cb8e7ead03df8d9cfd9797350be82ff765dfcf3cfec04e7355e1779e948da8f02bc5e11719d07a596eb1cb995c48a95e38cf - languageName: node - linkType: hard - -"@typescript-eslint/typescript-estree@npm:8.4.0": - version: 8.4.0 - resolution: "@typescript-eslint/typescript-estree@npm:8.4.0" - dependencies: - "@typescript-eslint/types": "npm:8.4.0" - "@typescript-eslint/visitor-keys": "npm:8.4.0" - debug: "npm:^4.3.4" - fast-glob: "npm:^3.3.2" - is-glob: "npm:^4.0.3" - minimatch: "npm:^9.0.4" - semver: "npm:^7.6.0" - ts-api-utils: "npm:^1.3.0" - peerDependenciesMeta: - typescript: - optional: true - checksum: 10c0/170702b024121cff9268f53de8054796b0ce025f9a78d6f2bc850a360e5f3f7032ba3ee9d4b7392726308273a5f3ade5ab31b1788b504b514bc15afc07302b37 - languageName: node - linkType: hard - -"@typescript-eslint/utils@npm:5.62.0, @typescript-eslint/utils@npm:^5.58.0": - version: 5.62.0 - resolution: "@typescript-eslint/utils@npm:5.62.0" - dependencies: - "@eslint-community/eslint-utils": "npm:^4.2.0" - "@types/json-schema": "npm:^7.0.9" - "@types/semver": "npm:^7.3.12" - "@typescript-eslint/scope-manager": "npm:5.62.0" - "@typescript-eslint/types": "npm:5.62.0" - "@typescript-eslint/typescript-estree": "npm:5.62.0" - eslint-scope: "npm:^5.1.1" - semver: "npm:^7.3.7" - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - checksum: 10c0/f09b7d9952e4a205eb1ced31d7684dd55cee40bf8c2d78e923aa8a255318d97279825733902742c09d8690f37a50243f4c4d383ab16bd7aefaf9c4b438f785e1 - languageName: node - linkType: hard - -"@typescript-eslint/utils@npm:8.4.0": - version: 8.4.0 - resolution: "@typescript-eslint/utils@npm:8.4.0" - dependencies: - "@eslint-community/eslint-utils": "npm:^4.4.0" - "@typescript-eslint/scope-manager": "npm:8.4.0" - "@typescript-eslint/types": "npm:8.4.0" - "@typescript-eslint/typescript-estree": "npm:8.4.0" - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - checksum: 10c0/8c9c36b3aa23f9bcc28cc4b10f0fa2996f1bc6cdd75135f08c2ef734baa30dbd2a8b92f344b90518e1fd07a486936734789fc7e90b780221a7707dad8e9c9364 - languageName: node - linkType: hard - -"@typescript-eslint/visitor-keys@npm:5.62.0": - version: 5.62.0 - resolution: "@typescript-eslint/visitor-keys@npm:5.62.0" - dependencies: - "@typescript-eslint/types": "npm:5.62.0" - eslint-visitor-keys: "npm:^3.3.0" - checksum: 10c0/7c3b8e4148e9b94d9b7162a596a1260d7a3efc4e65199693b8025c71c4652b8042501c0bc9f57654c1e2943c26da98c0f77884a746c6ae81389fcb0b513d995d - languageName: node - linkType: hard - -"@typescript-eslint/visitor-keys@npm:8.4.0": - version: 8.4.0 - resolution: "@typescript-eslint/visitor-keys@npm:8.4.0" - dependencies: - "@typescript-eslint/types": "npm:8.4.0" - eslint-visitor-keys: "npm:^3.4.3" - checksum: 10c0/339199b7fbb9ac83b530d03ab25f6bc5ceb688c9cd0ae460112cd14ee78ca7284a845aef5620cdf70170980123475ec875e85ebf595c60255ba3c0d6fe48c714 - languageName: node - linkType: hard - -"@vitejs/plugin-react@npm:^4.3.1": - version: 4.3.1 - resolution: "@vitejs/plugin-react@npm:4.3.1" - dependencies: - "@babel/core": "npm:^7.24.5" - "@babel/plugin-transform-react-jsx-self": "npm:^7.24.5" - "@babel/plugin-transform-react-jsx-source": "npm:^7.24.1" - "@types/babel__core": "npm:^7.20.5" - react-refresh: "npm:^0.14.2" - peerDependencies: - vite: ^4.2.0 || ^5.0.0 - checksum: 10c0/39a027feddfd6b3e307121d79631462ef1aae05714ba7a2f9a73d240d0f89c2bf281132568eb27b55d6ddaf08d86ad1bd8b0066090240e570de8c6320eb9a903 - languageName: node - linkType: hard - -"abbrev@npm:^2.0.0": - version: 2.0.0 - resolution: "abbrev@npm:2.0.0" - checksum: 10c0/f742a5a107473946f426c691c08daba61a1d15942616f300b5d32fd735be88fef5cba24201757b6c407fd564555fb48c751cfa33519b2605c8a7aadd22baf372 - languageName: node - linkType: hard - -"accepts@npm:^2.0.0": - version: 2.0.0 - resolution: "accepts@npm:2.0.0" - dependencies: - mime-types: "npm:^3.0.0" - negotiator: "npm:^1.0.0" - checksum: 10c0/98374742097e140891546076215f90c32644feacf652db48412329de4c2a529178a81aa500fbb13dd3e6cbf6e68d829037b123ac037fc9a08bcec4b87b358eef - languageName: node - linkType: hard - -"accepts@npm:~1.3.4": - version: 1.3.8 - resolution: "accepts@npm:1.3.8" - dependencies: - mime-types: "npm:~2.1.34" - negotiator: "npm:0.6.3" - checksum: 10c0/3a35c5f5586cfb9a21163ca47a5f77ac34fa8ceb5d17d2fa2c0d81f41cbd7f8c6fa52c77e2c039acc0f4d09e71abdc51144246900f6bef5e3c4b333f77d89362 - languageName: node - linkType: hard - -"acorn-jsx@npm:^5.3.2": - version: 5.3.2 - resolution: "acorn-jsx@npm:5.3.2" - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - checksum: 10c0/4c54868fbef3b8d58927d5e33f0a4de35f59012fe7b12cf9dfbb345fb8f46607709e1c4431be869a23fb63c151033d84c4198fa9f79385cec34fcb1dd53974c1 - languageName: node - linkType: hard - -"acorn@npm:^8.12.0": - version: 8.12.1 - resolution: "acorn@npm:8.12.1" - bin: - acorn: bin/acorn - checksum: 10c0/51fb26cd678f914e13287e886da2d7021f8c2bc0ccc95e03d3e0447ee278dd3b40b9c57dc222acd5881adcf26f3edc40901a4953403232129e3876793cd17386 - languageName: node - linkType: hard - -"agent-base@npm:^7.0.2, agent-base@npm:^7.1.0, agent-base@npm:^7.1.1": - version: 7.1.1 - resolution: "agent-base@npm:7.1.1" - dependencies: - debug: "npm:^4.3.4" - checksum: 10c0/e59ce7bed9c63bf071a30cc471f2933862044c97fd9958967bfe22521d7a0f601ce4ed5a8c011799d0c726ca70312142ae193bbebb60f576b52be19d4a363b50 - languageName: node - linkType: hard - -"aggregate-error@npm:^3.0.0": - version: 3.1.0 - resolution: "aggregate-error@npm:3.1.0" - dependencies: - clean-stack: "npm:^2.0.0" - indent-string: "npm:^4.0.0" - checksum: 10c0/a42f67faa79e3e6687a4923050e7c9807db3848a037076f791d10e092677d65c1d2d863b7848560699f40fc0502c19f40963fb1cd1fb3d338a7423df8e45e039 - languageName: node - linkType: hard - -"ajv@npm:^6.12.4": - version: 6.12.6 - resolution: "ajv@npm:6.12.6" - dependencies: - fast-deep-equal: "npm:^3.1.1" - fast-json-stable-stringify: "npm:^2.0.0" - json-schema-traverse: "npm:^0.4.1" - uri-js: "npm:^4.2.2" - checksum: 10c0/41e23642cbe545889245b9d2a45854ebba51cda6c778ebced9649420d9205f2efb39cb43dbc41e358409223b1ea43303ae4839db682c848b891e4811da1a5a71 - languageName: node - linkType: hard - -"ajv@npm:^8.12.0": - version: 8.12.0 - resolution: "ajv@npm:8.12.0" - dependencies: - fast-deep-equal: "npm:^3.1.1" - json-schema-traverse: "npm:^1.0.0" - require-from-string: "npm:^2.0.2" - uri-js: "npm:^4.2.2" - checksum: 10c0/ac4f72adf727ee425e049bc9d8b31d4a57e1c90da8d28bcd23d60781b12fcd6fc3d68db5df16994c57b78b94eed7988f5a6b482fd376dc5b084125e20a0a622e - languageName: node - linkType: hard - -"ansi-colors@npm:^4.1.3": - version: 4.1.3 - resolution: "ansi-colors@npm:4.1.3" - checksum: 10c0/ec87a2f59902f74e61eada7f6e6fe20094a628dab765cfdbd03c3477599368768cffccdb5d3bb19a1b6c99126783a143b1fee31aab729b31ffe5836c7e5e28b9 - languageName: node - linkType: hard - -"ansi-regex@npm:^5.0.1": - version: 5.0.1 - resolution: "ansi-regex@npm:5.0.1" - checksum: 10c0/9a64bb8627b434ba9327b60c027742e5d17ac69277960d041898596271d992d4d52ba7267a63ca10232e29f6107fc8a835f6ce8d719b88c5f8493f8254813737 - languageName: node - linkType: hard - -"ansi-regex@npm:^6.0.1": - version: 6.0.1 - resolution: "ansi-regex@npm:6.0.1" - checksum: 10c0/cbe16dbd2c6b2735d1df7976a7070dd277326434f0212f43abf6d87674095d247968209babdaad31bb00882fa68807256ba9be340eec2f1004de14ca75f52a08 - languageName: node - linkType: hard - -"ansi-styles@npm:^3.2.1": - version: 3.2.1 - resolution: "ansi-styles@npm:3.2.1" - dependencies: - color-convert: "npm:^1.9.0" - checksum: 10c0/ece5a8ef069fcc5298f67e3f4771a663129abd174ea2dfa87923a2be2abf6cd367ef72ac87942da00ce85bd1d651d4cd8595aebdb1b385889b89b205860e977b - languageName: node - linkType: hard - -"ansi-styles@npm:^4.0.0, ansi-styles@npm:^4.1.0": - version: 4.3.0 - resolution: "ansi-styles@npm:4.3.0" - dependencies: - color-convert: "npm:^2.0.1" - checksum: 10c0/895a23929da416f2bd3de7e9cb4eabd340949328ab85ddd6e484a637d8f6820d485f53933446f5291c3b760cbc488beb8e88573dd0f9c7daf83dccc8fe81b041 - languageName: node - linkType: hard - -"ansi-styles@npm:^6.1.0": - version: 6.2.1 - resolution: "ansi-styles@npm:6.2.1" - checksum: 10c0/5d1ec38c123984bcedd996eac680d548f31828bd679a66db2bdf11844634dde55fec3efa9c6bb1d89056a5e79c1ac540c4c784d592ea1d25028a92227d2f2d5c - languageName: node - linkType: hard - -"anymatch@npm:~3.1.2": - version: 3.1.3 - resolution: "anymatch@npm:3.1.3" - dependencies: - normalize-path: "npm:^3.0.0" - picomatch: "npm:^2.0.4" - checksum: 10c0/57b06ae984bc32a0d22592c87384cd88fe4511b1dd7581497831c56d41939c8a001b28e7b853e1450f2bf61992dfcaa8ae2d0d161a0a90c4fb631ef07098fbac - languageName: node - linkType: hard - -"argparse@npm:^2.0.1": - version: 2.0.1 - resolution: "argparse@npm:2.0.1" - checksum: 10c0/c5640c2d89045371c7cedd6a70212a04e360fd34d6edeae32f6952c63949e3525ea77dbec0289d8213a99bbaeab5abfa860b5c12cf88a2e6cf8106e90dd27a7e - languageName: node - linkType: hard - -"aria-query@npm:~5.1.3": - version: 5.1.3 - resolution: "aria-query@npm:5.1.3" - dependencies: - deep-equal: "npm:^2.0.5" - checksum: 10c0/edcbc8044c4663d6f88f785e983e6784f98cb62b4ba1e9dd8d61b725d0203e4cfca38d676aee984c31f354103461102a3d583aa4fbe4fd0a89b679744f4e5faf - languageName: node - linkType: hard - -"array-buffer-byte-length@npm:^1.0.0, array-buffer-byte-length@npm:^1.0.1": - version: 1.0.1 - resolution: "array-buffer-byte-length@npm:1.0.1" - dependencies: - call-bind: "npm:^1.0.5" - is-array-buffer: "npm:^3.0.4" - checksum: 10c0/f5cdf54527cd18a3d2852ddf73df79efec03829e7373a8322ef5df2b4ef546fb365c19c71d6b42d641cb6bfe0f1a2f19bc0ece5b533295f86d7c3d522f228917 - languageName: node - linkType: hard - -"array-flatten@npm:3.0.0": - version: 3.0.0 - resolution: "array-flatten@npm:3.0.0" - checksum: 10c0/202dae2bcee760f12e29958a0957bd166b3a9a8b41d0c942786d48857f903479c8a10fc7f55ed64904254dce7240f9fa50633504c925288fd051d9fb122cbb48 - languageName: node - linkType: hard - -"array-includes@npm:^3.1.6, array-includes@npm:^3.1.8": - version: 3.1.8 - resolution: "array-includes@npm:3.1.8" - dependencies: - call-bind: "npm:^1.0.7" - define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.2" - es-object-atoms: "npm:^1.0.0" - get-intrinsic: "npm:^1.2.4" - is-string: "npm:^1.0.7" - checksum: 10c0/5b1004d203e85873b96ddc493f090c9672fd6c80d7a60b798da8a14bff8a670ff95db5aafc9abc14a211943f05220dacf8ea17638ae0af1a6a47b8c0b48ce370 - languageName: node - linkType: hard - -"array-union@npm:^2.1.0": - version: 2.1.0 - resolution: "array-union@npm:2.1.0" - checksum: 10c0/429897e68110374f39b771ec47a7161fc6a8fc33e196857c0a396dc75df0b5f65e4d046674db764330b6bb66b39ef48dd7c53b6a2ee75cfb0681e0c1a7033962 - languageName: node - linkType: hard - -"array.prototype.findlast@npm:^1.2.5": - version: 1.2.5 - resolution: "array.prototype.findlast@npm:1.2.5" - dependencies: - call-bind: "npm:^1.0.7" - define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.2" - es-errors: "npm:^1.3.0" - es-object-atoms: "npm:^1.0.0" - es-shim-unscopables: "npm:^1.0.2" - checksum: 10c0/ddc952b829145ab45411b9d6adcb51a8c17c76bf89c9dd64b52d5dffa65d033da8c076ed2e17091779e83bc892b9848188d7b4b33453c5565e65a92863cb2775 - languageName: node - linkType: hard - -"array.prototype.findlastindex@npm:^1.2.5": - version: 1.2.5 - resolution: "array.prototype.findlastindex@npm:1.2.5" - dependencies: - call-bind: "npm:^1.0.7" - define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.2" - es-errors: "npm:^1.3.0" - es-object-atoms: "npm:^1.0.0" - es-shim-unscopables: "npm:^1.0.2" - checksum: 10c0/962189487728b034f3134802b421b5f39e42ee2356d13b42d2ddb0e52057ffdcc170b9524867f4f0611a6f638f4c19b31e14606e8bcbda67799e26685b195aa3 - languageName: node - linkType: hard - -"array.prototype.flat@npm:^1.3.1, array.prototype.flat@npm:^1.3.2": - version: 1.3.2 - resolution: "array.prototype.flat@npm:1.3.2" - dependencies: - call-bind: "npm:^1.0.2" - define-properties: "npm:^1.2.0" - es-abstract: "npm:^1.22.1" - es-shim-unscopables: "npm:^1.0.0" - checksum: 10c0/a578ed836a786efbb6c2db0899ae80781b476200617f65a44846cb1ed8bd8b24c8821b83703375d8af639c689497b7b07277060024b9919db94ac3e10dc8a49b - languageName: node - linkType: hard - -"array.prototype.flatmap@npm:^1.3.2": - version: 1.3.2 - resolution: "array.prototype.flatmap@npm:1.3.2" - dependencies: - call-bind: "npm:^1.0.2" - define-properties: "npm:^1.2.0" - es-abstract: "npm:^1.22.1" - es-shim-unscopables: "npm:^1.0.0" - checksum: 10c0/67b3f1d602bb73713265145853128b1ad77cc0f9b833c7e1e056b323fbeac41a4ff1c9c99c7b9445903caea924d9ca2450578d9011913191aa88cc3c3a4b54f4 - languageName: node - linkType: hard - -"array.prototype.tosorted@npm:^1.1.4": - version: 1.1.4 - resolution: "array.prototype.tosorted@npm:1.1.4" - dependencies: - call-bind: "npm:^1.0.7" - define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.3" - es-errors: "npm:^1.3.0" - es-shim-unscopables: "npm:^1.0.2" - checksum: 10c0/eb3c4c4fc0381b0bf6dba2ea4d48d367c2827a0d4236a5718d97caaccc6b78f11f4cadf090736e86301d295a6aa4967ed45568f92ced51be8cbbacd9ca410943 - languageName: node - linkType: hard - -"arraybuffer.prototype.slice@npm:^1.0.3": - version: 1.0.3 - resolution: "arraybuffer.prototype.slice@npm:1.0.3" - dependencies: - array-buffer-byte-length: "npm:^1.0.1" - call-bind: "npm:^1.0.5" - define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.22.3" - es-errors: "npm:^1.2.1" - get-intrinsic: "npm:^1.2.3" - is-array-buffer: "npm:^3.0.4" - is-shared-array-buffer: "npm:^1.0.2" - checksum: 10c0/d32754045bcb2294ade881d45140a5e52bda2321b9e98fa514797b7f0d252c4c5ab0d1edb34112652c62fa6a9398def568da63a4d7544672229afea283358c36 - languageName: node - linkType: hard - -"assertion-error@npm:^2.0.1": - version: 2.0.1 - resolution: "assertion-error@npm:2.0.1" - checksum: 10c0/bbbcb117ac6480138f8c93cf7f535614282dea9dc828f540cdece85e3c665e8f78958b96afac52f29ff883c72638e6a87d469ecc9fe5bc902df03ed24a55dba8 - languageName: node - linkType: hard - -"ast-types-flow@npm:^0.0.8": - version: 0.0.8 - resolution: "ast-types-flow@npm:0.0.8" - checksum: 10c0/f2a0ba8055353b743c41431974521e5e852a9824870cd6fce2db0e538ac7bf4da406bbd018d109af29ff3f8f0993f6a730c9eddbd0abd031fbcb29ca75c1014e - languageName: node - linkType: hard - -"attr-accept@npm:^2.2.2": - version: 2.2.2 - resolution: "attr-accept@npm:2.2.2" - checksum: 10c0/f77c073ac9616a783f2df814a56f65f1c870193e8da6097139e30b3be84ecc19fb835b93e81315d1da4f19e80721f14e8c8075014205e00abd37b856fe030b80 - languageName: node - linkType: hard - -"available-typed-arrays@npm:^1.0.7": - version: 1.0.7 - resolution: "available-typed-arrays@npm:1.0.7" - dependencies: - possible-typed-array-names: "npm:^1.0.0" - checksum: 10c0/d07226ef4f87daa01bd0fe80f8f310982e345f372926da2e5296aecc25c41cab440916bbaa4c5e1034b453af3392f67df5961124e4b586df1e99793a1374bdb2 - languageName: node - linkType: hard - -"axe-core@npm:^4.9.1": - version: 4.10.0 - resolution: "axe-core@npm:4.10.0" - checksum: 10c0/732c171d48caaace5e784895c4dacb8ca6155e9d98045138ebe3952f78457dd05b92c57d05b41ce2a570aff87dbd0471e8398d2c0f6ebe79617b746c8f658998 - languageName: node - linkType: hard - -"axobject-query@npm:~3.1.1": - version: 3.1.1 - resolution: "axobject-query@npm:3.1.1" - dependencies: - deep-equal: "npm:^2.0.5" - checksum: 10c0/fff3175a22fd1f41fceb7ae0cd25f6594a0d7fba28c2335dd904538b80eb4e1040432564a3c643025cd2bb748f68d35aaabffb780b794da97ecfc748810b25ad - languageName: node - linkType: hard - -"babel-plugin-macros@npm:^3.1.0": - version: 3.1.0 - resolution: "babel-plugin-macros@npm:3.1.0" - dependencies: - "@babel/runtime": "npm:^7.12.5" - cosmiconfig: "npm:^7.0.0" - resolve: "npm:^1.19.0" - checksum: 10c0/c6dfb15de96f67871d95bd2e8c58b0c81edc08b9b087dc16755e7157f357dc1090a8dc60ebab955e92587a9101f02eba07e730adc253a1e4cf593ca3ebd3839c - languageName: node - linkType: hard - -"babel-plugin-polyfill-corejs2@npm:^0.4.10": - version: 0.4.11 - resolution: "babel-plugin-polyfill-corejs2@npm:0.4.11" - dependencies: - "@babel/compat-data": "npm:^7.22.6" - "@babel/helper-define-polyfill-provider": "npm:^0.6.2" - semver: "npm:^6.3.1" - peerDependencies: - "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 10c0/b2217bc8d5976cf8142453ed44daabf0b2e0e75518f24eac83b54a8892e87a88f1bd9089daa92fd25df979ecd0acfd29b6bc28c4182c1c46344cee15ef9bce84 - languageName: node - linkType: hard - -"babel-plugin-polyfill-corejs3@npm:^0.10.6": - version: 0.10.6 - resolution: "babel-plugin-polyfill-corejs3@npm:0.10.6" - dependencies: - "@babel/helper-define-polyfill-provider": "npm:^0.6.2" - core-js-compat: "npm:^3.38.0" - peerDependencies: - "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 10c0/3a69220471b07722c2ae6537310bf26b772514e12b601398082965459c838be70a0ca70b0662f0737070654ff6207673391221d48599abb4a2b27765206d9f79 - languageName: node - linkType: hard - -"babel-plugin-polyfill-regenerator@npm:^0.6.1": - version: 0.6.2 - resolution: "babel-plugin-polyfill-regenerator@npm:0.6.2" - dependencies: - "@babel/helper-define-polyfill-provider": "npm:^0.6.2" - peerDependencies: - "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 10c0/bc541037cf7620bc84ddb75a1c0ce3288f90e7d2799c070a53f8a495c8c8ae0316447becb06f958dd25dcce2a2fce855d318ecfa48036a1ddb218d55aa38a744 - languageName: node - linkType: hard - -"babel-plugin-transform-react-remove-prop-types@npm:^0.4.24": - version: 0.4.24 - resolution: "babel-plugin-transform-react-remove-prop-types@npm:0.4.24" - checksum: 10c0/713441fd9fb663cc95709cb52d9c2c6228ea6d5406092a8a50094c810bcb13c3c347f8fca703d45b20cc401782743a91d7272025950147f9247d53360267f107 - languageName: node - linkType: hard - -"babel-preset-react-app@npm:^10.0.1": - version: 10.0.1 - resolution: "babel-preset-react-app@npm:10.0.1" - dependencies: - "@babel/core": "npm:^7.16.0" - "@babel/plugin-proposal-class-properties": "npm:^7.16.0" - "@babel/plugin-proposal-decorators": "npm:^7.16.4" - "@babel/plugin-proposal-nullish-coalescing-operator": "npm:^7.16.0" - "@babel/plugin-proposal-numeric-separator": "npm:^7.16.0" - "@babel/plugin-proposal-optional-chaining": "npm:^7.16.0" - "@babel/plugin-proposal-private-methods": "npm:^7.16.0" - "@babel/plugin-transform-flow-strip-types": "npm:^7.16.0" - "@babel/plugin-transform-react-display-name": "npm:^7.16.0" - "@babel/plugin-transform-runtime": "npm:^7.16.4" - "@babel/preset-env": "npm:^7.16.4" - "@babel/preset-react": "npm:^7.16.0" - "@babel/preset-typescript": "npm:^7.16.0" - "@babel/runtime": "npm:^7.16.3" - babel-plugin-macros: "npm:^3.1.0" - babel-plugin-transform-react-remove-prop-types: "npm:^0.4.24" - checksum: 10c0/aba225d0caf2591f7cc1aeed69369d1e86419a92fcf6025312421c238f4468347a7b6c12a45a4fb15e879a01f5a060439842fbde5739d7be31077b35c7d20fa5 - languageName: node - linkType: hard - -"balanced-match@npm:^1.0.0": - version: 1.0.2 - resolution: "balanced-match@npm:1.0.2" - checksum: 10c0/9308baf0a7e4838a82bbfd11e01b1cb0f0cf2893bc1676c27c2a8c0e70cbae1c59120c3268517a8ae7fb6376b4639ef81ca22582611dbee4ed28df945134aaee - languageName: node - linkType: hard - -"base64id@npm:2.0.0, base64id@npm:~2.0.0": - version: 2.0.0 - resolution: "base64id@npm:2.0.0" - checksum: 10c0/6919efd237ed44b9988cbfc33eca6f173a10e810ce50292b271a1a421aac7748ef232a64d1e6032b08f19aae48dce6ee8f66c5ae2c9e5066c82b884861d4d453 - languageName: node - linkType: hard - -"binary-extensions@npm:^2.0.0": - version: 2.3.0 - resolution: "binary-extensions@npm:2.3.0" - checksum: 10c0/75a59cafc10fb12a11d510e77110c6c7ae3f4ca22463d52487709ca7f18f69d886aa387557cc9864fbdb10153d0bdb4caacabf11541f55e89ed6e18d12ece2b5 - languageName: node - linkType: hard - -"body-parser@npm:^2.0.1": - version: 2.0.1 - resolution: "body-parser@npm:2.0.1" - dependencies: - bytes: "npm:3.1.2" - content-type: "npm:~1.0.5" - debug: "npm:3.1.0" - destroy: "npm:1.2.0" - http-errors: "npm:2.0.0" - iconv-lite: "npm:0.5.2" - on-finished: "npm:2.4.1" - qs: "npm:6.13.0" - raw-body: "npm:^3.0.0" - type-is: "npm:~1.6.18" - unpipe: "npm:1.0.0" - checksum: 10c0/c38a3bac4c0a124edda02a55d104573c4a6787287041bd8656e7c18a5cbc959e88502d39cd52f26a5218e065ff39b65c0e46c109de160e5054e2d5fed25a9659 - languageName: node - linkType: hard - -"brace-expansion@npm:^1.1.7": - version: 1.1.11 - resolution: "brace-expansion@npm:1.1.11" - dependencies: - balanced-match: "npm:^1.0.0" - concat-map: "npm:0.0.1" - checksum: 10c0/695a56cd058096a7cb71fb09d9d6a7070113c7be516699ed361317aca2ec169f618e28b8af352e02ab4233fb54eb0168460a40dc320bab0034b36ab59aaad668 - languageName: node - linkType: hard - -"brace-expansion@npm:^2.0.1": - version: 2.0.1 - resolution: "brace-expansion@npm:2.0.1" - dependencies: - balanced-match: "npm:^1.0.0" - checksum: 10c0/b358f2fe060e2d7a87aa015979ecea07f3c37d4018f8d6deb5bd4c229ad3a0384fe6029bb76cd8be63c81e516ee52d1a0673edbe2023d53a5191732ae3c3e49f - languageName: node - linkType: hard - -"braces@npm:^3.0.3, braces@npm:~3.0.2": - version: 3.0.3 - resolution: "braces@npm:3.0.3" - dependencies: - fill-range: "npm:^7.1.1" - checksum: 10c0/7c6dfd30c338d2997ba77500539227b9d1f85e388a5f43220865201e407e076783d0881f2d297b9f80951b4c957fcf0b51c1d2d24227631643c3f7c284b0aa04 - languageName: node - linkType: hard - -"browser-stdout@npm:^1.3.1": - version: 1.3.1 - resolution: "browser-stdout@npm:1.3.1" - checksum: 10c0/c40e482fd82be872b6ea7b9f7591beafbf6f5ba522fe3dade98ba1573a1c29a11101564993e4eb44e5488be8f44510af072df9a9637c739217eb155ceb639205 - languageName: node - linkType: hard - -"browserslist@npm:^4.21.3": - version: 4.21.5 - resolution: "browserslist@npm:4.21.5" - dependencies: - caniuse-lite: "npm:^1.0.30001449" - electron-to-chromium: "npm:^1.4.284" - node-releases: "npm:^2.0.8" - update-browserslist-db: "npm:^1.0.10" - bin: - browserslist: cli.js - checksum: 10c0/903040d2c45b733e1177c288b4f146ff21d45e8a44ccc87d1d7fc2f6a8d021c7ee54b514fd7722529c282381969382a54bd2ab4263f5b6c8981a856b457ea162 - languageName: node - linkType: hard - -"browserslist@npm:^4.23.1, browserslist@npm:^4.23.3": - version: 4.23.3 - resolution: "browserslist@npm:4.23.3" - dependencies: - caniuse-lite: "npm:^1.0.30001646" - electron-to-chromium: "npm:^1.5.4" - node-releases: "npm:^2.0.18" - update-browserslist-db: "npm:^1.1.0" - bin: - browserslist: cli.js - checksum: 10c0/3063bfdf812815346447f4796c8f04601bf5d62003374305fd323c2a463e42776475bcc5309264e39bcf9a8605851e53560695991a623be988138b3ff8c66642 - languageName: node - linkType: hard - -"bytes@npm:3.1.2": - version: 3.1.2 - resolution: "bytes@npm:3.1.2" - checksum: 10c0/76d1c43cbd602794ad8ad2ae94095cddeb1de78c5dddaa7005c51af10b0176c69971a6d88e805a90c2b6550d76636e43c40d8427a808b8645ede885de4a0358e - languageName: node - linkType: hard - -"cacache@npm:^18.0.0": - version: 18.0.4 - resolution: "cacache@npm:18.0.4" - dependencies: - "@npmcli/fs": "npm:^3.1.0" - fs-minipass: "npm:^3.0.0" - glob: "npm:^10.2.2" - lru-cache: "npm:^10.0.1" - minipass: "npm:^7.0.3" - minipass-collect: "npm:^2.0.1" - minipass-flush: "npm:^1.0.5" - minipass-pipeline: "npm:^1.2.4" - p-map: "npm:^4.0.0" - ssri: "npm:^10.0.0" - tar: "npm:^6.1.11" - unique-filename: "npm:^3.0.0" - checksum: 10c0/6c055bafed9de4f3dcc64ac3dc7dd24e863210902b7c470eb9ce55a806309b3efff78033e3d8b4f7dcc5d467f2db43c6a2857aaaf26f0094b8a351d44c42179f - languageName: node - linkType: hard - -"call-bind@npm:^1.0.0": - version: 1.0.2 - resolution: "call-bind@npm:1.0.2" - dependencies: - function-bind: "npm:^1.1.1" - get-intrinsic: "npm:^1.0.2" - checksum: 10c0/74ba3f31e715456e22e451d8d098779b861eba3c7cac0d9b510049aced70d75c231ba05071f97e1812c98e34e2bee734c0c6126653e0088c2d9819ca047f4073 - languageName: node - linkType: hard - -"call-bind@npm:^1.0.2, call-bind@npm:^1.0.5, call-bind@npm:^1.0.6, call-bind@npm:^1.0.7": - version: 1.0.7 - resolution: "call-bind@npm:1.0.7" - dependencies: - es-define-property: "npm:^1.0.0" - es-errors: "npm:^1.3.0" - function-bind: "npm:^1.1.2" - get-intrinsic: "npm:^1.2.4" - set-function-length: "npm:^1.2.1" - checksum: 10c0/a3ded2e423b8e2a265983dba81c27e125b48eefb2655e7dfab6be597088da3d47c47976c24bc51b8fd9af1061f8f87b4ab78a314f3c77784b2ae2ba535ad8b8d - languageName: node - linkType: hard - -"callsites@npm:^3.0.0": - version: 3.1.0 - resolution: "callsites@npm:3.1.0" - checksum: 10c0/fff92277400eb06c3079f9e74f3af120db9f8ea03bad0e84d9aede54bbe2d44a56cccb5f6cf12211f93f52306df87077ecec5b712794c5a9b5dac6d615a3f301 - languageName: node - linkType: hard - -"camelcase@npm:^6.0.0": - version: 6.3.0 - resolution: "camelcase@npm:6.3.0" - checksum: 10c0/0d701658219bd3116d12da3eab31acddb3f9440790c0792e0d398f0a520a6a4058018e546862b6fba89d7ae990efaeb97da71e1913e9ebf5a8b5621a3d55c710 - languageName: node - linkType: hard - -"caniuse-lite@npm:^1.0.30001449": - version: 1.0.30001562 - resolution: "caniuse-lite@npm:1.0.30001562" - checksum: 10c0/4d2d4174317b582699ed66e12034a21222c931b9fefc42d53a283666d1323c27bb2d135b9f5efd89c242222b591111a21b2c90166457019389191d54566eef68 - languageName: node - linkType: hard - -"caniuse-lite@npm:^1.0.30001646": - version: 1.0.30001655 - resolution: "caniuse-lite@npm:1.0.30001655" - checksum: 10c0/fff0c0c3ffcba89828bfa6b99f118e82c064f46f15bb8655b9f2a352a3f552ccac0b87a9fe9532f8c5a29e284aae5579791e196480ec717d11ef1d1a1c2e3ff9 - languageName: node - linkType: hard - -"chai@npm:^5.1.1": - version: 5.1.1 - resolution: "chai@npm:5.1.1" - dependencies: - assertion-error: "npm:^2.0.1" - check-error: "npm:^2.1.1" - deep-eql: "npm:^5.0.1" - loupe: "npm:^3.1.0" - pathval: "npm:^2.0.0" - checksum: 10c0/e7f00e5881e3d5224f08fe63966ed6566bd9fdde175863c7c16dd5240416de9b34c4a0dd925f4fd64ad56256ca6507d32cf6131c49e1db65c62578eb31d4566c - languageName: node - linkType: hard - -"chalk@npm:^2.0.0, chalk@npm:^2.4.2": - version: 2.4.2 - resolution: "chalk@npm:2.4.2" - dependencies: - ansi-styles: "npm:^3.2.1" - escape-string-regexp: "npm:^1.0.5" - supports-color: "npm:^5.3.0" - checksum: 10c0/e6543f02ec877732e3a2d1c3c3323ddb4d39fbab687c23f526e25bd4c6a9bf3b83a696e8c769d078e04e5754921648f7821b2a2acfd16c550435fd630026e073 - languageName: node - linkType: hard - -"chalk@npm:^4.0.0, chalk@npm:^4.1.0": - version: 4.1.2 - resolution: "chalk@npm:4.1.2" - dependencies: - ansi-styles: "npm:^4.1.0" - supports-color: "npm:^7.1.0" - checksum: 10c0/4a3fef5cc34975c898ffe77141450f679721df9dde00f6c304353fa9c8b571929123b26a0e4617bde5018977eb655b31970c297b91b63ee83bb82aeb04666880 - languageName: node - linkType: hard - -"check-error@npm:^2.1.1": - version: 2.1.1 - resolution: "check-error@npm:2.1.1" - checksum: 10c0/979f13eccab306cf1785fa10941a590b4e7ea9916ea2a4f8c87f0316fc3eab07eabefb6e587424ef0f88cbcd3805791f172ea739863ca3d7ce2afc54641c7f0e - languageName: node - linkType: hard - -"chokidar@npm:^3.5.3": - version: 3.6.0 - resolution: "chokidar@npm:3.6.0" - dependencies: - anymatch: "npm:~3.1.2" - braces: "npm:~3.0.2" - fsevents: "npm:~2.3.2" - glob-parent: "npm:~5.1.2" - is-binary-path: "npm:~2.1.0" - is-glob: "npm:~4.0.1" - normalize-path: "npm:~3.0.0" - readdirp: "npm:~3.6.0" - dependenciesMeta: - fsevents: - optional: true - checksum: 10c0/8361dcd013f2ddbe260eacb1f3cb2f2c6f2b0ad118708a343a5ed8158941a39cb8fb1d272e0f389712e74ee90ce8ba864eece9e0e62b9705cb468a2f6d917462 - languageName: node - linkType: hard - -"chownr@npm:^2.0.0": - version: 2.0.0 - resolution: "chownr@npm:2.0.0" - checksum: 10c0/594754e1303672171cc04e50f6c398ae16128eb134a88f801bf5354fd96f205320f23536a045d9abd8b51024a149696e51231565891d4efdab8846021ecf88e6 - languageName: node - linkType: hard - -"clean-stack@npm:^2.0.0": - version: 2.2.0 - resolution: "clean-stack@npm:2.2.0" - checksum: 10c0/1f90262d5f6230a17e27d0c190b09d47ebe7efdd76a03b5a1127863f7b3c9aec4c3e6c8bb3a7bbf81d553d56a1fd35728f5a8ef4c63f867ac8d690109742a8c1 - languageName: node - linkType: hard - -"cliui@npm:^7.0.2": - version: 7.0.4 - resolution: "cliui@npm:7.0.4" - dependencies: - string-width: "npm:^4.2.0" - strip-ansi: "npm:^6.0.0" - wrap-ansi: "npm:^7.0.0" - checksum: 10c0/6035f5daf7383470cef82b3d3db00bec70afb3423538c50394386ffbbab135e26c3689c41791f911fa71b62d13d3863c712fdd70f0fbdffd938a1e6fd09aac00 - languageName: node - linkType: hard - -"clsx@npm:^1.2.1": - version: 1.2.1 - resolution: "clsx@npm:1.2.1" - checksum: 10c0/34dead8bee24f5e96f6e7937d711978380647e936a22e76380290e35486afd8634966ce300fc4b74a32f3762c7d4c0303f442c3e259f4ce02374eb0c82834f27 - languageName: node - linkType: hard - -"color-convert@npm:^1.9.0": - version: 1.9.3 - resolution: "color-convert@npm:1.9.3" - dependencies: - color-name: "npm:1.1.3" - checksum: 10c0/5ad3c534949a8c68fca8fbc6f09068f435f0ad290ab8b2f76841b9e6af7e0bb57b98cb05b0e19fe33f5d91e5a8611ad457e5f69e0a484caad1f7487fd0e8253c - languageName: node - linkType: hard - -"color-convert@npm:^2.0.1": - version: 2.0.1 - resolution: "color-convert@npm:2.0.1" - dependencies: - color-name: "npm:~1.1.4" - checksum: 10c0/37e1150172f2e311fe1b2df62c6293a342ee7380da7b9cfdba67ea539909afbd74da27033208d01d6d5cfc65ee7868a22e18d7e7648e004425441c0f8a15a7d7 - languageName: node - linkType: hard - -"color-name@npm:1.1.3": - version: 1.1.3 - resolution: "color-name@npm:1.1.3" - checksum: 10c0/566a3d42cca25b9b3cd5528cd7754b8e89c0eb646b7f214e8e2eaddb69994ac5f0557d9c175eb5d8f0ad73531140d9c47525085ee752a91a2ab15ab459caf6d6 - languageName: node - linkType: hard - -"color-name@npm:~1.1.4": - version: 1.1.4 - resolution: "color-name@npm:1.1.4" - checksum: 10c0/a1a3f914156960902f46f7f56bc62effc6c94e84b2cae157a526b1c1f74b677a47ec602bf68a61abfa2b42d15b7c5651c6dbe72a43af720bc588dff885b10f95 - languageName: node - linkType: hard - -"concat-map@npm:0.0.1": - version: 0.0.1 - resolution: "concat-map@npm:0.0.1" - checksum: 10c0/c996b1cfdf95b6c90fee4dae37e332c8b6eb7d106430c17d538034c0ad9a1630cb194d2ab37293b1bdd4d779494beee7786d586a50bd9376fd6f7bcc2bd4c98f - languageName: node - linkType: hard - -"confusing-browser-globals@npm:^1.0.11": - version: 1.0.11 - resolution: "confusing-browser-globals@npm:1.0.11" - checksum: 10c0/475d0a284fa964a5182b519af5738b5b64bf7e413cfd703c1b3496bf6f4df9f827893a9b221c0ea5873c1476835beb1e0df569ba643eff0734010c1eb780589e - languageName: node - linkType: hard - -"content-disposition@npm:^1.0.0": - version: 1.0.0 - resolution: "content-disposition@npm:1.0.0" - dependencies: - safe-buffer: "npm:5.2.1" - checksum: 10c0/c7b1ba0cea2829da0352ebc1b7f14787c73884bc707c8bc2271d9e3bf447b372270d09f5d3980dc5037c749ceef56b9a13fccd0b0001c87c3f12579967e4dd27 - languageName: node - linkType: hard - -"content-type@npm:^1.0.5, content-type@npm:~1.0.4, content-type@npm:~1.0.5": - version: 1.0.5 - resolution: "content-type@npm:1.0.5" - checksum: 10c0/b76ebed15c000aee4678c3707e0860cb6abd4e680a598c0a26e17f0bfae723ec9cc2802f0ff1bc6e4d80603719010431d2231018373d4dde10f9ccff9dadf5af - languageName: node - linkType: hard - -"convert-source-map@npm:^1.5.0, convert-source-map@npm:^1.7.0": - version: 1.9.0 - resolution: "convert-source-map@npm:1.9.0" - checksum: 10c0/281da55454bf8126cbc6625385928c43479f2060984180c42f3a86c8b8c12720a24eac260624a7d1e090004028d2dee78602330578ceec1a08e27cb8bb0a8a5b - languageName: node - linkType: hard - -"convert-source-map@npm:^2.0.0": - version: 2.0.0 - resolution: "convert-source-map@npm:2.0.0" - checksum: 10c0/8f2f7a27a1a011cc6cc88cc4da2d7d0cfa5ee0369508baae3d98c260bb3ac520691464e5bbe4ae7cdf09860c1d69ecc6f70c63c6e7c7f7e3f18ec08484dc7d9b - languageName: node - linkType: hard - -"cookie-signature@npm:^1.2.1": - version: 1.2.1 - resolution: "cookie-signature@npm:1.2.1" - checksum: 10c0/1f71acf64931d7e7684aa228a0dad70162f6993b65b2957e076833cbd6f9a2f507b8d731b15e3895dce0e7ba4c63551f4686d1a3120199fe28060c41fd493a73 - languageName: node - linkType: hard - -"cookie@npm:0.7.1": - version: 0.7.1 - resolution: "cookie@npm:0.7.1" - checksum: 10c0/5de60c67a410e7c8dc8a46a4b72eb0fe925871d057c9a5d2c0e8145c4270a4f81076de83410c4d397179744b478e33cd80ccbcc457abf40a9409ad27dcd21dde - languageName: node - linkType: hard - -"cookie@npm:~0.4.1": - version: 0.4.2 - resolution: "cookie@npm:0.4.2" - checksum: 10c0/beab41fbd7c20175e3a2799ba948c1dcc71ef69f23fe14eeeff59fc09f50c517b0f77098db87dbb4c55da802f9d86ee86cdc1cd3efd87760341551838d53fca2 - languageName: node - linkType: hard - -"core-js-compat@npm:^3.37.1, core-js-compat@npm:^3.38.0": - version: 3.38.1 - resolution: "core-js-compat@npm:3.38.1" - dependencies: - browserslist: "npm:^4.23.3" - checksum: 10c0/d8bc8a35591fc5fbf3e376d793f298ec41eb452619c7ef9de4ea59b74be06e9fda799e0dcbf9ba59880dae87e3b41fb191d744ffc988315642a1272bb9442b31 - languageName: node - linkType: hard - -"cors@npm:^2.8.5, cors@npm:~2.8.5": - version: 2.8.5 - resolution: "cors@npm:2.8.5" - dependencies: - object-assign: "npm:^4" - vary: "npm:^1" - checksum: 10c0/373702b7999409922da80de4a61938aabba6929aea5b6fd9096fefb9e8342f626c0ebd7507b0e8b0b311380744cc985f27edebc0a26e0ddb784b54e1085de761 - languageName: node - linkType: hard - -"cosmiconfig@npm:^7.0.0": - version: 7.1.0 - resolution: "cosmiconfig@npm:7.1.0" - dependencies: - "@types/parse-json": "npm:^4.0.0" - import-fresh: "npm:^3.2.1" - parse-json: "npm:^5.0.0" - path-type: "npm:^4.0.0" - yaml: "npm:^1.10.0" - checksum: 10c0/b923ff6af581638128e5f074a5450ba12c0300b71302398ea38dbeabd33bbcaa0245ca9adbedfcf284a07da50f99ede5658c80bb3e39e2ce770a99d28a21ef03 - languageName: node - linkType: hard - -"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2": - version: 7.0.3 - resolution: "cross-spawn@npm:7.0.3" - dependencies: - path-key: "npm:^3.1.0" - shebang-command: "npm:^2.0.0" - which: "npm:^2.0.1" - checksum: 10c0/5738c312387081c98d69c98e105b6327b069197f864a60593245d64c8089c8a0a744e16349281210d56835bb9274130d825a78b2ad6853ca13cfbeffc0c31750 - languageName: node - linkType: hard - -"csstype@npm:^3.0.2, csstype@npm:^3.1.1": - version: 3.1.1 - resolution: "csstype@npm:3.1.1" - checksum: 10c0/7c8b8c5923049d84132581c13bae6e1faf999746fe3998ba5f3819a8e1cdc7512ace87b7d0a4a69f0f4b8ba11daf835d4f1390af23e09fc4f0baad52c084753a - languageName: node - linkType: hard - -"damerau-levenshtein@npm:^1.0.8": - version: 1.0.8 - resolution: "damerau-levenshtein@npm:1.0.8" - checksum: 10c0/4c2647e0f42acaee7d068756c1d396e296c3556f9c8314bac1ac63ffb236217ef0e7e58602b18bb2173deec7ec8e0cac8e27cccf8f5526666b4ff11a13ad54a3 - languageName: node - linkType: hard - -"data-view-buffer@npm:^1.0.1": - version: 1.0.1 - resolution: "data-view-buffer@npm:1.0.1" - dependencies: - call-bind: "npm:^1.0.6" - es-errors: "npm:^1.3.0" - is-data-view: "npm:^1.0.1" - checksum: 10c0/8984119e59dbed906a11fcfb417d7d861936f16697a0e7216fe2c6c810f6b5e8f4a5281e73f2c28e8e9259027190ac4a33e2a65fdd7fa86ac06b76e838918583 - languageName: node - linkType: hard - -"data-view-byte-length@npm:^1.0.1": - version: 1.0.1 - resolution: "data-view-byte-length@npm:1.0.1" - dependencies: - call-bind: "npm:^1.0.7" - es-errors: "npm:^1.3.0" - is-data-view: "npm:^1.0.1" - checksum: 10c0/b7d9e48a0cf5aefed9ab7d123559917b2d7e0d65531f43b2fd95b9d3a6b46042dd3fca597c42bba384e66b70d7ad66ff23932f8367b241f53d93af42cfe04ec2 - languageName: node - linkType: hard - -"data-view-byte-offset@npm:^1.0.0": - version: 1.0.0 - resolution: "data-view-byte-offset@npm:1.0.0" - dependencies: - call-bind: "npm:^1.0.6" - es-errors: "npm:^1.3.0" - is-data-view: "npm:^1.0.1" - checksum: 10c0/21b0d2e53fd6e20cc4257c873bf6d36d77bd6185624b84076c0a1ddaa757b49aaf076254006341d35568e89f52eecd1ccb1a502cfb620f2beca04f48a6a62a8f - languageName: node - linkType: hard - -"debug@npm:2.6.9": - version: 2.6.9 - resolution: "debug@npm:2.6.9" - dependencies: - ms: "npm:2.0.0" - checksum: 10c0/121908fb839f7801180b69a7e218a40b5a0b718813b886b7d6bdb82001b931c938e2941d1e4450f33a1b1df1da653f5f7a0440c197f29fbf8a6e9d45ff6ef589 - languageName: node - linkType: hard - -"debug@npm:3.1.0": - version: 3.1.0 - resolution: "debug@npm:3.1.0" - dependencies: - ms: "npm:2.0.0" - checksum: 10c0/5bff34a352d7b2eaa31886eeaf2ee534b5461ec0548315b2f9f80bd1d2533cab7df1fa52e130ce27bc31c3945fbffb0fc72baacdceb274b95ce853db89254ea4 - languageName: node - linkType: hard - -"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:~4.3.1, debug@npm:~4.3.2": - version: 4.3.4 - resolution: "debug@npm:4.3.4" - dependencies: - ms: "npm:2.1.2" - peerDependenciesMeta: - supports-color: - optional: true - checksum: 10c0/cedbec45298dd5c501d01b92b119cd3faebe5438c3917ff11ae1bff86a6c722930ac9c8659792824013168ba6db7c4668225d845c633fbdafbbf902a6389f736 - languageName: node - linkType: hard - -"debug@npm:4.3.6, debug@npm:^4.3.5": - version: 4.3.6 - resolution: "debug@npm:4.3.6" - dependencies: - ms: "npm:2.1.2" - peerDependenciesMeta: - supports-color: - optional: true - checksum: 10c0/3293416bff072389c101697d4611c402a6bacd1900ac20c0492f61a9cdd6b3b29750fc7f5e299f8058469ef60ff8fb79b86395a30374fbd2490113c1c7112285 - languageName: node - linkType: hard - -"debug@npm:^3.2.7": - version: 3.2.7 - resolution: "debug@npm:3.2.7" - dependencies: - ms: "npm:^2.1.1" - checksum: 10c0/37d96ae42cbc71c14844d2ae3ba55adf462ec89fd3a999459dec3833944cd999af6007ff29c780f1c61153bcaaf2c842d1e4ce1ec621e4fc4923244942e4a02a - languageName: node - linkType: hard - -"decamelize@npm:^4.0.0": - version: 4.0.0 - resolution: "decamelize@npm:4.0.0" - checksum: 10c0/e06da03fc05333e8cd2778c1487da67ffbea5b84e03ca80449519b8fa61f888714bbc6f459ea963d5641b4aa98832130eb5cd193d90ae9f0a27eee14be8e278d - languageName: node - linkType: hard - -"deep-eql@npm:^5.0.1": - version: 5.0.2 - resolution: "deep-eql@npm:5.0.2" - checksum: 10c0/7102cf3b7bb719c6b9c0db2e19bf0aa9318d141581befe8c7ce8ccd39af9eaa4346e5e05adef7f9bd7015da0f13a3a25dcfe306ef79dc8668aedbecb658dd247 - languageName: node - linkType: hard - -"deep-equal@npm:^2.0.5": - version: 2.2.3 - resolution: "deep-equal@npm:2.2.3" - dependencies: - array-buffer-byte-length: "npm:^1.0.0" - call-bind: "npm:^1.0.5" - es-get-iterator: "npm:^1.1.3" - get-intrinsic: "npm:^1.2.2" - is-arguments: "npm:^1.1.1" - is-array-buffer: "npm:^3.0.2" - is-date-object: "npm:^1.0.5" - is-regex: "npm:^1.1.4" - is-shared-array-buffer: "npm:^1.0.2" - isarray: "npm:^2.0.5" - object-is: "npm:^1.1.5" - object-keys: "npm:^1.1.1" - object.assign: "npm:^4.1.4" - regexp.prototype.flags: "npm:^1.5.1" - side-channel: "npm:^1.0.4" - which-boxed-primitive: "npm:^1.0.2" - which-collection: "npm:^1.0.1" - which-typed-array: "npm:^1.1.13" - checksum: 10c0/a48244f90fa989f63ff5ef0cc6de1e4916b48ea0220a9c89a378561960814794a5800c600254482a2c8fd2e49d6c2e196131dc983976adb024c94a42dfe4949f - languageName: node - linkType: hard - -"deep-is@npm:^0.1.3": - version: 0.1.4 - resolution: "deep-is@npm:0.1.4" - checksum: 10c0/7f0ee496e0dff14a573dc6127f14c95061b448b87b995fc96c017ce0a1e66af1675e73f1d6064407975bc4ea6ab679497a29fff7b5b9c4e99cb10797c1ad0b4c - languageName: node - linkType: hard - -"define-data-property@npm:^1.0.1, define-data-property@npm:^1.1.4": - version: 1.1.4 - resolution: "define-data-property@npm:1.1.4" - dependencies: - es-define-property: "npm:^1.0.0" - es-errors: "npm:^1.3.0" - gopd: "npm:^1.0.1" - checksum: 10c0/dea0606d1483eb9db8d930d4eac62ca0fa16738b0b3e07046cddfacf7d8c868bbe13fa0cb263eb91c7d0d527960dc3f2f2471a69ed7816210307f6744fe62e37 - languageName: node - linkType: hard - -"define-properties@npm:^1.1.3, define-properties@npm:^1.2.0, define-properties@npm:^1.2.1": - version: 1.2.1 - resolution: "define-properties@npm:1.2.1" - dependencies: - define-data-property: "npm:^1.0.1" - has-property-descriptors: "npm:^1.0.0" - object-keys: "npm:^1.1.1" - checksum: 10c0/88a152319ffe1396ccc6ded510a3896e77efac7a1bfbaa174a7b00414a1747377e0bb525d303794a47cf30e805c2ec84e575758512c6e44a993076d29fd4e6c3 - languageName: node - linkType: hard - -"depd@npm:2.0.0": - version: 2.0.0 - resolution: "depd@npm:2.0.0" - checksum: 10c0/58bd06ec20e19529b06f7ad07ddab60e504d9e0faca4bd23079fac2d279c3594334d736508dc350e06e510aba5e22e4594483b3a6562ce7c17dd797f4cc4ad2c - languageName: node - linkType: hard - -"destroy@npm:1.2.0, destroy@npm:^1.2.0": - version: 1.2.0 - resolution: "destroy@npm:1.2.0" - checksum: 10c0/bd7633942f57418f5a3b80d5cb53898127bcf53e24cdf5d5f4396be471417671f0fee48a4ebe9a1e9defbde2a31280011af58a57e090ff822f589b443ed4e643 - languageName: node - linkType: hard - -"diff@npm:^5.0.0": - version: 5.1.0 - resolution: "diff@npm:5.1.0" - checksum: 10c0/77a0d9beb9ed54796154ac2511872288432124ac90a1cabb1878783c9b4d81f1847f3b746a0630b1e836181461d2c76e1e6b95559bef86ed16294d114862e364 - languageName: node - linkType: hard - -"diff@npm:^5.2.0": - version: 5.2.0 - resolution: "diff@npm:5.2.0" - checksum: 10c0/aed0941f206fe261ecb258dc8d0ceea8abbde3ace5827518ff8d302f0fc9cc81ce116c4d8f379151171336caf0516b79e01abdc1ed1201b6440d895a66689eb4 - languageName: node - linkType: hard - -"dir-glob@npm:^3.0.1": - version: 3.0.1 - resolution: "dir-glob@npm:3.0.1" - dependencies: - path-type: "npm:^4.0.0" - checksum: 10c0/dcac00920a4d503e38bb64001acb19df4efc14536ada475725e12f52c16777afdee4db827f55f13a908ee7efc0cb282e2e3dbaeeb98c0993dd93d1802d3bf00c - languageName: node - linkType: hard - -"doctrine@npm:^2.1.0": - version: 2.1.0 - resolution: "doctrine@npm:2.1.0" - dependencies: - esutils: "npm:^2.0.2" - checksum: 10c0/b6416aaff1f380bf56c3b552f31fdf7a69b45689368deca72d28636f41c16bb28ec3ebc40ace97db4c1afc0ceeb8120e8492fe0046841c94c2933b2e30a7d5ac - languageName: node - linkType: hard - -"dom-helpers@npm:^5.0.1": - version: 5.2.1 - resolution: "dom-helpers@npm:5.2.1" - dependencies: - "@babel/runtime": "npm:^7.8.7" - csstype: "npm:^3.0.2" - checksum: 10c0/f735074d66dd759b36b158fa26e9d00c9388ee0e8c9b16af941c38f014a37fc80782de83afefd621681b19ac0501034b4f1c4a3bff5caa1b8667f0212b5e124c - languageName: node - linkType: hard - -"dotenv@npm:^16.0.3": - version: 16.0.3 - resolution: "dotenv@npm:16.0.3" - checksum: 10c0/109457ac5f9e930ca8066ea33887b6f839ab24d647a7a8b49ddcd1f32662e2c35591c5e5b9819063e430148a664d0927f0cbe60cf9575d89bc524f47ff7e78f0 - languageName: node - linkType: hard - -"eastasianwidth@npm:^0.2.0": - version: 0.2.0 - resolution: "eastasianwidth@npm:0.2.0" - checksum: 10c0/26f364ebcdb6395f95124fda411f63137a4bfb5d3a06453f7f23dfe52502905bd84e0488172e0f9ec295fdc45f05c23d5d91baf16bd26f0fe9acd777a188dc39 - languageName: node - linkType: hard - -"ee-first@npm:1.1.1": - version: 1.1.1 - resolution: "ee-first@npm:1.1.1" - checksum: 10c0/b5bb125ee93161bc16bfe6e56c6b04de5ad2aa44234d8f644813cc95d861a6910903132b05093706de2b706599367c4130eb6d170f6b46895686b95f87d017b7 - languageName: node - linkType: hard - -"electron-to-chromium@npm:^1.4.284": - version: 1.4.305 - resolution: "electron-to-chromium@npm:1.4.305" - checksum: 10c0/b4b425c094d1ad57ccdd4afbb386100d53d4c46553b4fd9489bbf6e1866df3860d8e433ff7a41bfa48c1f697c3eed6c6fb267c94c362a421da14385d080e088a - languageName: node - linkType: hard - -"electron-to-chromium@npm:^1.5.4": - version: 1.5.13 - resolution: "electron-to-chromium@npm:1.5.13" - checksum: 10c0/1d88ac39447e1d718c4296f92fe89836df4688daf2d362d6c49108136795f05a56dd9c950f1c6715e0395fa037c3b5f5ea686c543fdc90e6d74a005877c45022 - languageName: node - linkType: hard - -"emoji-regex@npm:^8.0.0": - version: 8.0.0 - resolution: "emoji-regex@npm:8.0.0" - checksum: 10c0/b6053ad39951c4cf338f9092d7bfba448cdfd46fe6a2a034700b149ac9ffbc137e361cbd3c442297f86bed2e5f7576c1b54cc0a6bf8ef5106cc62f496af35010 - languageName: node - linkType: hard - -"emoji-regex@npm:^9.2.2": - version: 9.2.2 - resolution: "emoji-regex@npm:9.2.2" - checksum: 10c0/af014e759a72064cf66e6e694a7fc6b0ed3d8db680427b021a89727689671cefe9d04151b2cad51dbaf85d5ba790d061cd167f1cf32eb7b281f6368b3c181639 - languageName: node - linkType: hard - -"encodeurl@npm:^2.0.0, encodeurl@npm:~2.0.0": - version: 2.0.0 - resolution: "encodeurl@npm:2.0.0" - checksum: 10c0/5d317306acb13e6590e28e27924c754163946a2480de11865c991a3a7eed4315cd3fba378b543ca145829569eefe9b899f3d84bb09870f675ae60bc924b01ceb - languageName: node - linkType: hard - -"encodeurl@npm:~1.0.2": - version: 1.0.2 - resolution: "encodeurl@npm:1.0.2" - checksum: 10c0/f6c2387379a9e7c1156c1c3d4f9cb7bb11cf16dd4c1682e1f6746512564b053df5781029b6061296832b59fb22f459dbe250386d217c2f6e203601abb2ee0bec - languageName: node - linkType: hard - -"encoding@npm:^0.1.13": - version: 0.1.13 - resolution: "encoding@npm:0.1.13" - dependencies: - iconv-lite: "npm:^0.6.2" - checksum: 10c0/36d938712ff00fe1f4bac88b43bcffb5930c1efa57bbcdca9d67e1d9d6c57cfb1200fb01efe0f3109b2ce99b231f90779532814a81370a1bd3274a0f58585039 - languageName: node - linkType: hard - -"engine.io-client@npm:~6.5.2": - version: 6.5.4 - resolution: "engine.io-client@npm:6.5.4" - dependencies: - "@socket.io/component-emitter": "npm:~3.1.0" - debug: "npm:~4.3.1" - engine.io-parser: "npm:~5.2.1" - ws: "npm:~8.17.1" - xmlhttprequest-ssl: "npm:~2.0.0" - checksum: 10c0/ef220f9875d6a43bade906bd9b61118e812474bbe46e80f38c92dca238484170daf92d51e58bbade6433c29ffb5ba329f4864c5609f2e33c5e31041b1f8ad672 - languageName: node - linkType: hard - -"engine.io-parser@npm:~5.2.1": - version: 5.2.3 - resolution: "engine.io-parser@npm:5.2.3" - checksum: 10c0/ed4900d8dbef470ab3839ccf3bfa79ee518ea8277c7f1f2759e8c22a48f64e687ea5e474291394d0c94f84054749fd93f3ef0acb51fa2f5f234cc9d9d8e7c536 - languageName: node - linkType: hard - -"engine.io@npm:~6.5.2": - version: 6.5.5 - resolution: "engine.io@npm:6.5.5" - dependencies: - "@types/cookie": "npm:^0.4.1" - "@types/cors": "npm:^2.8.12" - "@types/node": "npm:>=10.0.0" - accepts: "npm:~1.3.4" - base64id: "npm:2.0.0" - cookie: "npm:~0.4.1" - cors: "npm:~2.8.5" - debug: "npm:~4.3.1" - engine.io-parser: "npm:~5.2.1" - ws: "npm:~8.17.1" - checksum: 10c0/b0994134917c5d3649fd7aea283492eaf092131e572a8d379c7c9081548b42cff756730b4641edd0d1598148dd3be253c4d634cea2ba5c59622d175d9e567469 - languageName: node - linkType: hard - -"env-paths@npm:^2.2.0": - version: 2.2.1 - resolution: "env-paths@npm:2.2.1" - checksum: 10c0/285325677bf00e30845e330eec32894f5105529db97496ee3f598478e50f008c5352a41a30e5e72ec9de8a542b5a570b85699cd63bd2bc646dbcb9f311d83bc4 - languageName: node - linkType: hard - -"err-code@npm:^2.0.2": - version: 2.0.3 - resolution: "err-code@npm:2.0.3" - checksum: 10c0/b642f7b4dd4a376e954947550a3065a9ece6733ab8e51ad80db727aaae0817c2e99b02a97a3d6cecc648a97848305e728289cf312d09af395403a90c9d4d8a66 - languageName: node - linkType: hard - -"error-ex@npm:^1.3.1": - version: 1.3.2 - resolution: "error-ex@npm:1.3.2" - dependencies: - is-arrayish: "npm:^0.2.1" - checksum: 10c0/ba827f89369b4c93382cfca5a264d059dfefdaa56ecc5e338ffa58a6471f5ed93b71a20add1d52290a4873d92381174382658c885ac1a2305f7baca363ce9cce - languageName: node - linkType: hard - -"es-abstract@npm:^1.17.5, es-abstract@npm:^1.22.1, es-abstract@npm:^1.22.3, es-abstract@npm:^1.23.0, es-abstract@npm:^1.23.1, es-abstract@npm:^1.23.2, es-abstract@npm:^1.23.3": - version: 1.23.3 - resolution: "es-abstract@npm:1.23.3" - dependencies: - array-buffer-byte-length: "npm:^1.0.1" - arraybuffer.prototype.slice: "npm:^1.0.3" - available-typed-arrays: "npm:^1.0.7" - call-bind: "npm:^1.0.7" - data-view-buffer: "npm:^1.0.1" - data-view-byte-length: "npm:^1.0.1" - data-view-byte-offset: "npm:^1.0.0" - es-define-property: "npm:^1.0.0" - es-errors: "npm:^1.3.0" - es-object-atoms: "npm:^1.0.0" - es-set-tostringtag: "npm:^2.0.3" - es-to-primitive: "npm:^1.2.1" - function.prototype.name: "npm:^1.1.6" - get-intrinsic: "npm:^1.2.4" - get-symbol-description: "npm:^1.0.2" - globalthis: "npm:^1.0.3" - gopd: "npm:^1.0.1" - has-property-descriptors: "npm:^1.0.2" - has-proto: "npm:^1.0.3" - has-symbols: "npm:^1.0.3" - hasown: "npm:^2.0.2" - internal-slot: "npm:^1.0.7" - is-array-buffer: "npm:^3.0.4" - is-callable: "npm:^1.2.7" - is-data-view: "npm:^1.0.1" - is-negative-zero: "npm:^2.0.3" - is-regex: "npm:^1.1.4" - is-shared-array-buffer: "npm:^1.0.3" - is-string: "npm:^1.0.7" - is-typed-array: "npm:^1.1.13" - is-weakref: "npm:^1.0.2" - object-inspect: "npm:^1.13.1" - object-keys: "npm:^1.1.1" - object.assign: "npm:^4.1.5" - regexp.prototype.flags: "npm:^1.5.2" - safe-array-concat: "npm:^1.1.2" - safe-regex-test: "npm:^1.0.3" - string.prototype.trim: "npm:^1.2.9" - string.prototype.trimend: "npm:^1.0.8" - string.prototype.trimstart: "npm:^1.0.8" - typed-array-buffer: "npm:^1.0.2" - typed-array-byte-length: "npm:^1.0.1" - typed-array-byte-offset: "npm:^1.0.2" - typed-array-length: "npm:^1.0.6" - unbox-primitive: "npm:^1.0.2" - which-typed-array: "npm:^1.1.15" - checksum: 10c0/d27e9afafb225c6924bee9971a7f25f20c314f2d6cb93a63cada4ac11dcf42040896a6c22e5fb8f2a10767055ed4ddf400be3b1eb12297d281726de470b75666 - languageName: node - linkType: hard - -"es-define-property@npm:^1.0.0": - version: 1.0.0 - resolution: "es-define-property@npm:1.0.0" - dependencies: - get-intrinsic: "npm:^1.2.4" - checksum: 10c0/6bf3191feb7ea2ebda48b577f69bdfac7a2b3c9bcf97307f55fd6ef1bbca0b49f0c219a935aca506c993d8c5d8bddd937766cb760cd5e5a1071351f2df9f9aa4 - languageName: node - linkType: hard - -"es-errors@npm:^1.2.1, es-errors@npm:^1.3.0": - version: 1.3.0 - resolution: "es-errors@npm:1.3.0" - checksum: 10c0/0a61325670072f98d8ae3b914edab3559b6caa980f08054a3b872052640d91da01d38df55df797fcc916389d77fc92b8d5906cf028f4db46d7e3003abecbca85 - languageName: node - linkType: hard - -"es-get-iterator@npm:^1.1.3": - version: 1.1.3 - resolution: "es-get-iterator@npm:1.1.3" - dependencies: - call-bind: "npm:^1.0.2" - get-intrinsic: "npm:^1.1.3" - has-symbols: "npm:^1.0.3" - is-arguments: "npm:^1.1.1" - is-map: "npm:^2.0.2" - is-set: "npm:^2.0.2" - is-string: "npm:^1.0.7" - isarray: "npm:^2.0.5" - stop-iteration-iterator: "npm:^1.0.0" - checksum: 10c0/ebd11effa79851ea75d7f079405f9d0dc185559fd65d986c6afea59a0ff2d46c2ed8675f19f03dce7429d7f6c14ff9aede8d121fbab78d75cfda6a263030bac0 - languageName: node - linkType: hard - -"es-iterator-helpers@npm:^1.0.19": - version: 1.0.19 - resolution: "es-iterator-helpers@npm:1.0.19" - dependencies: - call-bind: "npm:^1.0.7" - define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.3" - es-errors: "npm:^1.3.0" - es-set-tostringtag: "npm:^2.0.3" - function-bind: "npm:^1.1.2" - get-intrinsic: "npm:^1.2.4" - globalthis: "npm:^1.0.3" - has-property-descriptors: "npm:^1.0.2" - has-proto: "npm:^1.0.3" - has-symbols: "npm:^1.0.3" - internal-slot: "npm:^1.0.7" - iterator.prototype: "npm:^1.1.2" - safe-array-concat: "npm:^1.1.2" - checksum: 10c0/ae8f0241e383b3d197383b9842c48def7fce0255fb6ed049311b686ce295595d9e389b466f6a1b7d4e7bb92d82f5e716d6fae55e20c1040249bf976743b038c5 - languageName: node - linkType: hard - -"es-object-atoms@npm:^1.0.0": - version: 1.0.0 - resolution: "es-object-atoms@npm:1.0.0" - dependencies: - es-errors: "npm:^1.3.0" - checksum: 10c0/1fed3d102eb27ab8d983337bb7c8b159dd2a1e63ff833ec54eea1311c96d5b08223b433060ba240541ca8adba9eee6b0a60cdbf2f80634b784febc9cc8b687b4 - languageName: node - linkType: hard - -"es-set-tostringtag@npm:^2.0.3": - version: 2.0.3 - resolution: "es-set-tostringtag@npm:2.0.3" - dependencies: - get-intrinsic: "npm:^1.2.4" - has-tostringtag: "npm:^1.0.2" - hasown: "npm:^2.0.1" - checksum: 10c0/f22aff1585eb33569c326323f0b0d175844a1f11618b86e193b386f8be0ea9474cfbe46df39c45d959f7aa8f6c06985dc51dd6bce5401645ec5a74c4ceaa836a - languageName: node - linkType: hard - -"es-shim-unscopables@npm:^1.0.0, es-shim-unscopables@npm:^1.0.2": - version: 1.0.2 - resolution: "es-shim-unscopables@npm:1.0.2" - dependencies: - hasown: "npm:^2.0.0" - checksum: 10c0/f495af7b4b7601a4c0cfb893581c352636e5c08654d129590386a33a0432cf13a7bdc7b6493801cadd990d838e2839b9013d1de3b880440cb537825e834fe783 - languageName: node - linkType: hard - -"es-to-primitive@npm:^1.2.1": - version: 1.2.1 - resolution: "es-to-primitive@npm:1.2.1" - dependencies: - is-callable: "npm:^1.1.4" - is-date-object: "npm:^1.0.1" - is-symbol: "npm:^1.0.2" - checksum: 10c0/0886572b8dc075cb10e50c0af62a03d03a68e1e69c388bd4f10c0649ee41b1fbb24840a1b7e590b393011b5cdbe0144b776da316762653685432df37d6de60f1 - languageName: node - linkType: hard - -"esbuild@npm:^0.21.3": - version: 0.21.5 - resolution: "esbuild@npm:0.21.5" - dependencies: - "@esbuild/aix-ppc64": "npm:0.21.5" - "@esbuild/android-arm": "npm:0.21.5" - "@esbuild/android-arm64": "npm:0.21.5" - "@esbuild/android-x64": "npm:0.21.5" - "@esbuild/darwin-arm64": "npm:0.21.5" - "@esbuild/darwin-x64": "npm:0.21.5" - "@esbuild/freebsd-arm64": "npm:0.21.5" - "@esbuild/freebsd-x64": "npm:0.21.5" - "@esbuild/linux-arm": "npm:0.21.5" - "@esbuild/linux-arm64": "npm:0.21.5" - "@esbuild/linux-ia32": "npm:0.21.5" - "@esbuild/linux-loong64": "npm:0.21.5" - "@esbuild/linux-mips64el": "npm:0.21.5" - "@esbuild/linux-ppc64": "npm:0.21.5" - "@esbuild/linux-riscv64": "npm:0.21.5" - "@esbuild/linux-s390x": "npm:0.21.5" - "@esbuild/linux-x64": "npm:0.21.5" - "@esbuild/netbsd-x64": "npm:0.21.5" - "@esbuild/openbsd-x64": "npm:0.21.5" - "@esbuild/sunos-x64": "npm:0.21.5" - "@esbuild/win32-arm64": "npm:0.21.5" - "@esbuild/win32-ia32": "npm:0.21.5" - "@esbuild/win32-x64": "npm:0.21.5" - dependenciesMeta: - "@esbuild/aix-ppc64": - optional: true - "@esbuild/android-arm": - optional: true - "@esbuild/android-arm64": - optional: true - "@esbuild/android-x64": - optional: true - "@esbuild/darwin-arm64": - optional: true - "@esbuild/darwin-x64": - optional: true - "@esbuild/freebsd-arm64": - optional: true - "@esbuild/freebsd-x64": - optional: true - "@esbuild/linux-arm": - optional: true - "@esbuild/linux-arm64": - optional: true - "@esbuild/linux-ia32": - optional: true - "@esbuild/linux-loong64": - optional: true - "@esbuild/linux-mips64el": - optional: true - "@esbuild/linux-ppc64": - optional: true - "@esbuild/linux-riscv64": - optional: true - "@esbuild/linux-s390x": - optional: true - "@esbuild/linux-x64": - optional: true - "@esbuild/netbsd-x64": - optional: true - "@esbuild/openbsd-x64": - optional: true - "@esbuild/sunos-x64": - optional: true - "@esbuild/win32-arm64": - optional: true - "@esbuild/win32-ia32": - optional: true - "@esbuild/win32-x64": - optional: true - bin: - esbuild: bin/esbuild - checksum: 10c0/fa08508adf683c3f399e8a014a6382a6b65542213431e26206c0720e536b31c09b50798747c2a105a4bbba1d9767b8d3615a74c2f7bf1ddf6d836cd11eb672de - languageName: node - linkType: hard - -"esbuild@npm:~0.23.0": - version: 0.23.1 - resolution: "esbuild@npm:0.23.1" - dependencies: - "@esbuild/aix-ppc64": "npm:0.23.1" - "@esbuild/android-arm": "npm:0.23.1" - "@esbuild/android-arm64": "npm:0.23.1" - "@esbuild/android-x64": "npm:0.23.1" - "@esbuild/darwin-arm64": "npm:0.23.1" - "@esbuild/darwin-x64": "npm:0.23.1" - "@esbuild/freebsd-arm64": "npm:0.23.1" - "@esbuild/freebsd-x64": "npm:0.23.1" - "@esbuild/linux-arm": "npm:0.23.1" - "@esbuild/linux-arm64": "npm:0.23.1" - "@esbuild/linux-ia32": "npm:0.23.1" - "@esbuild/linux-loong64": "npm:0.23.1" - "@esbuild/linux-mips64el": "npm:0.23.1" - "@esbuild/linux-ppc64": "npm:0.23.1" - "@esbuild/linux-riscv64": "npm:0.23.1" - "@esbuild/linux-s390x": "npm:0.23.1" - "@esbuild/linux-x64": "npm:0.23.1" - "@esbuild/netbsd-x64": "npm:0.23.1" - "@esbuild/openbsd-arm64": "npm:0.23.1" - "@esbuild/openbsd-x64": "npm:0.23.1" - "@esbuild/sunos-x64": "npm:0.23.1" - "@esbuild/win32-arm64": "npm:0.23.1" - "@esbuild/win32-ia32": "npm:0.23.1" - "@esbuild/win32-x64": "npm:0.23.1" - dependenciesMeta: - "@esbuild/aix-ppc64": - optional: true - "@esbuild/android-arm": - optional: true - "@esbuild/android-arm64": - optional: true - "@esbuild/android-x64": - optional: true - "@esbuild/darwin-arm64": - optional: true - "@esbuild/darwin-x64": - optional: true - "@esbuild/freebsd-arm64": - optional: true - "@esbuild/freebsd-x64": - optional: true - "@esbuild/linux-arm": - optional: true - "@esbuild/linux-arm64": - optional: true - "@esbuild/linux-ia32": - optional: true - "@esbuild/linux-loong64": - optional: true - "@esbuild/linux-mips64el": - optional: true - "@esbuild/linux-ppc64": - optional: true - "@esbuild/linux-riscv64": - optional: true - "@esbuild/linux-s390x": - optional: true - "@esbuild/linux-x64": - optional: true - "@esbuild/netbsd-x64": - optional: true - "@esbuild/openbsd-arm64": - optional: true - "@esbuild/openbsd-x64": - optional: true - "@esbuild/sunos-x64": - optional: true - "@esbuild/win32-arm64": - optional: true - "@esbuild/win32-ia32": - optional: true - "@esbuild/win32-x64": - optional: true - bin: - esbuild: bin/esbuild - checksum: 10c0/08c2ed1105cc3c5e3a24a771e35532fe6089dd24a39c10097899072cef4a99f20860e41e9294e000d86380f353b04d8c50af482483d7f69f5208481cce61eec7 - languageName: node - linkType: hard - -"escalade@npm:^3.1.1": - version: 3.1.1 - resolution: "escalade@npm:3.1.1" - checksum: 10c0/afd02e6ca91ffa813e1108b5e7756566173d6bc0d1eb951cb44d6b21702ec17c1cf116cfe75d4a2b02e05acb0b808a7a9387d0d1ca5cf9c04ad03a8445c3e46d - languageName: node - linkType: hard - -"escalade@npm:^3.1.2": - version: 3.2.0 - resolution: "escalade@npm:3.2.0" - checksum: 10c0/ced4dd3a78e15897ed3be74e635110bbf3b08877b0a41be50dcb325ee0e0b5f65fc2d50e9845194d7c4633f327e2e1c6cce00a71b617c5673df0374201d67f65 - languageName: node - linkType: hard - -"escape-html@npm:^1.0.3, escape-html@npm:~1.0.3": - version: 1.0.3 - resolution: "escape-html@npm:1.0.3" - checksum: 10c0/524c739d776b36c3d29fa08a22e03e8824e3b2fd57500e5e44ecf3cc4707c34c60f9ca0781c0e33d191f2991161504c295e98f68c78fe7baa6e57081ec6ac0a3 - languageName: node - linkType: hard - -"escape-string-regexp@npm:^1.0.5": - version: 1.0.5 - resolution: "escape-string-regexp@npm:1.0.5" - checksum: 10c0/a968ad453dd0c2724e14a4f20e177aaf32bb384ab41b674a8454afe9a41c5e6fe8903323e0a1052f56289d04bd600f81278edf140b0fcc02f5cac98d0f5b5371 - languageName: node - linkType: hard - -"escape-string-regexp@npm:^4.0.0": - version: 4.0.0 - resolution: "escape-string-regexp@npm:4.0.0" - checksum: 10c0/9497d4dd307d845bd7f75180d8188bb17ea8c151c1edbf6b6717c100e104d629dc2dfb687686181b0f4b7d732c7dfdc4d5e7a8ff72de1b0ca283a75bbb3a9cd9 - languageName: node - linkType: hard - -"eslint-config-prettier@npm:^9.1.0": - version: 9.1.0 - resolution: "eslint-config-prettier@npm:9.1.0" - peerDependencies: - eslint: ">=7.0.0" - bin: - eslint-config-prettier: bin/cli.js - checksum: 10c0/6d332694b36bc9ac6fdb18d3ca2f6ac42afa2ad61f0493e89226950a7091e38981b66bac2b47ba39d15b73fff2cd32c78b850a9cf9eed9ca9a96bfb2f3a2f10d - languageName: node - linkType: hard - -"eslint-config-react-app@npm:^7.0.1": - version: 7.0.1 - resolution: "eslint-config-react-app@npm:7.0.1" - dependencies: - "@babel/core": "npm:^7.16.0" - "@babel/eslint-parser": "npm:^7.16.3" - "@rushstack/eslint-patch": "npm:^1.1.0" - "@typescript-eslint/eslint-plugin": "npm:^5.5.0" - "@typescript-eslint/parser": "npm:^5.5.0" - babel-preset-react-app: "npm:^10.0.1" - confusing-browser-globals: "npm:^1.0.11" - eslint-plugin-flowtype: "npm:^8.0.3" - eslint-plugin-import: "npm:^2.25.3" - eslint-plugin-jest: "npm:^25.3.0" - eslint-plugin-jsx-a11y: "npm:^6.5.1" - eslint-plugin-react: "npm:^7.27.1" - eslint-plugin-react-hooks: "npm:^4.3.0" - eslint-plugin-testing-library: "npm:^5.0.1" - peerDependencies: - eslint: ^8.0.0 - checksum: 10c0/be290ec0cd5a2c0bb0b85cb1645e8734769cae77f101cd453631d77a60fa4894ee8b5b1e080ee8c21e01af0d0fc22367a2882931a549691b5ab801abb985cbba - languageName: node - linkType: hard - -"eslint-import-resolver-node@npm:^0.3.9": - version: 0.3.9 - resolution: "eslint-import-resolver-node@npm:0.3.9" - dependencies: - debug: "npm:^3.2.7" - is-core-module: "npm:^2.13.0" - resolve: "npm:^1.22.4" - checksum: 10c0/0ea8a24a72328a51fd95aa8f660dcca74c1429806737cf10261ab90cfcaaf62fd1eff664b76a44270868e0a932711a81b250053942595bcd00a93b1c1575dd61 - languageName: node - linkType: hard - -"eslint-module-utils@npm:^2.9.0": - version: 2.9.0 - resolution: "eslint-module-utils@npm:2.9.0" - dependencies: - debug: "npm:^3.2.7" - peerDependenciesMeta: - eslint: - optional: true - checksum: 10c0/7c45c5b54402a969e99315890c10e9bf8c8bee16c7890573343af05dfa04566d61546585678c413e5228af0550e39461be47e35a8ff0d1863e113bdbb28d1d29 - languageName: node - linkType: hard - -"eslint-plugin-es@npm:^3.0.0": - version: 3.0.1 - resolution: "eslint-plugin-es@npm:3.0.1" - dependencies: - eslint-utils: "npm:^2.0.0" - regexpp: "npm:^3.0.0" - peerDependencies: - eslint: ">=4.19.1" - checksum: 10c0/12ae730aa9603e680af048e1653aac15e529411b68b8d0da6e290700b17c695485af7c3f5360f531f80970786cab7288c2c1d4a58c35ec1bb89649897c016c4a - languageName: node - linkType: hard - -"eslint-plugin-flowtype@npm:^8.0.3": - version: 8.0.3 - resolution: "eslint-plugin-flowtype@npm:8.0.3" - dependencies: - lodash: "npm:^4.17.21" - string-natural-compare: "npm:^3.0.1" - peerDependencies: - "@babel/plugin-syntax-flow": ^7.14.5 - "@babel/plugin-transform-react-jsx": ^7.14.9 - eslint: ^8.1.0 - checksum: 10c0/a4596ba1cb80c19a06f1ddef6c36e6a671769da8d056d4a8f3482a2c46f475c547e78f82c3233099dba3759dc9a29e36d0ca07019cf6deb666db17f49d8f566d - languageName: node - linkType: hard - -"eslint-plugin-import@npm:^2.25.3, eslint-plugin-import@npm:^2.30.0": - version: 2.30.0 - resolution: "eslint-plugin-import@npm:2.30.0" - dependencies: - "@rtsao/scc": "npm:^1.1.0" - array-includes: "npm:^3.1.8" - array.prototype.findlastindex: "npm:^1.2.5" - array.prototype.flat: "npm:^1.3.2" - array.prototype.flatmap: "npm:^1.3.2" - debug: "npm:^3.2.7" - doctrine: "npm:^2.1.0" - eslint-import-resolver-node: "npm:^0.3.9" - eslint-module-utils: "npm:^2.9.0" - hasown: "npm:^2.0.2" - is-core-module: "npm:^2.15.1" - is-glob: "npm:^4.0.3" - minimatch: "npm:^3.1.2" - object.fromentries: "npm:^2.0.8" - object.groupby: "npm:^1.0.3" - object.values: "npm:^1.2.0" - semver: "npm:^6.3.1" - tsconfig-paths: "npm:^3.15.0" - peerDependencies: - eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 - checksum: 10c0/4c9dcb1f27505c4d5dd891d2b551f56c70786d136aa3992a77e785bdc67c9f60200a2c7fb0ce55b7647fe550b12bc433d5dfa59e2c00ab44227791c5ab86badf - languageName: node - linkType: hard - -"eslint-plugin-jest@npm:^25.3.0": - version: 25.7.0 - resolution: "eslint-plugin-jest@npm:25.7.0" - dependencies: - "@typescript-eslint/experimental-utils": "npm:^5.0.0" - peerDependencies: - "@typescript-eslint/eslint-plugin": ^4.0.0 || ^5.0.0 - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - peerDependenciesMeta: - "@typescript-eslint/eslint-plugin": - optional: true - jest: - optional: true - checksum: 10c0/72dad05323d54e815c2bb10235bd5b77423796ef2e2940f7dba28bdb6cfac5a578793d3f0c7ac74618c41f9d8d6f345097a2a1f89f41aeec27bb873cb66ab270 - languageName: node - linkType: hard - -"eslint-plugin-jsx-a11y@npm:^6.5.1": - version: 6.9.0 - resolution: "eslint-plugin-jsx-a11y@npm:6.9.0" - dependencies: - aria-query: "npm:~5.1.3" - array-includes: "npm:^3.1.8" - array.prototype.flatmap: "npm:^1.3.2" - ast-types-flow: "npm:^0.0.8" - axe-core: "npm:^4.9.1" - axobject-query: "npm:~3.1.1" - damerau-levenshtein: "npm:^1.0.8" - emoji-regex: "npm:^9.2.2" - es-iterator-helpers: "npm:^1.0.19" - hasown: "npm:^2.0.2" - jsx-ast-utils: "npm:^3.3.5" - language-tags: "npm:^1.0.9" - minimatch: "npm:^3.1.2" - object.fromentries: "npm:^2.0.8" - safe-regex-test: "npm:^1.0.3" - string.prototype.includes: "npm:^2.0.0" - peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 - checksum: 10c0/72ac719ca90b6149c8f3c708ac5b1177f6757668b6e174d72a78512d4ac10329331b9c666c21e9561237a96a45d7f147f6a5d270dadbb99eb4ee093f127792c3 - languageName: node - linkType: hard - -"eslint-plugin-node@npm:^11.1.0": - version: 11.1.0 - resolution: "eslint-plugin-node@npm:11.1.0" - dependencies: - eslint-plugin-es: "npm:^3.0.0" - eslint-utils: "npm:^2.0.0" - ignore: "npm:^5.1.1" - minimatch: "npm:^3.0.4" - resolve: "npm:^1.10.1" - semver: "npm:^6.1.0" - peerDependencies: - eslint: ">=5.16.0" - checksum: 10c0/c7716adac4020cb852fd2410dcd8bdb13a227004de77f96d7f9806d0cf2274f24e0920a7ca73bcd72d90003696c1f17fdd9fe3ca218e64ee03dc2b840e4416fa - languageName: node - linkType: hard - -"eslint-plugin-prettier@npm:^5.2.1": - version: 5.2.1 - resolution: "eslint-plugin-prettier@npm:5.2.1" - dependencies: - prettier-linter-helpers: "npm:^1.0.0" - synckit: "npm:^0.9.1" - peerDependencies: - "@types/eslint": ">=8.0.0" - eslint: ">=8.0.0" - eslint-config-prettier: "*" - prettier: ">=3.0.0" - peerDependenciesMeta: - "@types/eslint": - optional: true - eslint-config-prettier: - optional: true - checksum: 10c0/4bc8bbaf5bb556c9c501dcdff369137763c49ccaf544f9fa91400360ed5e3a3f1234ab59690e06beca5b1b7e6f6356978cdd3b02af6aba3edea2ffe69ca6e8b2 - languageName: node - linkType: hard - -"eslint-plugin-react-hooks@npm:^4.3.0": - version: 4.6.2 - resolution: "eslint-plugin-react-hooks@npm:4.6.2" - peerDependencies: - eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 - checksum: 10c0/4844e58c929bc05157fb70ba1e462e34f1f4abcbc8dd5bbe5b04513d33e2699effb8bca668297976ceea8e7ebee4e8fc29b9af9d131bcef52886feaa2308b2cc - languageName: node - linkType: hard - -"eslint-plugin-react@npm:^7.27.1": - version: 7.35.1 - resolution: "eslint-plugin-react@npm:7.35.1" - dependencies: - array-includes: "npm:^3.1.8" - array.prototype.findlast: "npm:^1.2.5" - array.prototype.flatmap: "npm:^1.3.2" - array.prototype.tosorted: "npm:^1.1.4" - doctrine: "npm:^2.1.0" - es-iterator-helpers: "npm:^1.0.19" - estraverse: "npm:^5.3.0" - hasown: "npm:^2.0.2" - jsx-ast-utils: "npm:^2.4.1 || ^3.0.0" - minimatch: "npm:^3.1.2" - object.entries: "npm:^1.1.8" - object.fromentries: "npm:^2.0.8" - object.values: "npm:^1.2.0" - prop-types: "npm:^15.8.1" - resolve: "npm:^2.0.0-next.5" - semver: "npm:^6.3.1" - string.prototype.matchall: "npm:^4.0.11" - string.prototype.repeat: "npm:^1.0.0" - peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 - checksum: 10c0/faaa2108543675951949685208d105d9c3efa0ac8f4d950851b8f20e065b8bf87fdd7e78abae9b0382090bc22e61c00709388f35bdab2a8b1d283f985d310ed7 - languageName: node - linkType: hard - -"eslint-plugin-testing-library@npm:^5.0.1": - version: 5.11.1 - resolution: "eslint-plugin-testing-library@npm:5.11.1" - dependencies: - "@typescript-eslint/utils": "npm:^5.58.0" - peerDependencies: - eslint: ^7.5.0 || ^8.0.0 - checksum: 10c0/55c7792345710a2b951acb0552ebe4e491d988f7d37fd308749e75fdbc36142b9a151ecec03b39992f672afea1a99dd3c3d2fb9f737ef18f56d7168e294fd9eb - languageName: node - linkType: hard - -"eslint-scope@npm:5.1.1, eslint-scope@npm:^5.1.1": - version: 5.1.1 - resolution: "eslint-scope@npm:5.1.1" - dependencies: - esrecurse: "npm:^4.3.0" - estraverse: "npm:^4.1.1" - checksum: 10c0/d30ef9dc1c1cbdece34db1539a4933fe3f9b14e1ffb27ecc85987902ee663ad7c9473bbd49a9a03195a373741e62e2f807c4938992e019b511993d163450e70a - languageName: node - linkType: hard - -"eslint-scope@npm:^8.0.2": - version: 8.0.2 - resolution: "eslint-scope@npm:8.0.2" - dependencies: - esrecurse: "npm:^4.3.0" - estraverse: "npm:^5.2.0" - checksum: 10c0/477f820647c8755229da913025b4567347fd1f0bf7cbdf3a256efff26a7e2e130433df052bd9e3d014025423dc00489bea47eb341002b15553673379c1a7dc36 - languageName: node - linkType: hard - -"eslint-utils@npm:^2.0.0": - version: 2.1.0 - resolution: "eslint-utils@npm:2.1.0" - dependencies: - eslint-visitor-keys: "npm:^1.1.0" - checksum: 10c0/69521c5d6569384b24093125d037ba238d3d6e54367f7143af9928f5286369e912c26cad5016d730c0ffb9797ac9e83831059d7f1d863f7dc84330eb02414611 - languageName: node - linkType: hard - -"eslint-visitor-keys@npm:^1.1.0": - version: 1.3.0 - resolution: "eslint-visitor-keys@npm:1.3.0" - checksum: 10c0/10c91fdbbe36810dd4308e57f9a8bc7177188b2a70247e54e3af1fa05ebc66414ae6fd4ce3c6c6821591f43a556e9037bc6b071122e099b5f8b7d2f76df553e3 - languageName: node - linkType: hard - -"eslint-visitor-keys@npm:^2.1.0": - version: 2.1.0 - resolution: "eslint-visitor-keys@npm:2.1.0" - checksum: 10c0/9f0e3a2db751d84067d15977ac4b4472efd6b303e369e6ff241a99feac04da758f46d5add022c33d06b53596038dbae4b4aceb27c7e68b8dfc1055b35e495787 - languageName: node - linkType: hard - -"eslint-visitor-keys@npm:^3.3.0": - version: 3.3.0 - resolution: "eslint-visitor-keys@npm:3.3.0" - checksum: 10c0/fc6a9b5bdee8d90e35e7564fd9db10fdf507a2c089a4f0d4d3dd091f7f4ac6790547c8b1b7a760642ef819f875ef86dd5bcb8cdf01b0775f57a699f4e6a20a18 - languageName: node - linkType: hard - -"eslint-visitor-keys@npm:^3.4.3": - version: 3.4.3 - resolution: "eslint-visitor-keys@npm:3.4.3" - checksum: 10c0/92708e882c0a5ffd88c23c0b404ac1628cf20104a108c745f240a13c332a11aac54f49a22d5762efbffc18ecbc9a580d1b7ad034bf5f3cc3307e5cbff2ec9820 - languageName: node - linkType: hard - -"eslint-visitor-keys@npm:^4.0.0": - version: 4.0.0 - resolution: "eslint-visitor-keys@npm:4.0.0" - checksum: 10c0/76619f42cf162705a1515a6868e6fc7567e185c7063a05621a8ac4c3b850d022661262c21d9f1fc1d144ecf0d5d64d70a3f43c15c3fc969a61ace0fb25698cf5 - languageName: node - linkType: hard - -"eslint@npm:^9.9.1": - version: 9.9.1 - resolution: "eslint@npm:9.9.1" - dependencies: - "@eslint-community/eslint-utils": "npm:^4.2.0" - "@eslint-community/regexpp": "npm:^4.11.0" - "@eslint/config-array": "npm:^0.18.0" - "@eslint/eslintrc": "npm:^3.1.0" - "@eslint/js": "npm:9.9.1" - "@humanwhocodes/module-importer": "npm:^1.0.1" - "@humanwhocodes/retry": "npm:^0.3.0" - "@nodelib/fs.walk": "npm:^1.2.8" - ajv: "npm:^6.12.4" - chalk: "npm:^4.0.0" - cross-spawn: "npm:^7.0.2" - debug: "npm:^4.3.2" - escape-string-regexp: "npm:^4.0.0" - eslint-scope: "npm:^8.0.2" - eslint-visitor-keys: "npm:^4.0.0" - espree: "npm:^10.1.0" - esquery: "npm:^1.5.0" - esutils: "npm:^2.0.2" - fast-deep-equal: "npm:^3.1.3" - file-entry-cache: "npm:^8.0.0" - find-up: "npm:^5.0.0" - glob-parent: "npm:^6.0.2" - ignore: "npm:^5.2.0" - imurmurhash: "npm:^0.1.4" - is-glob: "npm:^4.0.0" - is-path-inside: "npm:^3.0.3" - json-stable-stringify-without-jsonify: "npm:^1.0.1" - levn: "npm:^0.4.1" - lodash.merge: "npm:^4.6.2" - minimatch: "npm:^3.1.2" - natural-compare: "npm:^1.4.0" - optionator: "npm:^0.9.3" - strip-ansi: "npm:^6.0.1" - text-table: "npm:^0.2.0" - peerDependencies: - jiti: "*" - peerDependenciesMeta: - jiti: - optional: true - bin: - eslint: bin/eslint.js - checksum: 10c0/5e71efda7c0a14ee95436d5cdfed04ee61dfb1d89d7a32b50a424de2e680af82849628ea6581950c2e0726491f786a3cfd0032ce013c1c5093786e475cfdfb33 - languageName: node - linkType: hard - -"espree@npm:^10.0.1, espree@npm:^10.1.0": - version: 10.1.0 - resolution: "espree@npm:10.1.0" - dependencies: - acorn: "npm:^8.12.0" - acorn-jsx: "npm:^5.3.2" - eslint-visitor-keys: "npm:^4.0.0" - checksum: 10c0/52e6feaa77a31a6038f0c0e3fce93010a4625701925b0715cd54a2ae190b3275053a0717db698697b32653788ac04845e489d6773b508d6c2e8752f3c57470a0 - languageName: node - linkType: hard - -"esquery@npm:^1.5.0": - version: 1.6.0 - resolution: "esquery@npm:1.6.0" - dependencies: - estraverse: "npm:^5.1.0" - checksum: 10c0/cb9065ec605f9da7a76ca6dadb0619dfb611e37a81e318732977d90fab50a256b95fee2d925fba7c2f3f0523aa16f91587246693bc09bc34d5a59575fe6e93d2 - languageName: node - linkType: hard - -"esrecurse@npm:^4.3.0": - version: 4.3.0 - resolution: "esrecurse@npm:4.3.0" - dependencies: - estraverse: "npm:^5.2.0" - checksum: 10c0/81a37116d1408ded88ada45b9fb16dbd26fba3aadc369ce50fcaf82a0bac12772ebd7b24cd7b91fc66786bf2c1ac7b5f196bc990a473efff972f5cb338877cf5 - languageName: node - linkType: hard - -"estraverse@npm:^4.1.1": - version: 4.3.0 - resolution: "estraverse@npm:4.3.0" - checksum: 10c0/9cb46463ef8a8a4905d3708a652d60122a0c20bb58dec7e0e12ab0e7235123d74214fc0141d743c381813e1b992767e2708194f6f6e0f9fd00c1b4e0887b8b6d - languageName: node - linkType: hard - -"estraverse@npm:^5.1.0, estraverse@npm:^5.2.0, estraverse@npm:^5.3.0": - version: 5.3.0 - resolution: "estraverse@npm:5.3.0" - checksum: 10c0/1ff9447b96263dec95d6d67431c5e0771eb9776427421260a3e2f0fdd5d6bd4f8e37a7338f5ad2880c9f143450c9b1e4fc2069060724570a49cf9cf0312bd107 - languageName: node - linkType: hard - -"esutils@npm:^2.0.2": - version: 2.0.3 - resolution: "esutils@npm:2.0.3" - checksum: 10c0/9a2fe69a41bfdade834ba7c42de4723c97ec776e40656919c62cbd13607c45e127a003f05f724a1ea55e5029a4cf2de444b13009f2af71271e42d93a637137c7 - languageName: node - linkType: hard - -"etag@npm:^1.8.1, etag@npm:~1.8.1": - version: 1.8.1 - resolution: "etag@npm:1.8.1" - checksum: 10c0/12be11ef62fb9817314d790089a0a49fae4e1b50594135dcb8076312b7d7e470884b5100d249b28c18581b7fd52f8b485689ffae22a11ed9ec17377a33a08f84 - languageName: node - linkType: hard - -"exponential-backoff@npm:^3.1.1": - version: 3.1.1 - resolution: "exponential-backoff@npm:3.1.1" - checksum: 10c0/160456d2d647e6019640bd07111634d8c353038d9fa40176afb7cd49b0548bdae83b56d05e907c2cce2300b81cae35d800ef92fefb9d0208e190fa3b7d6bb579 - languageName: node - linkType: hard - -"express@npm:^5.0.0": - version: 5.0.1 - resolution: "express@npm:5.0.1" - dependencies: - accepts: "npm:^2.0.0" - body-parser: "npm:^2.0.1" - content-disposition: "npm:^1.0.0" - content-type: "npm:~1.0.4" - cookie: "npm:0.7.1" - cookie-signature: "npm:^1.2.1" - debug: "npm:4.3.6" - depd: "npm:2.0.0" - encodeurl: "npm:~2.0.0" - escape-html: "npm:~1.0.3" - etag: "npm:~1.8.1" - finalhandler: "npm:^2.0.0" - fresh: "npm:2.0.0" - http-errors: "npm:2.0.0" - merge-descriptors: "npm:^2.0.0" - methods: "npm:~1.1.2" - mime-types: "npm:^3.0.0" - on-finished: "npm:2.4.1" - once: "npm:1.4.0" - parseurl: "npm:~1.3.3" - proxy-addr: "npm:~2.0.7" - qs: "npm:6.13.0" - range-parser: "npm:~1.2.1" - router: "npm:^2.0.0" - safe-buffer: "npm:5.2.1" - send: "npm:^1.1.0" - serve-static: "npm:^2.1.0" - setprototypeof: "npm:1.2.0" - statuses: "npm:2.0.1" - type-is: "npm:^2.0.0" - utils-merge: "npm:1.0.1" - vary: "npm:~1.1.2" - checksum: 10c0/6e533f28adb64178c90c3e357cbcdb5d1f233ca1290d023b3c4936c4ac67c6699aaba726edf6c148979220ec7ce6ed2e5e374cff904e3a2bf9ce91620cf8afff - languageName: node - linkType: hard - -"fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": - version: 3.1.3 - resolution: "fast-deep-equal@npm:3.1.3" - checksum: 10c0/40dedc862eb8992c54579c66d914635afbec43350afbbe991235fdcb4e3a8d5af1b23ae7e79bef7d4882d0ecee06c3197488026998fb19f72dc95acff1d1b1d0 - languageName: node - linkType: hard - -"fast-diff@npm:^1.1.2": - version: 1.3.0 - resolution: "fast-diff@npm:1.3.0" - checksum: 10c0/5c19af237edb5d5effda008c891a18a585f74bf12953be57923f17a3a4d0979565fc64dbc73b9e20926b9d895f5b690c618cbb969af0cf022e3222471220ad29 - languageName: node - linkType: hard - -"fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.2": - version: 3.3.2 - resolution: "fast-glob@npm:3.3.2" - dependencies: - "@nodelib/fs.stat": "npm:^2.0.2" - "@nodelib/fs.walk": "npm:^1.2.3" - glob-parent: "npm:^5.1.2" - merge2: "npm:^1.3.0" - micromatch: "npm:^4.0.4" - checksum: 10c0/42baad7b9cd40b63e42039132bde27ca2cb3a4950d0a0f9abe4639ea1aa9d3e3b40f98b1fe31cbc0cc17b664c9ea7447d911a152fa34ec5b72977b125a6fc845 - languageName: node - linkType: hard - -"fast-json-stable-stringify@npm:^2.0.0": - version: 2.1.0 - resolution: "fast-json-stable-stringify@npm:2.1.0" - checksum: 10c0/7f081eb0b8a64e0057b3bb03f974b3ef00135fbf36c1c710895cd9300f13c94ba809bb3a81cf4e1b03f6e5285610a61abbd7602d0652de423144dfee5a389c9b - languageName: node - linkType: hard - -"fast-levenshtein@npm:^2.0.6": - version: 2.0.6 - resolution: "fast-levenshtein@npm:2.0.6" - checksum: 10c0/111972b37338bcb88f7d9e2c5907862c280ebf4234433b95bc611e518d192ccb2d38119c4ac86e26b668d75f7f3894f4ff5c4982899afced7ca78633b08287c4 - languageName: node - linkType: hard - -"fastq@npm:^1.6.0": - version: 1.15.0 - resolution: "fastq@npm:1.15.0" - dependencies: - reusify: "npm:^1.0.4" - checksum: 10c0/5ce4f83afa5f88c9379e67906b4d31bc7694a30826d6cc8d0f0473c966929017fda65c2174b0ec89f064ede6ace6c67f8a4fe04cef42119b6a55b0d465554c24 - languageName: node - linkType: hard - -"file-entry-cache@npm:^8.0.0": - version: 8.0.0 - resolution: "file-entry-cache@npm:8.0.0" - dependencies: - flat-cache: "npm:^4.0.0" - checksum: 10c0/9e2b5938b1cd9b6d7e3612bdc533afd4ac17b2fc646569e9a8abbf2eb48e5eb8e316bc38815a3ef6a1b456f4107f0d0f055a614ca613e75db6bf9ff4d72c1638 - languageName: node - linkType: hard - -"file-selector@npm:^0.4.0": - version: 0.4.0 - resolution: "file-selector@npm:0.4.0" - dependencies: - tslib: "npm:^2.0.3" - checksum: 10c0/2c55019ea5db84c0c27a5337028604d8a9fe9a53551eb20f4ad44541dfdfdda6bb576e950ac51d11fc3791247c9407734043a9c68a7fe473925631ffc03442e4 - languageName: node - linkType: hard - -"fill-range@npm:^7.1.1": - version: 7.1.1 - resolution: "fill-range@npm:7.1.1" - dependencies: - to-regex-range: "npm:^5.0.1" - checksum: 10c0/b75b691bbe065472f38824f694c2f7449d7f5004aa950426a2c28f0306c60db9b880c0b0e4ed819997ffb882d1da02cfcfc819bddc94d71627f5269682edf018 - languageName: node - linkType: hard - -"finalhandler@npm:^2.0.0": - version: 2.0.0 - resolution: "finalhandler@npm:2.0.0" - dependencies: - debug: "npm:2.6.9" - encodeurl: "npm:~1.0.2" - escape-html: "npm:~1.0.3" - on-finished: "npm:2.4.1" - parseurl: "npm:~1.3.3" - statuses: "npm:2.0.1" - unpipe: "npm:~1.0.0" - checksum: 10c0/ca6f69d69797eebc900d7627bde4bb7d38417112911eb11ce4e40011195b6ad1a09413ad082da9bb64da789a4ecfffdd0e6a5ea1ccb4147062224c3050f134ea - languageName: node - linkType: hard - -"find-root@npm:^1.1.0": - version: 1.1.0 - resolution: "find-root@npm:1.1.0" - checksum: 10c0/1abc7f3bf2f8d78ff26d9e00ce9d0f7b32e5ff6d1da2857bcdf4746134c422282b091c672cde0572cac3840713487e0a7a636af9aa1b74cb11894b447a521efa - languageName: node - linkType: hard - -"find-up@npm:^5.0.0": - version: 5.0.0 - resolution: "find-up@npm:5.0.0" - dependencies: - locate-path: "npm:^6.0.0" - path-exists: "npm:^4.0.0" - checksum: 10c0/062c5a83a9c02f53cdd6d175a37ecf8f87ea5bbff1fdfb828f04bfa021441bc7583e8ebc0872a4c1baab96221fb8a8a275a19809fb93fbc40bd69ec35634069a - languageName: node - linkType: hard - -"flat-cache@npm:^4.0.0": - version: 4.0.1 - resolution: "flat-cache@npm:4.0.1" - dependencies: - flatted: "npm:^3.2.9" - keyv: "npm:^4.5.4" - checksum: 10c0/2c59d93e9faa2523e4fda6b4ada749bed432cfa28c8e251f33b25795e426a1c6dbada777afb1f74fcfff33934fdbdea921ee738fcc33e71adc9d6eca984a1cfc - languageName: node - linkType: hard - -"flat@npm:^5.0.2": - version: 5.0.2 - resolution: "flat@npm:5.0.2" - bin: - flat: cli.js - checksum: 10c0/f178b13482f0cd80c7fede05f4d10585b1f2fdebf26e12edc138e32d3150c6ea6482b7f12813a1091143bad52bb6d3596bca51a162257a21163c0ff438baa5fe - languageName: node - linkType: hard - -"flatted@npm:^3.2.9": - version: 3.3.1 - resolution: "flatted@npm:3.3.1" - checksum: 10c0/324166b125ee07d4ca9bcf3a5f98d915d5db4f39d711fba640a3178b959919aae1f7cfd8aabcfef5826ed8aa8a2aa14cc85b2d7d18ff638ddf4ae3df39573eaf - languageName: node - linkType: hard - -"for-each@npm:^0.3.3": - version: 0.3.3 - resolution: "for-each@npm:0.3.3" - dependencies: - is-callable: "npm:^1.1.3" - checksum: 10c0/22330d8a2db728dbf003ec9182c2d421fbcd2969b02b4f97ec288721cda63eb28f2c08585ddccd0f77cb2930af8d958005c9e72f47141dc51816127a118f39aa - languageName: node - linkType: hard - -"foreground-child@npm:^3.1.0": - version: 3.3.0 - resolution: "foreground-child@npm:3.3.0" - dependencies: - cross-spawn: "npm:^7.0.0" - signal-exit: "npm:^4.0.1" - checksum: 10c0/028f1d41000553fcfa6c4bb5c372963bf3d9bf0b1f25a87d1a6253014343fb69dfb1b42d9625d7cf44c8ba429940f3d0ff718b62105d4d4a4f6ef8ca0a53faa2 - languageName: node - linkType: hard - -"forwarded@npm:0.2.0": - version: 0.2.0 - resolution: "forwarded@npm:0.2.0" - checksum: 10c0/9b67c3fac86acdbc9ae47ba1ddd5f2f81526fa4c8226863ede5600a3f7c7416ef451f6f1e240a3cc32d0fd79fcfe6beb08fd0da454f360032bde70bf80afbb33 - languageName: node - linkType: hard - -"fresh@npm:2.0.0": - version: 2.0.0 - resolution: "fresh@npm:2.0.0" - checksum: 10c0/0557548194cb9a809a435bf92bcfbc20c89e8b5eb38861b73ced36750437251e39a111fc3a18b98531be9dd91fe1411e4969f229dc579ec0251ce6c5d4900bbc - languageName: node - linkType: hard - -"fresh@npm:^0.5.2": - version: 0.5.2 - resolution: "fresh@npm:0.5.2" - checksum: 10c0/c6d27f3ed86cc5b601404822f31c900dd165ba63fff8152a3ef714e2012e7535027063bc67ded4cb5b3a49fa596495d46cacd9f47d6328459cf570f08b7d9e5a - languageName: node - linkType: hard - -"fs-minipass@npm:^2.0.0": - version: 2.1.0 - resolution: "fs-minipass@npm:2.1.0" - dependencies: - minipass: "npm:^3.0.0" - checksum: 10c0/703d16522b8282d7299337539c3ed6edddd1afe82435e4f5b76e34a79cd74e488a8a0e26a636afc2440e1a23b03878e2122e3a2cfe375a5cf63c37d92b86a004 - languageName: node - linkType: hard - -"fs-minipass@npm:^3.0.0": - version: 3.0.3 - resolution: "fs-minipass@npm:3.0.3" - dependencies: - minipass: "npm:^7.0.3" - checksum: 10c0/63e80da2ff9b621e2cb1596abcb9207f1cf82b968b116ccd7b959e3323144cce7fb141462200971c38bbf2ecca51695069db45265705bed09a7cd93ae5b89f94 - languageName: node - linkType: hard - -"fs.realpath@npm:^1.0.0": - version: 1.0.0 - resolution: "fs.realpath@npm:1.0.0" - checksum: 10c0/444cf1291d997165dfd4c0d58b69f0e4782bfd9149fd72faa4fe299e68e0e93d6db941660b37dd29153bf7186672ececa3b50b7e7249477b03fdf850f287c948 - languageName: node - linkType: hard - -"fsevents@npm:~2.3.2": - version: 2.3.2 - resolution: "fsevents@npm:2.3.2" - dependencies: - node-gyp: "npm:latest" - checksum: 10c0/be78a3efa3e181cda3cf7a4637cb527bcebb0bd0ea0440105a3bb45b86f9245b307dc10a2507e8f4498a7d4ec349d1910f4d73e4d4495b16103106e07eee735b - conditions: os=darwin - languageName: node - linkType: hard - -"fsevents@npm:~2.3.3": - version: 2.3.3 - resolution: "fsevents@npm:2.3.3" - dependencies: - node-gyp: "npm:latest" - checksum: 10c0/a1f0c44595123ed717febbc478aa952e47adfc28e2092be66b8ab1635147254ca6cfe1df792a8997f22716d4cbafc73309899ff7bfac2ac3ad8cf2e4ecc3ec60 - conditions: os=darwin - languageName: node - linkType: hard - -"fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin": - version: 2.3.2 - resolution: "fsevents@patch:fsevents@npm%3A2.3.2#optional!builtin::version=2.3.2&hash=df0bf1" - dependencies: - node-gyp: "npm:latest" - conditions: os=darwin - languageName: node - linkType: hard - -"fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": - version: 2.3.3 - resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" - dependencies: - node-gyp: "npm:latest" - conditions: os=darwin - languageName: node - linkType: hard - -"function-bind@npm:^1.1.1": - version: 1.1.1 - resolution: "function-bind@npm:1.1.1" - checksum: 10c0/60b74b2407e1942e1ed7f8c284f8ef714d0689dcfce5319985a5b7da3fc727f40b4a59ec72dc55aa83365ad7b8fa4fac3a30d93c850a2b452f29ae03dbc10a1e - languageName: node - linkType: hard - -"function-bind@npm:^1.1.2": - version: 1.1.2 - resolution: "function-bind@npm:1.1.2" - checksum: 10c0/d8680ee1e5fcd4c197e4ac33b2b4dce03c71f4d91717292785703db200f5c21f977c568d28061226f9b5900cbcd2c84463646134fd5337e7925e0942bc3f46d5 - languageName: node - linkType: hard - -"function.prototype.name@npm:^1.1.6": - version: 1.1.6 - resolution: "function.prototype.name@npm:1.1.6" - dependencies: - call-bind: "npm:^1.0.2" - define-properties: "npm:^1.2.0" - es-abstract: "npm:^1.22.1" - functions-have-names: "npm:^1.2.3" - checksum: 10c0/9eae11294905b62cb16874adb4fc687927cda3162285e0ad9612e6a1d04934005d46907362ea9cdb7428edce05a2f2c3dabc3b2d21e9fd343e9bb278230ad94b - languageName: node - linkType: hard - -"functions-have-names@npm:^1.2.3": - version: 1.2.3 - resolution: "functions-have-names@npm:1.2.3" - checksum: 10c0/33e77fd29bddc2d9bb78ab3eb854c165909201f88c75faa8272e35899e2d35a8a642a15e7420ef945e1f64a9670d6aa3ec744106b2aa42be68ca5114025954ca - languageName: node - linkType: hard - -"gensync@npm:^1.0.0-beta.2": - version: 1.0.0-beta.2 - resolution: "gensync@npm:1.0.0-beta.2" - checksum: 10c0/782aba6cba65b1bb5af3b095d96249d20edbe8df32dbf4696fd49be2583faf676173bf4809386588828e4dd76a3354fcbeb577bab1c833ccd9fc4577f26103f8 - languageName: node - linkType: hard - -"get-caller-file@npm:^2.0.5": - version: 2.0.5 - resolution: "get-caller-file@npm:2.0.5" - checksum: 10c0/c6c7b60271931fa752aeb92f2b47e355eac1af3a2673f47c9589e8f8a41adc74d45551c1bc57b5e66a80609f10ffb72b6f575e4370d61cc3f7f3aaff01757cde - languageName: node - linkType: hard - -"get-func-name@npm:^2.0.1": - version: 2.0.2 - resolution: "get-func-name@npm:2.0.2" - checksum: 10c0/89830fd07623fa73429a711b9daecdb304386d237c71268007f788f113505ef1d4cc2d0b9680e072c5082490aec9df5d7758bf5ac6f1c37062855e8e3dc0b9df - languageName: node - linkType: hard - -"get-intrinsic@npm:^1.0.2": - version: 1.2.0 - resolution: "get-intrinsic@npm:1.2.0" - dependencies: - function-bind: "npm:^1.1.1" - has: "npm:^1.0.3" - has-symbols: "npm:^1.0.3" - checksum: 10c0/7c564f6b1061e6ca9eb1abab424a2cf80b93e75dcde65229d504e4055aa0ea54f88330e9b75d10e41c72bca881a947e84193b3549a4692d836f304239a178d63 - languageName: node - linkType: hard - -"get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.2.2, get-intrinsic@npm:^1.2.3, get-intrinsic@npm:^1.2.4": - version: 1.2.4 - resolution: "get-intrinsic@npm:1.2.4" - dependencies: - es-errors: "npm:^1.3.0" - function-bind: "npm:^1.1.2" - has-proto: "npm:^1.0.1" - has-symbols: "npm:^1.0.3" - hasown: "npm:^2.0.0" - checksum: 10c0/0a9b82c16696ed6da5e39b1267104475c47e3a9bdbe8b509dfe1710946e38a87be70d759f4bb3cda042d76a41ef47fe769660f3b7c0d1f68750299344ffb15b7 - languageName: node - linkType: hard - -"get-symbol-description@npm:^1.0.2": - version: 1.0.2 - resolution: "get-symbol-description@npm:1.0.2" - dependencies: - call-bind: "npm:^1.0.5" - es-errors: "npm:^1.3.0" - get-intrinsic: "npm:^1.2.4" - checksum: 10c0/867be6d63f5e0eb026cb3b0ef695ec9ecf9310febb041072d2e142f260bd91ced9eeb426b3af98791d1064e324e653424afa6fd1af17dee373bea48ae03162bc - languageName: node - linkType: hard - -"get-tsconfig@npm:^4.7.5": - version: 4.8.0 - resolution: "get-tsconfig@npm:4.8.0" - dependencies: - resolve-pkg-maps: "npm:^1.0.0" - checksum: 10c0/943721c996d9a77351aa7c07956de77baece97f997bd30f3247f46907e4b743f7b9da02c7b3692a36f0884d3724271faeb88ed1c3aca3aba2afe3f27d6c4aeb3 - languageName: node - linkType: hard - -"glob-parent@npm:^5.1.2, glob-parent@npm:~5.1.2": - version: 5.1.2 - resolution: "glob-parent@npm:5.1.2" - dependencies: - is-glob: "npm:^4.0.1" - checksum: 10c0/cab87638e2112bee3f839ef5f6e0765057163d39c66be8ec1602f3823da4692297ad4e972de876ea17c44d652978638d2fd583c6713d0eb6591706825020c9ee - languageName: node - linkType: hard - -"glob-parent@npm:^6.0.2": - version: 6.0.2 - resolution: "glob-parent@npm:6.0.2" - dependencies: - is-glob: "npm:^4.0.3" - checksum: 10c0/317034d88654730230b3f43bb7ad4f7c90257a426e872ea0bf157473ac61c99bf5d205fad8f0185f989be8d2fa6d3c7dce1645d99d545b6ea9089c39f838e7f8 - languageName: node - linkType: hard - -"glob@npm:^10.2.2, glob@npm:^10.3.10": - version: 10.4.5 - resolution: "glob@npm:10.4.5" - dependencies: - foreground-child: "npm:^3.1.0" - jackspeak: "npm:^3.1.2" - minimatch: "npm:^9.0.4" - minipass: "npm:^7.1.2" - package-json-from-dist: "npm:^1.0.0" - path-scurry: "npm:^1.11.1" - bin: - glob: dist/esm/bin.mjs - checksum: 10c0/19a9759ea77b8e3ca0a43c2f07ecddc2ad46216b786bb8f993c445aee80d345925a21e5280c7b7c6c59e860a0154b84e4b2b60321fea92cd3c56b4a7489f160e - languageName: node - linkType: hard - -"glob@npm:^8.1.0": - version: 8.1.0 - resolution: "glob@npm:8.1.0" - dependencies: - fs.realpath: "npm:^1.0.0" - inflight: "npm:^1.0.4" - inherits: "npm:2" - minimatch: "npm:^5.0.1" - once: "npm:^1.3.0" - checksum: 10c0/cb0b5cab17a59c57299376abe5646c7070f8acb89df5595b492dba3bfb43d301a46c01e5695f01154e6553168207cb60d4eaf07d3be4bc3eb9b0457c5c561d0f - languageName: node - linkType: hard - -"globals@npm:^11.1.0": - version: 11.12.0 - resolution: "globals@npm:11.12.0" - checksum: 10c0/758f9f258e7b19226bd8d4af5d3b0dcf7038780fb23d82e6f98932c44e239f884847f1766e8fa9cc5635ccb3204f7fa7314d4408dd4002a5e8ea827b4018f0a1 - languageName: node - linkType: hard - -"globals@npm:^14.0.0": - version: 14.0.0 - resolution: "globals@npm:14.0.0" - checksum: 10c0/b96ff42620c9231ad468d4c58ff42afee7777ee1c963013ff8aabe095a451d0ceeb8dcd8ef4cbd64d2538cef45f787a78ba3a9574f4a634438963e334471302d - languageName: node - linkType: hard - -"globalthis@npm:^1.0.3": - version: 1.0.4 - resolution: "globalthis@npm:1.0.4" - dependencies: - define-properties: "npm:^1.2.1" - gopd: "npm:^1.0.1" - checksum: 10c0/9d156f313af79d80b1566b93e19285f481c591ad6d0d319b4be5e03750d004dde40a39a0f26f7e635f9007a3600802f53ecd85a759b86f109e80a5f705e01846 - languageName: node - linkType: hard - -"globby@npm:^11.1.0": - version: 11.1.0 - resolution: "globby@npm:11.1.0" - dependencies: - array-union: "npm:^2.1.0" - dir-glob: "npm:^3.0.1" - fast-glob: "npm:^3.2.9" - ignore: "npm:^5.2.0" - merge2: "npm:^1.4.1" - slash: "npm:^3.0.0" - checksum: 10c0/b39511b4afe4bd8a7aead3a27c4ade2b9968649abab0a6c28b1a90141b96ca68ca5db1302f7c7bd29eab66bf51e13916b8e0a3d0ac08f75e1e84a39b35691189 - languageName: node - linkType: hard - -"globrex@npm:^0.1.2": - version: 0.1.2 - resolution: "globrex@npm:0.1.2" - checksum: 10c0/a54c029520cf58bda1d8884f72bd49b4cd74e977883268d931fd83bcbd1a9eb96d57c7dbd4ad80148fb9247467ebfb9b215630b2ed7563b2a8de02e1ff7f89d1 - languageName: node - linkType: hard - -"gopd@npm:^1.0.1": - version: 1.0.1 - resolution: "gopd@npm:1.0.1" - dependencies: - get-intrinsic: "npm:^1.1.3" - checksum: 10c0/505c05487f7944c552cee72087bf1567debb470d4355b1335f2c262d218ebbff805cd3715448fe29b4b380bae6912561d0467233e4165830efd28da241418c63 - languageName: node - linkType: hard - -"graceful-fs@npm:^4.2.6": - version: 4.2.10 - resolution: "graceful-fs@npm:4.2.10" - checksum: 10c0/4223a833e38e1d0d2aea630c2433cfb94ddc07dfc11d511dbd6be1d16688c5be848acc31f9a5d0d0ddbfb56d2ee5a6ae0278aceeb0ca6a13f27e06b9956fb952 - languageName: node - linkType: hard - -"graphemer@npm:^1.4.0": - version: 1.4.0 - resolution: "graphemer@npm:1.4.0" - checksum: 10c0/e951259d8cd2e0d196c72ec711add7115d42eb9a8146c8eeda5b8d3ac91e5dd816b9cd68920726d9fd4490368e7ed86e9c423f40db87e2d8dfafa00fa17c3a31 - languageName: node - linkType: hard - -"has-bigints@npm:^1.0.1, has-bigints@npm:^1.0.2": - version: 1.0.2 - resolution: "has-bigints@npm:1.0.2" - checksum: 10c0/724eb1485bfa3cdff6f18d95130aa190561f00b3fcf9f19dc640baf8176b5917c143b81ec2123f8cddb6c05164a198c94b13e1377c497705ccc8e1a80306e83b - languageName: node - linkType: hard - -"has-flag@npm:^3.0.0": - version: 3.0.0 - resolution: "has-flag@npm:3.0.0" - checksum: 10c0/1c6c83b14b8b1b3c25b0727b8ba3e3b647f99e9e6e13eb7322107261de07a4c1be56fc0d45678fc376e09772a3a1642ccdaf8fc69bdf123b6c086598397ce473 - languageName: node - linkType: hard - -"has-flag@npm:^4.0.0": - version: 4.0.0 - resolution: "has-flag@npm:4.0.0" - checksum: 10c0/2e789c61b7888d66993e14e8331449e525ef42aac53c627cc53d1c3334e768bcb6abdc4f5f0de1478a25beec6f0bd62c7549058b7ac53e924040d4f301f02fd1 - languageName: node - linkType: hard - -"has-property-descriptors@npm:^1.0.0, has-property-descriptors@npm:^1.0.2": - version: 1.0.2 - resolution: "has-property-descriptors@npm:1.0.2" - dependencies: - es-define-property: "npm:^1.0.0" - checksum: 10c0/253c1f59e80bb476cf0dde8ff5284505d90c3bdb762983c3514d36414290475fe3fd6f574929d84de2a8eec00d35cf07cb6776205ff32efd7c50719125f00236 - languageName: node - linkType: hard - -"has-proto@npm:^1.0.1, has-proto@npm:^1.0.3": - version: 1.0.3 - resolution: "has-proto@npm:1.0.3" - checksum: 10c0/35a6989f81e9f8022c2f4027f8b48a552de714938765d019dbea6bb547bd49ce5010a3c7c32ec6ddac6e48fc546166a3583b128f5a7add8b058a6d8b4afec205 - languageName: node - linkType: hard - -"has-symbols@npm:^1.0.2, has-symbols@npm:^1.0.3": - version: 1.0.3 - resolution: "has-symbols@npm:1.0.3" - checksum: 10c0/e6922b4345a3f37069cdfe8600febbca791c94988c01af3394d86ca3360b4b93928bbf395859158f88099cb10b19d98e3bbab7c9ff2c1bd09cf665ee90afa2c3 - languageName: node - linkType: hard - -"has-tostringtag@npm:^1.0.0, has-tostringtag@npm:^1.0.2": - version: 1.0.2 - resolution: "has-tostringtag@npm:1.0.2" - dependencies: - has-symbols: "npm:^1.0.3" - checksum: 10c0/a8b166462192bafe3d9b6e420a1d581d93dd867adb61be223a17a8d6dad147aa77a8be32c961bb2f27b3ef893cae8d36f564ab651f5e9b7938ae86f74027c48c - languageName: node - linkType: hard - -"has@npm:^1.0.3": - version: 1.0.3 - resolution: "has@npm:1.0.3" - dependencies: - function-bind: "npm:^1.1.1" - checksum: 10c0/e1da0d2bd109f116b632f27782cf23182b42f14972ca9540e4c5aa7e52647407a0a4a76937334fddcb56befe94a3494825ec22b19b51f5e5507c3153fd1a5e1b - languageName: node - linkType: hard - -"hasown@npm:^2.0.0, hasown@npm:^2.0.1, hasown@npm:^2.0.2": - version: 2.0.2 - resolution: "hasown@npm:2.0.2" - dependencies: - function-bind: "npm:^1.1.2" - checksum: 10c0/3769d434703b8ac66b209a4cca0737519925bbdb61dd887f93a16372b14694c63ff4e797686d87c90f08168e81082248b9b028bad60d4da9e0d1148766f56eb9 - languageName: node - linkType: hard - -"he@npm:^1.2.0": - version: 1.2.0 - resolution: "he@npm:1.2.0" - bin: - he: bin/he - checksum: 10c0/a27d478befe3c8192f006cdd0639a66798979dfa6e2125c6ac582a19a5ebfec62ad83e8382e6036170d873f46e4536a7e795bf8b95bf7c247f4cc0825ccc8c17 - languageName: node - linkType: hard - -"history@npm:5": - version: 5.3.0 - resolution: "history@npm:5.3.0" - dependencies: - "@babel/runtime": "npm:^7.7.6" - checksum: 10c0/812ec839386222d6437bd78d9f05db32e47d105ada0ad8834b32626919dd2fee7a10001bc489510f93a8069d02f118214bd8d42a82f7cf9daf8e84fbcbbb2016 - languageName: node - linkType: hard - -"hoist-non-react-statics@npm:^3.3.1": - version: 3.3.2 - resolution: "hoist-non-react-statics@npm:3.3.2" - dependencies: - react-is: "npm:^16.7.0" - checksum: 10c0/fe0889169e845d738b59b64badf5e55fa3cf20454f9203d1eb088df322d49d4318df774828e789898dcb280e8a5521bb59b3203385662ca5e9218a6ca5820e74 - languageName: node - linkType: hard - -"http-cache-semantics@npm:^4.1.1": - version: 4.1.1 - resolution: "http-cache-semantics@npm:4.1.1" - checksum: 10c0/ce1319b8a382eb3cbb4a37c19f6bfe14e5bb5be3d09079e885e8c513ab2d3cd9214902f8a31c9dc4e37022633ceabfc2d697405deeaf1b8f3552bb4ed996fdfc - languageName: node - linkType: hard - -"http-errors@npm:2.0.0, http-errors@npm:^2.0.0": - version: 2.0.0 - resolution: "http-errors@npm:2.0.0" - dependencies: - depd: "npm:2.0.0" - inherits: "npm:2.0.4" - setprototypeof: "npm:1.2.0" - statuses: "npm:2.0.1" - toidentifier: "npm:1.0.1" - checksum: 10c0/fc6f2715fe188d091274b5ffc8b3657bd85c63e969daa68ccb77afb05b071a4b62841acb7a21e417b5539014dff2ebf9550f0b14a9ff126f2734a7c1387f8e19 - languageName: node - linkType: hard - -"http-proxy-agent@npm:^7.0.0": - version: 7.0.2 - resolution: "http-proxy-agent@npm:7.0.2" - dependencies: - agent-base: "npm:^7.1.0" - debug: "npm:^4.3.4" - checksum: 10c0/4207b06a4580fb85dd6dff521f0abf6db517489e70863dca1a0291daa7f2d3d2d6015a57bd702af068ea5cf9f1f6ff72314f5f5b4228d299c0904135d2aef921 - languageName: node - linkType: hard - -"https-proxy-agent@npm:^7.0.1": - version: 7.0.5 - resolution: "https-proxy-agent@npm:7.0.5" - dependencies: - agent-base: "npm:^7.0.2" - debug: "npm:4" - checksum: 10c0/2490e3acec397abeb88807db52cac59102d5ed758feee6df6112ab3ccd8325e8a1ce8bce6f4b66e5470eca102d31e425ace904242e4fa28dbe0c59c4bafa7b2c - languageName: node - linkType: hard - -"iconv-lite@npm:0.5.2": - version: 0.5.2 - resolution: "iconv-lite@npm:0.5.2" - dependencies: - safer-buffer: "npm:>= 2.1.2 < 3" - checksum: 10c0/6c51c9996fe360b03f501c0f76f122f007c6a9be924cfdf0b007044cfbcdeeb9c9decb5435465934dbd3804f37e67fdc2fb3ed8c9948464299165776541dff25 - languageName: node - linkType: hard - -"iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.2": - version: 0.6.3 - resolution: "iconv-lite@npm:0.6.3" - dependencies: - safer-buffer: "npm:>= 2.1.2 < 3.0.0" - checksum: 10c0/98102bc66b33fcf5ac044099d1257ba0b7ad5e3ccd3221f34dd508ab4070edff183276221684e1e0555b145fce0850c9f7d2b60a9fcac50fbb4ea0d6e845a3b1 - languageName: node - linkType: hard - -"ignore@npm:^5.1.1, ignore@npm:^5.3.1": - version: 5.3.2 - resolution: "ignore@npm:5.3.2" - checksum: 10c0/f9f652c957983634ded1e7f02da3b559a0d4cc210fca3792cb67f1b153623c9c42efdc1c4121af171e295444459fc4a9201101fb041b1104a3c000bccb188337 - languageName: node - linkType: hard - -"ignore@npm:^5.2.0": - version: 5.2.4 - resolution: "ignore@npm:5.2.4" - checksum: 10c0/7c7cd90edd9fea6e037f9b9da4b01bf0a86b198ce78345f9bbd983929d68ff14830be31111edc5d70c264921f4962404d75b7262b4d9cc3bc12381eccbd03096 - languageName: node - linkType: hard - -"import-fresh@npm:^3.2.1": - version: 3.3.0 - resolution: "import-fresh@npm:3.3.0" - dependencies: - parent-module: "npm:^1.0.0" - resolve-from: "npm:^4.0.0" - checksum: 10c0/7f882953aa6b740d1f0e384d0547158bc86efbf2eea0f1483b8900a6f65c5a5123c2cf09b0d542cc419d0b98a759ecaeb394237e97ea427f2da221dc3cd80cc3 - languageName: node - linkType: hard - -"imurmurhash@npm:^0.1.4": - version: 0.1.4 - resolution: "imurmurhash@npm:0.1.4" - checksum: 10c0/8b51313850dd33605c6c9d3fd9638b714f4c4c40250cff658209f30d40da60f78992fb2df5dabee4acf589a6a82bbc79ad5486550754bd9ec4e3fc0d4a57d6a6 - languageName: node - linkType: hard - -"indent-string@npm:^4.0.0": - version: 4.0.0 - resolution: "indent-string@npm:4.0.0" - checksum: 10c0/1e1904ddb0cb3d6cce7cd09e27a90184908b7a5d5c21b92e232c93579d314f0b83c246ffb035493d0504b1e9147ba2c9b21df0030f48673fba0496ecd698161f - languageName: node - linkType: hard - -"inflight@npm:^1.0.4": - version: 1.0.6 - resolution: "inflight@npm:1.0.6" - dependencies: - once: "npm:^1.3.0" - wrappy: "npm:1" - checksum: 10c0/7faca22584600a9dc5b9fca2cd5feb7135ac8c935449837b315676b4c90aa4f391ec4f42240178244b5a34e8bede1948627fda392ca3191522fc46b34e985ab2 - languageName: node - linkType: hard - -"inherits@npm:2, inherits@npm:2.0.4": - version: 2.0.4 - resolution: "inherits@npm:2.0.4" - checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2 - languageName: node - linkType: hard - -"internal-slot@npm:^1.0.4, internal-slot@npm:^1.0.7": - version: 1.0.7 - resolution: "internal-slot@npm:1.0.7" - dependencies: - es-errors: "npm:^1.3.0" - hasown: "npm:^2.0.0" - side-channel: "npm:^1.0.4" - checksum: 10c0/f8b294a4e6ea3855fc59551bbf35f2b832cf01fd5e6e2a97f5c201a071cc09b49048f856e484b67a6c721da5e55736c5b6ddafaf19e2dbeb4a3ff1821680de6c - languageName: node - linkType: hard - -"ip-address@npm:^9.0.5": - version: 9.0.5 - resolution: "ip-address@npm:9.0.5" - dependencies: - jsbn: "npm:1.1.0" - sprintf-js: "npm:^1.1.3" - checksum: 10c0/331cd07fafcb3b24100613e4b53e1a2b4feab11e671e655d46dc09ee233da5011284d09ca40c4ecbdfe1d0004f462958675c224a804259f2f78d2465a87824bc - languageName: node - linkType: hard - -"ipaddr.js@npm:1.9.1": - version: 1.9.1 - resolution: "ipaddr.js@npm:1.9.1" - checksum: 10c0/0486e775047971d3fdb5fb4f063829bac45af299ae0b82dcf3afa2145338e08290563a2a70f34b732d795ecc8311902e541a8530eeb30d75860a78ff4e94ce2a - languageName: node - linkType: hard - -"is-arguments@npm:^1.1.1": - version: 1.1.1 - resolution: "is-arguments@npm:1.1.1" - dependencies: - call-bind: "npm:^1.0.2" - has-tostringtag: "npm:^1.0.0" - checksum: 10c0/5ff1f341ee4475350adfc14b2328b38962564b7c2076be2f5bac7bd9b61779efba99b9f844a7b82ba7654adccf8e8eb19d1bb0cc6d1c1a085e498f6793d4328f - languageName: node - linkType: hard - -"is-array-buffer@npm:^3.0.2, is-array-buffer@npm:^3.0.4": - version: 3.0.4 - resolution: "is-array-buffer@npm:3.0.4" - dependencies: - call-bind: "npm:^1.0.2" - get-intrinsic: "npm:^1.2.1" - checksum: 10c0/42a49d006cc6130bc5424eae113e948c146f31f9d24460fc0958f855d9d810e6fd2e4519bf19aab75179af9c298ea6092459d8cafdec523cd19e529b26eab860 - languageName: node - linkType: hard - -"is-arrayish@npm:^0.2.1": - version: 0.2.1 - resolution: "is-arrayish@npm:0.2.1" - checksum: 10c0/e7fb686a739068bb70f860b39b67afc62acc62e36bb61c5f965768abce1873b379c563e61dd2adad96ebb7edf6651111b385e490cf508378959b0ed4cac4e729 - languageName: node - linkType: hard - -"is-async-function@npm:^2.0.0": - version: 2.0.0 - resolution: "is-async-function@npm:2.0.0" - dependencies: - has-tostringtag: "npm:^1.0.0" - checksum: 10c0/787bc931576aad525d751fc5ce211960fe91e49ac84a5c22d6ae0bc9541945fbc3f686dc590c3175722ce4f6d7b798a93f6f8ff4847fdb2199aea6f4baf5d668 - languageName: node - linkType: hard - -"is-bigint@npm:^1.0.1": - version: 1.0.4 - resolution: "is-bigint@npm:1.0.4" - dependencies: - has-bigints: "npm:^1.0.1" - checksum: 10c0/eb9c88e418a0d195ca545aff2b715c9903d9b0a5033bc5922fec600eb0c3d7b1ee7f882dbf2e0d5a6e694e42391be3683e4368737bd3c4a77f8ac293e7773696 - languageName: node - linkType: hard - -"is-binary-path@npm:~2.1.0": - version: 2.1.0 - resolution: "is-binary-path@npm:2.1.0" - dependencies: - binary-extensions: "npm:^2.0.0" - checksum: 10c0/a16eaee59ae2b315ba36fad5c5dcaf8e49c3e27318f8ab8fa3cdb8772bf559c8d1ba750a589c2ccb096113bb64497084361a25960899cb6172a6925ab6123d38 - languageName: node - linkType: hard - -"is-boolean-object@npm:^1.1.0": - version: 1.1.2 - resolution: "is-boolean-object@npm:1.1.2" - dependencies: - call-bind: "npm:^1.0.2" - has-tostringtag: "npm:^1.0.0" - checksum: 10c0/6090587f8a8a8534c0f816da868bc94f32810f08807aa72fa7e79f7e11c466d281486ffe7a788178809c2aa71fe3e700b167fe80dd96dad68026bfff8ebf39f7 - languageName: node - linkType: hard - -"is-callable@npm:^1.1.3, is-callable@npm:^1.1.4, is-callable@npm:^1.2.7": - version: 1.2.7 - resolution: "is-callable@npm:1.2.7" - checksum: 10c0/ceebaeb9d92e8adee604076971dd6000d38d6afc40bb843ea8e45c5579b57671c3f3b50d7f04869618242c6cee08d1b67806a8cb8edaaaf7c0748b3720d6066f - languageName: node - linkType: hard - -"is-core-module@npm:^2.13.0, is-core-module@npm:^2.15.1": - version: 2.15.1 - resolution: "is-core-module@npm:2.15.1" - dependencies: - hasown: "npm:^2.0.2" - checksum: 10c0/53432f10c69c40bfd2fa8914133a68709ff9498c86c3bf5fca3cdf3145a56fd2168cbf4a43b29843a6202a120a5f9c5ffba0a4322e1e3441739bc0b641682612 - languageName: node - linkType: hard - -"is-core-module@npm:^2.9.0": - version: 2.11.0 - resolution: "is-core-module@npm:2.11.0" - dependencies: - has: "npm:^1.0.3" - checksum: 10c0/fd8f78ef4e243c295deafa809f89381d89aff5aaf38bb63266b17ee6e34b6a051baa5bdc2365456863336d56af6a59a4c1df1256b4eff7d6b4afac618586b004 - languageName: node - linkType: hard - -"is-data-view@npm:^1.0.1": - version: 1.0.1 - resolution: "is-data-view@npm:1.0.1" - dependencies: - is-typed-array: "npm:^1.1.13" - checksum: 10c0/a3e6ec84efe303da859107aed9b970e018e2bee7ffcb48e2f8096921a493608134240e672a2072577e5f23a729846241d9634806e8a0e51d9129c56d5f65442d - languageName: node - linkType: hard - -"is-date-object@npm:^1.0.1, is-date-object@npm:^1.0.5": - version: 1.0.5 - resolution: "is-date-object@npm:1.0.5" - dependencies: - has-tostringtag: "npm:^1.0.0" - checksum: 10c0/eed21e5dcc619c48ccef804dfc83a739dbb2abee6ca202838ee1bd5f760fe8d8a93444f0d49012ad19bb7c006186e2884a1b92f6e1c056da7fd23d0a9ad5992e - languageName: node - linkType: hard - -"is-extglob@npm:^2.1.1": - version: 2.1.1 - resolution: "is-extglob@npm:2.1.1" - checksum: 10c0/5487da35691fbc339700bbb2730430b07777a3c21b9ebaecb3072512dfd7b4ba78ac2381a87e8d78d20ea08affb3f1971b4af629173a6bf435ff8a4c47747912 - languageName: node - linkType: hard - -"is-finalizationregistry@npm:^1.0.2": - version: 1.0.2 - resolution: "is-finalizationregistry@npm:1.0.2" - dependencies: - call-bind: "npm:^1.0.2" - checksum: 10c0/81caecc984d27b1a35c68741156fc651fb1fa5e3e6710d21410abc527eb226d400c0943a167922b2e920f6b3e58b0dede9aa795882b038b85f50b3a4b877db86 - languageName: node - linkType: hard - -"is-fullwidth-code-point@npm:^3.0.0": - version: 3.0.0 - resolution: "is-fullwidth-code-point@npm:3.0.0" - checksum: 10c0/bb11d825e049f38e04c06373a8d72782eee0205bda9d908cc550ccb3c59b99d750ff9537982e01733c1c94a58e35400661f57042158ff5e8f3e90cf936daf0fc - languageName: node - linkType: hard - -"is-generator-function@npm:^1.0.10": - version: 1.0.10 - resolution: "is-generator-function@npm:1.0.10" - dependencies: - has-tostringtag: "npm:^1.0.0" - checksum: 10c0/df03514df01a6098945b5a0cfa1abff715807c8e72f57c49a0686ad54b3b74d394e2d8714e6f709a71eb00c9630d48e73ca1796c1ccc84ac95092c1fecc0d98b - languageName: node - linkType: hard - -"is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3, is-glob@npm:~4.0.1": - version: 4.0.3 - resolution: "is-glob@npm:4.0.3" - dependencies: - is-extglob: "npm:^2.1.1" - checksum: 10c0/17fb4014e22be3bbecea9b2e3a76e9e34ff645466be702f1693e8f1ee1adac84710d0be0bd9f967d6354036fd51ab7c2741d954d6e91dae6bb69714de92c197a - languageName: node - linkType: hard - -"is-lambda@npm:^1.0.1": - version: 1.0.1 - resolution: "is-lambda@npm:1.0.1" - checksum: 10c0/85fee098ae62ba6f1e24cf22678805473c7afd0fb3978a3aa260e354cb7bcb3a5806cf0a98403188465efedec41ab4348e8e4e79305d409601323855b3839d4d - languageName: node - linkType: hard - -"is-map@npm:^2.0.2, is-map@npm:^2.0.3": - version: 2.0.3 - resolution: "is-map@npm:2.0.3" - checksum: 10c0/2c4d431b74e00fdda7162cd8e4b763d6f6f217edf97d4f8538b94b8702b150610e2c64961340015fe8df5b1fcee33ccd2e9b62619c4a8a3a155f8de6d6d355fc - languageName: node - linkType: hard - -"is-negative-zero@npm:^2.0.3": - version: 2.0.3 - resolution: "is-negative-zero@npm:2.0.3" - checksum: 10c0/bcdcf6b8b9714063ffcfa9929c575ac69bfdabb8f4574ff557dfc086df2836cf07e3906f5bbc4f2a5c12f8f3ba56af640c843cdfc74da8caed86c7c7d66fd08e - languageName: node - linkType: hard - -"is-number-object@npm:^1.0.4": - version: 1.0.7 - resolution: "is-number-object@npm:1.0.7" - dependencies: - has-tostringtag: "npm:^1.0.0" - checksum: 10c0/aad266da1e530f1804a2b7bd2e874b4869f71c98590b3964f9d06cc9869b18f8d1f4778f838ecd2a11011bce20aeecb53cb269ba916209b79c24580416b74b1b - languageName: node - linkType: hard - -"is-number@npm:^7.0.0": - version: 7.0.0 - resolution: "is-number@npm:7.0.0" - checksum: 10c0/b4686d0d3053146095ccd45346461bc8e53b80aeb7671cc52a4de02dbbf7dc0d1d2a986e2fe4ae206984b4d34ef37e8b795ebc4f4295c978373e6575e295d811 - languageName: node - linkType: hard - -"is-path-inside@npm:^3.0.3": - version: 3.0.3 - resolution: "is-path-inside@npm:3.0.3" - checksum: 10c0/cf7d4ac35fb96bab6a1d2c3598fe5ebb29aafb52c0aaa482b5a3ed9d8ba3edc11631e3ec2637660c44b3ce0e61a08d54946e8af30dec0b60a7c27296c68ffd05 - languageName: node - linkType: hard - -"is-plain-obj@npm:^2.1.0": - version: 2.1.0 - resolution: "is-plain-obj@npm:2.1.0" - checksum: 10c0/e5c9814cdaa627a9ad0a0964ded0e0491bfd9ace405c49a5d63c88b30a162f1512c069d5b80997893c4d0181eadc3fed02b4ab4b81059aba5620bfcdfdeb9c53 - languageName: node - linkType: hard - -"is-promise@npm:4.0.0": - version: 4.0.0 - resolution: "is-promise@npm:4.0.0" - checksum: 10c0/ebd5c672d73db781ab33ccb155fb9969d6028e37414d609b115cc534654c91ccd061821d5b987eefaa97cf4c62f0b909bb2f04db88306de26e91bfe8ddc01503 - languageName: node - linkType: hard - -"is-regex@npm:^1.1.4": - version: 1.1.4 - resolution: "is-regex@npm:1.1.4" - dependencies: - call-bind: "npm:^1.0.2" - has-tostringtag: "npm:^1.0.0" - checksum: 10c0/bb72aae604a69eafd4a82a93002058c416ace8cde95873589a97fc5dac96a6c6c78a9977d487b7b95426a8f5073969124dd228f043f9f604f041f32fcc465fc1 - languageName: node - linkType: hard - -"is-set@npm:^2.0.2, is-set@npm:^2.0.3": - version: 2.0.3 - resolution: "is-set@npm:2.0.3" - checksum: 10c0/f73732e13f099b2dc879c2a12341cfc22ccaca8dd504e6edae26484bd5707a35d503fba5b4daad530a9b088ced1ae6c9d8200fd92e09b428fe14ea79ce8080b7 - languageName: node - linkType: hard - -"is-shared-array-buffer@npm:^1.0.2, is-shared-array-buffer@npm:^1.0.3": - version: 1.0.3 - resolution: "is-shared-array-buffer@npm:1.0.3" - dependencies: - call-bind: "npm:^1.0.7" - checksum: 10c0/adc11ab0acbc934a7b9e5e9d6c588d4ec6682f6fea8cda5180721704fa32927582ede5b123349e32517fdadd07958973d24716c80e7ab198970c47acc09e59c7 - languageName: node - linkType: hard - -"is-string@npm:^1.0.5, is-string@npm:^1.0.7": - version: 1.0.7 - resolution: "is-string@npm:1.0.7" - dependencies: - has-tostringtag: "npm:^1.0.0" - checksum: 10c0/905f805cbc6eedfa678aaa103ab7f626aac9ebbdc8737abb5243acaa61d9820f8edc5819106b8fcd1839e33db21de9f0116ae20de380c8382d16dc2a601921f6 - languageName: node - linkType: hard - -"is-symbol@npm:^1.0.2, is-symbol@npm:^1.0.3": - version: 1.0.4 - resolution: "is-symbol@npm:1.0.4" - dependencies: - has-symbols: "npm:^1.0.2" - checksum: 10c0/9381dd015f7c8906154dbcbf93fad769de16b4b961edc94f88d26eb8c555935caa23af88bda0c93a18e65560f6d7cca0fd5a3f8a8e1df6f1abbb9bead4502ef7 - languageName: node - linkType: hard - -"is-typed-array@npm:^1.1.13": - version: 1.1.13 - resolution: "is-typed-array@npm:1.1.13" - dependencies: - which-typed-array: "npm:^1.1.14" - checksum: 10c0/fa5cb97d4a80e52c2cc8ed3778e39f175a1a2ae4ddf3adae3187d69586a1fd57cfa0b095db31f66aa90331e9e3da79184cea9c6abdcd1abc722dc3c3edd51cca - languageName: node - linkType: hard - -"is-unicode-supported@npm:^0.1.0": - version: 0.1.0 - resolution: "is-unicode-supported@npm:0.1.0" - checksum: 10c0/00cbe3455c3756be68d2542c416cab888aebd5012781d6819749fefb15162ff23e38501fe681b3d751c73e8ff561ac09a5293eba6f58fdf0178462ce6dcb3453 - languageName: node - linkType: hard - -"is-weakmap@npm:^2.0.2": - version: 2.0.2 - resolution: "is-weakmap@npm:2.0.2" - checksum: 10c0/443c35bb86d5e6cc5929cd9c75a4024bb0fff9586ed50b092f94e700b89c43a33b186b76dbc6d54f3d3d09ece689ab38dcdc1af6a482cbe79c0f2da0a17f1299 - languageName: node - linkType: hard - -"is-weakref@npm:^1.0.2": - version: 1.0.2 - resolution: "is-weakref@npm:1.0.2" - dependencies: - call-bind: "npm:^1.0.2" - checksum: 10c0/1545c5d172cb690c392f2136c23eec07d8d78a7f57d0e41f10078aa4f5daf5d7f57b6513a67514ab4f073275ad00c9822fc8935e00229d0a2089e1c02685d4b1 - languageName: node - linkType: hard - -"is-weakset@npm:^2.0.3": - version: 2.0.3 - resolution: "is-weakset@npm:2.0.3" - dependencies: - call-bind: "npm:^1.0.7" - get-intrinsic: "npm:^1.2.4" - checksum: 10c0/8ad6141b6a400e7ce7c7442a13928c676d07b1f315ab77d9912920bf5f4170622f43126f111615788f26c3b1871158a6797c862233124507db0bcc33a9537d1a - languageName: node - linkType: hard - -"isarray@npm:0.0.1": - version: 0.0.1 - resolution: "isarray@npm:0.0.1" - checksum: 10c0/ed1e62da617f71fe348907c71743b5ed550448b455f8d269f89a7c7ddb8ae6e962de3dab6a74a237b06f5eb7f6ece7a45ada8ce96d87fe972926530f91ae3311 - languageName: node - linkType: hard - -"isarray@npm:^2.0.5": - version: 2.0.5 - resolution: "isarray@npm:2.0.5" - checksum: 10c0/4199f14a7a13da2177c66c31080008b7124331956f47bca57dd0b6ea9f11687aa25e565a2c7a2b519bc86988d10398e3049a1f5df13c9f6b7664154690ae79fd - languageName: node - linkType: hard - -"isexe@npm:^2.0.0": - version: 2.0.0 - resolution: "isexe@npm:2.0.0" - checksum: 10c0/228cfa503fadc2c31596ab06ed6aa82c9976eec2bfd83397e7eaf06d0ccf42cd1dfd6743bf9aeb01aebd4156d009994c5f76ea898d2832c1fe342da923ca457d - languageName: node - linkType: hard - -"isexe@npm:^3.1.1": - version: 3.1.1 - resolution: "isexe@npm:3.1.1" - checksum: 10c0/9ec257654093443eb0a528a9c8cbba9c0ca7616ccb40abd6dde7202734d96bb86e4ac0d764f0f8cd965856aacbff2f4ce23e730dc19dfb41e3b0d865ca6fdcc7 - languageName: node - linkType: hard - -"iterator.prototype@npm:^1.1.2": - version: 1.1.2 - resolution: "iterator.prototype@npm:1.1.2" - dependencies: - define-properties: "npm:^1.2.1" - get-intrinsic: "npm:^1.2.1" - has-symbols: "npm:^1.0.3" - reflect.getprototypeof: "npm:^1.0.4" - set-function-name: "npm:^2.0.1" - checksum: 10c0/a32151326095e916f306990d909f6bbf23e3221999a18ba686419535dcd1749b10ded505e89334b77dc4c7a58a8508978f0eb16c2c8573e6d412eb7eb894ea79 - languageName: node - linkType: hard - -"jackspeak@npm:^3.1.2": - version: 3.4.3 - resolution: "jackspeak@npm:3.4.3" - dependencies: - "@isaacs/cliui": "npm:^8.0.2" - "@pkgjs/parseargs": "npm:^0.11.0" - dependenciesMeta: - "@pkgjs/parseargs": - optional: true - checksum: 10c0/6acc10d139eaefdbe04d2f679e6191b3abf073f111edf10b1de5302c97ec93fffeb2fdd8681ed17f16268aa9dd4f8c588ed9d1d3bffbbfa6e8bf897cbb3149b9 - languageName: node - linkType: hard - -"js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": - version: 4.0.0 - resolution: "js-tokens@npm:4.0.0" - checksum: 10c0/e248708d377aa058eacf2037b07ded847790e6de892bbad3dac0abba2e759cb9f121b00099a65195616badcb6eca8d14d975cb3e89eb1cfda644756402c8aeed - languageName: node - linkType: hard - -"js-yaml@npm:^4.1.0": - version: 4.1.0 - resolution: "js-yaml@npm:4.1.0" - dependencies: - argparse: "npm:^2.0.1" - bin: - js-yaml: bin/js-yaml.js - checksum: 10c0/184a24b4eaacfce40ad9074c64fd42ac83cf74d8c8cd137718d456ced75051229e5061b8633c3366b8aada17945a7a356b337828c19da92b51ae62126575018f - languageName: node - linkType: hard - -"jsbn@npm:1.1.0": - version: 1.1.0 - resolution: "jsbn@npm:1.1.0" - checksum: 10c0/4f907fb78d7b712e11dea8c165fe0921f81a657d3443dde75359ed52eb2b5d33ce6773d97985a089f09a65edd80b11cb75c767b57ba47391fee4c969f7215c96 - languageName: node - linkType: hard - -"jsesc@npm:^2.5.1": - version: 2.5.2 - resolution: "jsesc@npm:2.5.2" - bin: - jsesc: bin/jsesc - checksum: 10c0/dbf59312e0ebf2b4405ef413ec2b25abb5f8f4d9bc5fb8d9f90381622ebca5f2af6a6aa9a8578f65903f9e33990a6dc798edd0ce5586894bf0e9e31803a1de88 - languageName: node - linkType: hard - -"jsesc@npm:~0.5.0": - version: 0.5.0 - resolution: "jsesc@npm:0.5.0" - bin: - jsesc: bin/jsesc - checksum: 10c0/f93792440ae1d80f091b65f8ceddf8e55c4bb7f1a09dee5dcbdb0db5612c55c0f6045625aa6b7e8edb2e0a4feabd80ee48616dbe2d37055573a84db3d24f96d9 - languageName: node - linkType: hard - -"json-buffer@npm:3.0.1": - version: 3.0.1 - resolution: "json-buffer@npm:3.0.1" - checksum: 10c0/0d1c91569d9588e7eef2b49b59851f297f3ab93c7b35c7c221e288099322be6b562767d11e4821da500f3219542b9afd2e54c5dc573107c1126ed1080f8e96d7 - languageName: node - linkType: hard - -"json-parse-even-better-errors@npm:^2.3.0": - version: 2.3.1 - resolution: "json-parse-even-better-errors@npm:2.3.1" - checksum: 10c0/140932564c8f0b88455432e0f33c4cb4086b8868e37524e07e723f4eaedb9425bdc2bafd71bd1d9765bd15fd1e2d126972bc83990f55c467168c228c24d665f3 - languageName: node - linkType: hard - -"json-schema-traverse@npm:^0.4.1": - version: 0.4.1 - resolution: "json-schema-traverse@npm:0.4.1" - checksum: 10c0/108fa90d4cc6f08243aedc6da16c408daf81793bf903e9fd5ab21983cda433d5d2da49e40711da016289465ec2e62e0324dcdfbc06275a607fe3233fde4942ce - languageName: node - linkType: hard - -"json-schema-traverse@npm:^1.0.0": - version: 1.0.0 - resolution: "json-schema-traverse@npm:1.0.0" - checksum: 10c0/71e30015d7f3d6dc1c316d6298047c8ef98a06d31ad064919976583eb61e1018a60a0067338f0f79cabc00d84af3fcc489bd48ce8a46ea165d9541ba17fb30c6 - languageName: node - linkType: hard - -"json-stable-stringify-without-jsonify@npm:^1.0.1": - version: 1.0.1 - resolution: "json-stable-stringify-without-jsonify@npm:1.0.1" - checksum: 10c0/cb168b61fd4de83e58d09aaa6425ef71001bae30d260e2c57e7d09a5fd82223e2f22a042dedaab8db23b7d9ae46854b08bb1f91675a8be11c5cffebef5fb66a5 - languageName: node - linkType: hard - -"json5@npm:^1.0.2": - version: 1.0.2 - resolution: "json5@npm:1.0.2" - dependencies: - minimist: "npm:^1.2.0" - bin: - json5: lib/cli.js - checksum: 10c0/9ee316bf21f000b00752e6c2a3b79ecf5324515a5c60ee88983a1910a45426b643a4f3461657586e8aeca87aaf96f0a519b0516d2ae527a6c3e7eed80f68717f - languageName: node - linkType: hard - -"json5@npm:^2.2.2, json5@npm:^2.2.3": - version: 2.2.3 - resolution: "json5@npm:2.2.3" - bin: - json5: lib/cli.js - checksum: 10c0/5a04eed94810fa55c5ea138b2f7a5c12b97c3750bc63d11e511dcecbfef758003861522a070c2272764ee0f4e3e323862f386945aeb5b85b87ee43f084ba586c - languageName: node - linkType: hard - -"jsx-ast-utils@npm:^2.4.1 || ^3.0.0, jsx-ast-utils@npm:^3.3.5": - version: 3.3.5 - resolution: "jsx-ast-utils@npm:3.3.5" - dependencies: - array-includes: "npm:^3.1.6" - array.prototype.flat: "npm:^1.3.1" - object.assign: "npm:^4.1.4" - object.values: "npm:^1.1.6" - checksum: 10c0/a32679e9cb55469cb6d8bbc863f7d631b2c98b7fc7bf172629261751a6e7bc8da6ae374ddb74d5fbd8b06cf0eb4572287b259813d92b36e384024ed35e4c13e1 - languageName: node - linkType: hard - -"just-extend@npm:^4.0.2": - version: 4.2.1 - resolution: "just-extend@npm:4.2.1" - checksum: 10c0/ab01b807ae064eee016001df7e958fab9d878a6e2e32119f5f5a94e986daca9d940aa6176889f04c2658e6e3edd75000d7bab1a2376d473ccb20ae571f4b8cbc - languageName: node - linkType: hard - -"keyv@npm:^4.5.4": - version: 4.5.4 - resolution: "keyv@npm:4.5.4" - dependencies: - json-buffer: "npm:3.0.1" - checksum: 10c0/aa52f3c5e18e16bb6324876bb8b59dd02acf782a4b789c7b2ae21107fab95fab3890ed448d4f8dba80ce05391eeac4bfabb4f02a20221342982f806fa2cf271e - languageName: node - linkType: hard - -"language-subtag-registry@npm:^0.3.20": - version: 0.3.23 - resolution: "language-subtag-registry@npm:0.3.23" - checksum: 10c0/e9b05190421d2cd36dd6c95c28673019c927947cb6d94f40ba7e77a838629ee9675c94accf897fbebb07923187deb843b8fbb8935762df6edafe6c28dcb0b86c - languageName: node - linkType: hard - -"language-tags@npm:^1.0.9": - version: 1.0.9 - resolution: "language-tags@npm:1.0.9" - dependencies: - language-subtag-registry: "npm:^0.3.20" - checksum: 10c0/9ab911213c4bd8bd583c850201c17794e52cb0660d1ab6e32558aadc8324abebf6844e46f92b80a5d600d0fbba7eface2c207bfaf270a1c7fd539e4c3a880bff - languageName: node - linkType: hard - -"levn@npm:^0.4.1": - version: 0.4.1 - resolution: "levn@npm:0.4.1" - dependencies: - prelude-ls: "npm:^1.2.1" - type-check: "npm:~0.4.0" - checksum: 10c0/effb03cad7c89dfa5bd4f6989364bfc79994c2042ec5966cb9b95990e2edee5cd8969ddf42616a0373ac49fac1403437deaf6e9050fbbaa3546093a59b9ac94e - languageName: node - linkType: hard - -"lines-and-columns@npm:^1.1.6": - version: 1.2.4 - resolution: "lines-and-columns@npm:1.2.4" - checksum: 10c0/3da6ee62d4cd9f03f5dc90b4df2540fb85b352081bee77fe4bbcd12c9000ead7f35e0a38b8d09a9bb99b13223446dd8689ff3c4959807620726d788701a83d2d - languageName: node - linkType: hard - -"locate-path@npm:^6.0.0": - version: 6.0.0 - resolution: "locate-path@npm:6.0.0" - dependencies: - p-locate: "npm:^5.0.0" - checksum: 10c0/d3972ab70dfe58ce620e64265f90162d247e87159b6126b01314dd67be43d50e96a50b517bce2d9452a79409c7614054c277b5232377de50416564a77ac7aad3 - languageName: node - linkType: hard - -"lodash-es@npm:^4.17.21": - version: 4.17.21 - resolution: "lodash-es@npm:4.17.21" - checksum: 10c0/fb407355f7e6cd523a9383e76e6b455321f0f153a6c9625e21a8827d10c54c2a2341bd2ae8d034358b60e07325e1330c14c224ff582d04612a46a4f0479ff2f2 - languageName: node - linkType: hard - -"lodash.debounce@npm:^4.0.8": - version: 4.0.8 - resolution: "lodash.debounce@npm:4.0.8" - checksum: 10c0/762998a63e095412b6099b8290903e0a8ddcb353ac6e2e0f2d7e7d03abd4275fe3c689d88960eb90b0dde4f177554d51a690f22a343932ecbc50a5d111849987 - languageName: node - linkType: hard - -"lodash.get@npm:^4.4.2": - version: 4.4.2 - resolution: "lodash.get@npm:4.4.2" - checksum: 10c0/48f40d471a1654397ed41685495acb31498d5ed696185ac8973daef424a749ca0c7871bf7b665d5c14f5cc479394479e0307e781f61d5573831769593411be6e - languageName: node - linkType: hard - -"lodash.merge@npm:^4.6.2": - version: 4.6.2 - resolution: "lodash.merge@npm:4.6.2" - checksum: 10c0/402fa16a1edd7538de5b5903a90228aa48eb5533986ba7fa26606a49db2572bf414ff73a2c9f5d5fd36b31c46a5d5c7e1527749c07cbcf965ccff5fbdf32c506 - languageName: node - linkType: hard - -"lodash@npm:^4.17.21": - version: 4.17.21 - resolution: "lodash@npm:4.17.21" - checksum: 10c0/d8cbea072bb08655bb4c989da418994b073a608dffa608b09ac04b43a791b12aeae7cd7ad919aa4c925f33b48490b5cfe6c1f71d827956071dae2e7bb3a6b74c - languageName: node - linkType: hard - -"log-symbols@npm:^4.1.0": - version: 4.1.0 - resolution: "log-symbols@npm:4.1.0" - dependencies: - chalk: "npm:^4.1.0" - is-unicode-supported: "npm:^0.1.0" - checksum: 10c0/67f445a9ffa76db1989d0fa98586e5bc2fd5247260dafb8ad93d9f0ccd5896d53fb830b0e54dade5ad838b9de2006c826831a3c528913093af20dff8bd24aca6 - languageName: node - linkType: hard - -"loose-envify@npm:^1.1.0, loose-envify@npm:^1.4.0": - version: 1.4.0 - resolution: "loose-envify@npm:1.4.0" - dependencies: - js-tokens: "npm:^3.0.0 || ^4.0.0" - bin: - loose-envify: cli.js - checksum: 10c0/655d110220983c1a4b9c0c679a2e8016d4b67f6e9c7b5435ff5979ecdb20d0813f4dec0a08674fcbdd4846a3f07edbb50a36811fd37930b94aaa0d9daceb017e - languageName: node - linkType: hard - -"loupe@npm:^3.1.0": - version: 3.1.1 - resolution: "loupe@npm:3.1.1" - dependencies: - get-func-name: "npm:^2.0.1" - checksum: 10c0/99f88badc47e894016df0c403de846fedfea61154aadabbf776c8428dd59e8d8378007135d385d737de32ae47980af07d22ba7bec5ef7beebd721de9baa0a0af - languageName: node - linkType: hard - -"lowdb@npm:^7.0.1": - version: 7.0.1 - resolution: "lowdb@npm:7.0.1" - dependencies: - steno: "npm:^4.0.2" - checksum: 10c0/d61f554d6a507419772b07c91982bb72a0d3976dddef83e1d58fb24ff9049398962ec2c1917b3a863aedb46559081e0cd208d032ff3c43b1d59f41be9f4f4022 - languageName: node - linkType: hard - -"lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0": - version: 10.4.3 - resolution: "lru-cache@npm:10.4.3" - checksum: 10c0/ebd04fbca961e6c1d6c0af3799adcc966a1babe798f685bb84e6599266599cd95d94630b10262f5424539bc4640107e8a33aa28585374abf561d30d16f4b39fb - languageName: node - linkType: hard - -"lru-cache@npm:^5.1.1": - version: 5.1.1 - resolution: "lru-cache@npm:5.1.1" - dependencies: - yallist: "npm:^3.0.2" - checksum: 10c0/89b2ef2ef45f543011e38737b8a8622a2f8998cddf0e5437174ef8f1f70a8b9d14a918ab3e232cb3ba343b7abddffa667f0b59075b2b80e6b4d63c3de6127482 - languageName: node - linkType: hard - -"lru-cache@npm:^6.0.0": - version: 6.0.0 - resolution: "lru-cache@npm:6.0.0" - dependencies: - yallist: "npm:^4.0.0" - checksum: 10c0/cb53e582785c48187d7a188d3379c181b5ca2a9c78d2bce3e7dee36f32761d1c42983da3fe12b55cb74e1779fa94cdc2e5367c028a9b35317184ede0c07a30a9 - languageName: node - linkType: hard - -"make-fetch-happen@npm:^13.0.0": - version: 13.0.1 - resolution: "make-fetch-happen@npm:13.0.1" - dependencies: - "@npmcli/agent": "npm:^2.0.0" - cacache: "npm:^18.0.0" - http-cache-semantics: "npm:^4.1.1" - is-lambda: "npm:^1.0.1" - minipass: "npm:^7.0.2" - minipass-fetch: "npm:^3.0.0" - minipass-flush: "npm:^1.0.5" - minipass-pipeline: "npm:^1.2.4" - negotiator: "npm:^0.6.3" - proc-log: "npm:^4.2.0" - promise-retry: "npm:^2.0.1" - ssri: "npm:^10.0.0" - checksum: 10c0/df5f4dbb6d98153b751bccf4dc4cc500de85a96a9331db9805596c46aa9f99d9555983954e6c1266d9f981ae37a9e4647f42b9a4bb5466f867f4012e582c9e7e - languageName: node - linkType: hard - -"media-typer@npm:0.3.0": - version: 0.3.0 - resolution: "media-typer@npm:0.3.0" - checksum: 10c0/d160f31246907e79fed398470285f21bafb45a62869dc469b1c8877f3f064f5eabc4bcc122f9479b8b605bc5c76187d7871cf84c4ee3ecd3e487da1993279928 - languageName: node - linkType: hard - -"media-typer@npm:^1.1.0": - version: 1.1.0 - resolution: "media-typer@npm:1.1.0" - checksum: 10c0/7b4baa40b25964bb90e2121ee489ec38642127e48d0cc2b6baa442688d3fde6262bfdca86d6bbf6ba708784afcac168c06840c71facac70e390f5f759ac121b9 - languageName: node - linkType: hard - -"merge-descriptors@npm:^2.0.0": - version: 2.0.0 - resolution: "merge-descriptors@npm:2.0.0" - checksum: 10c0/95389b7ced3f9b36fbdcf32eb946dc3dd1774c2fdf164609e55b18d03aa499b12bd3aae3a76c1c7185b96279e9803525550d3eb292b5224866060a288f335cb3 - languageName: node - linkType: hard - -"merge2@npm:^1.3.0, merge2@npm:^1.4.1": - version: 1.4.1 - resolution: "merge2@npm:1.4.1" - checksum: 10c0/254a8a4605b58f450308fc474c82ac9a094848081bf4c06778200207820e5193726dc563a0d2c16468810516a5c97d9d3ea0ca6585d23c58ccfff2403e8dbbeb - languageName: node - linkType: hard - -"methods@npm:~1.1.2": - version: 1.1.2 - resolution: "methods@npm:1.1.2" - checksum: 10c0/bdf7cc72ff0a33e3eede03708c08983c4d7a173f91348b4b1e4f47d4cdbf734433ad971e7d1e8c77247d9e5cd8adb81ea4c67b0a2db526b758b2233d7814b8b2 - languageName: node - linkType: hard - -"micromatch@npm:^4.0.4": - version: 4.0.8 - resolution: "micromatch@npm:4.0.8" - dependencies: - braces: "npm:^3.0.3" - picomatch: "npm:^2.3.1" - checksum: 10c0/166fa6eb926b9553f32ef81f5f531d27b4ce7da60e5baf8c021d043b27a388fb95e46a8038d5045877881e673f8134122b59624d5cecbd16eb50a42e7a6b5ca8 - languageName: node - linkType: hard - -"mime-db@npm:1.52.0": - version: 1.52.0 - resolution: "mime-db@npm:1.52.0" - checksum: 10c0/0557a01deebf45ac5f5777fe7740b2a5c309c6d62d40ceab4e23da9f821899ce7a900b7ac8157d4548ddbb7beffe9abc621250e6d182b0397ec7f10c7b91a5aa - languageName: node - linkType: hard - -"mime-db@npm:^1.53.0": - version: 1.53.0 - resolution: "mime-db@npm:1.53.0" - checksum: 10c0/1dcc37ba8ed5d1c179f5c6f0837e8db19371d5f2ea3690c3c2f3fa8c3858f976851d3460b172b4dee78ebd606762cbb407aa398545fbacd539e519f858cd7bf4 - languageName: node - linkType: hard - -"mime-types@npm:^2.1.35, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": - version: 2.1.35 - resolution: "mime-types@npm:2.1.35" - dependencies: - mime-db: "npm:1.52.0" - checksum: 10c0/82fb07ec56d8ff1fc999a84f2f217aa46cb6ed1033fefaabd5785b9a974ed225c90dc72fff460259e66b95b73648596dbcc50d51ed69cdf464af2d237d3149b2 - languageName: node - linkType: hard - -"mime-types@npm:^3.0.0": - version: 3.0.0 - resolution: "mime-types@npm:3.0.0" - dependencies: - mime-db: "npm:^1.53.0" - checksum: 10c0/8624501c75568f14e53fd9e12327fa18e70d6a85047c5beb9146990178f3adb31f893cb54d25ab58145cabf3e06c61953f2c5427ccd05b6b38fe09db8c5f5e7f - languageName: node - linkType: hard - -"minimatch@npm:^3.0.4, minimatch@npm:^3.1.2": - version: 3.1.2 - resolution: "minimatch@npm:3.1.2" - dependencies: - brace-expansion: "npm:^1.1.7" - checksum: 10c0/0262810a8fc2e72cca45d6fd86bd349eee435eb95ac6aa45c9ea2180e7ee875ef44c32b55b5973ceabe95ea12682f6e3725cbb63d7a2d1da3ae1163c8b210311 - languageName: node - linkType: hard - -"minimatch@npm:^5.0.1, minimatch@npm:^5.1.6": - version: 5.1.6 - resolution: "minimatch@npm:5.1.6" - dependencies: - brace-expansion: "npm:^2.0.1" - checksum: 10c0/3defdfd230914f22a8da203747c42ee3c405c39d4d37ffda284dac5e45b7e1f6c49aa8be606509002898e73091ff2a3bbfc59c2c6c71d4660609f63aa92f98e3 - languageName: node - linkType: hard - -"minimatch@npm:^9.0.4": - version: 9.0.5 - resolution: "minimatch@npm:9.0.5" - dependencies: - brace-expansion: "npm:^2.0.1" - checksum: 10c0/de96cf5e35bdf0eab3e2c853522f98ffbe9a36c37797778d2665231ec1f20a9447a7e567cb640901f89e4daaa95ae5d70c65a9e8aa2bb0019b6facbc3c0575ed - languageName: node - linkType: hard - -"minimist@npm:^1.2.0, minimist@npm:^1.2.6": - version: 1.2.8 - resolution: "minimist@npm:1.2.8" - checksum: 10c0/19d3fcdca050087b84c2029841a093691a91259a47def2f18222f41e7645a0b7c44ef4b40e88a1e58a40c84d2ef0ee6047c55594d298146d0eb3f6b737c20ce6 - languageName: node - linkType: hard - -"minipass-collect@npm:^2.0.1": - version: 2.0.1 - resolution: "minipass-collect@npm:2.0.1" - dependencies: - minipass: "npm:^7.0.3" - checksum: 10c0/5167e73f62bb74cc5019594709c77e6a742051a647fe9499abf03c71dca75515b7959d67a764bdc4f8b361cf897fbf25e2d9869ee039203ed45240f48b9aa06e - languageName: node - linkType: hard - -"minipass-fetch@npm:^3.0.0": - version: 3.0.5 - resolution: "minipass-fetch@npm:3.0.5" - dependencies: - encoding: "npm:^0.1.13" - minipass: "npm:^7.0.3" - minipass-sized: "npm:^1.0.3" - minizlib: "npm:^2.1.2" - dependenciesMeta: - encoding: - optional: true - checksum: 10c0/9d702d57f556274286fdd97e406fc38a2f5c8d15e158b498d7393b1105974b21249289ec571fa2b51e038a4872bfc82710111cf75fae98c662f3d6f95e72152b - languageName: node - linkType: hard - -"minipass-flush@npm:^1.0.5": - version: 1.0.5 - resolution: "minipass-flush@npm:1.0.5" - dependencies: - minipass: "npm:^3.0.0" - checksum: 10c0/2a51b63feb799d2bb34669205eee7c0eaf9dce01883261a5b77410c9408aa447e478efd191b4de6fc1101e796ff5892f8443ef20d9544385819093dbb32d36bd - languageName: node - linkType: hard - -"minipass-pipeline@npm:^1.2.4": - version: 1.2.4 - resolution: "minipass-pipeline@npm:1.2.4" - dependencies: - minipass: "npm:^3.0.0" - checksum: 10c0/cbda57cea20b140b797505dc2cac71581a70b3247b84480c1fed5ca5ba46c25ecc25f68bfc9e6dcb1a6e9017dab5c7ada5eab73ad4f0a49d84e35093e0c643f2 + picomatch: + optional: true + checksum: 10c0/e345083c4306b3aed6cb8ec551e26c36bab5c511e99ea4576a16750ddc8d3240e63826cc624f5ae17ad4dc82e68a253213b60d556c11bfad064b7607847ed07f languageName: node linkType: hard -"minipass-sized@npm:^1.0.3": - version: 1.0.3 - resolution: "minipass-sized@npm:1.0.3" +"file-entry-cache@npm:11.1.5 || >11.1.6 <12": + version: 11.1.5 + resolution: "file-entry-cache@npm:11.1.5" dependencies: - minipass: "npm:^3.0.0" - checksum: 10c0/298f124753efdc745cfe0f2bdfdd81ba25b9f4e753ca4a2066eb17c821f25d48acea607dfc997633ee5bf7b6dfffb4eee4f2051eb168663f0b99fad2fa4829cb + flat-cache: "npm:^6.1.23" + checksum: 10c0/d05eca8794b27824d79a0f1a0bc76b81ca7232b6c9634bc086f365a6d9ddac749c4826b2b2d36bb7ef9ed84fdf0097f34979527daeb9255b4f17673a4ae50fc5 languageName: node linkType: hard -"minipass@npm:^3.0.0": - version: 3.3.6 - resolution: "minipass@npm:3.3.6" - dependencies: - yallist: "npm:^4.0.0" - checksum: 10c0/a114746943afa1dbbca8249e706d1d38b85ed1298b530f5808ce51f8e9e941962e2a5ad2e00eae7dd21d8a4aae6586a66d4216d1a259385e9d0358f0c1eba16c +"file-selector@npm:^5.0.1": + version: 5.0.1 + resolution: "file-selector@npm:5.0.1" + checksum: 10c0/ea4c36f4c50f509973244050882c6b0e7b68d215c1deaaf13d55739b1d1cbe7f738bd6e6bfd9b7c439f4e928ebb52b471049ed244042045dd4bf89b8d978db59 languageName: node linkType: hard -"minipass@npm:^4.0.0": - version: 4.2.0 - resolution: "minipass@npm:4.2.0" - checksum: 10c0/5b0c6c935da83efad10ae4a30a0f81d10bb5e1376b6d5f6672b027f3e25bfcaa149b3b2924c417327c2cbb0ff9ea1c812b74f77cf58beb62322f58c8bda41450 +"finalhandler@npm:^2.1.0": + version: 2.1.1 + resolution: "finalhandler@npm:2.1.1" + dependencies: + debug: "npm:^4.4.0" + encodeurl: "npm:^2.0.0" + escape-html: "npm:^1.0.3" + on-finished: "npm:^2.4.1" + parseurl: "npm:^1.3.3" + statuses: "npm:^2.0.1" + checksum: 10c0/6bd664e21b7b2e79efcaace7d1a427169f61cce048fae68eb56290e6934e676b78e55d89f5998c5508871345bc59a61f47002dc505dc7288be68cceac1b701e2 languageName: node linkType: hard -"minipass@npm:^5.0.0": - version: 5.0.0 - resolution: "minipass@npm:5.0.0" - checksum: 10c0/a91d8043f691796a8ac88df039da19933ef0f633e3d7f0d35dcd5373af49131cf2399bfc355f41515dc495e3990369c3858cd319e5c2722b4753c90bf3152462 +"find-root@npm:^1.1.0": + version: 1.1.0 + resolution: "find-root@npm:1.1.0" + checksum: 10c0/1abc7f3bf2f8d78ff26d9e00ce9d0f7b32e5ff6d1da2857bcdf4746134c422282b091c672cde0572cac3840713487e0a7a636af9aa1b74cb11894b447a521efa languageName: node linkType: hard -"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.1.2": - version: 7.1.2 - resolution: "minipass@npm:7.1.2" - checksum: 10c0/b0fd20bb9fb56e5fa9a8bfac539e8915ae07430a619e4b86ff71f5fc757ef3924b23b2c4230393af1eda647ed3d75739e4e0acb250a6b1eb277cf7f8fe449557 +"find-up-simple@npm:^1.0.1": + version: 1.0.1 + resolution: "find-up-simple@npm:1.0.1" + checksum: 10c0/ad34de157b7db925d50ff78302fefb28e309f3bc947c93ffca0f9b0bccf9cf1a2dc57d805d5c94ec9fc60f4838f5dbdfd2a48ecd77c23015fa44c6dd5f60bc40 languageName: node linkType: hard -"minizlib@npm:^2.1.1, minizlib@npm:^2.1.2": - version: 2.1.2 - resolution: "minizlib@npm:2.1.2" +"find-up@npm:^5.0.0": + version: 5.0.0 + resolution: "find-up@npm:5.0.0" dependencies: - minipass: "npm:^3.0.0" - yallist: "npm:^4.0.0" - checksum: 10c0/64fae024e1a7d0346a1102bb670085b17b7f95bf6cfdf5b128772ec8faf9ea211464ea4add406a3a6384a7d87a0cd1a96263692134323477b4fb43659a6cab78 - languageName: node - linkType: hard - -"mkdirp@npm:^1.0.3": - version: 1.0.4 - resolution: "mkdirp@npm:1.0.4" - bin: - mkdirp: bin/cmd.js - checksum: 10c0/46ea0f3ffa8bc6a5bc0c7081ffc3907777f0ed6516888d40a518c5111f8366d97d2678911ad1a6882bf592fa9de6c784fea32e1687bb94e1f4944170af48a5cf + locate-path: "npm:^6.0.0" + path-exists: "npm:^4.0.0" + checksum: 10c0/062c5a83a9c02f53cdd6d175a37ecf8f87ea5bbff1fdfb828f04bfa021441bc7583e8ebc0872a4c1baab96221fb8a8a275a19809fb93fbc40bd69ec35634069a languageName: node linkType: hard -"mocha@npm:^10.7.3": - version: 10.7.3 - resolution: "mocha@npm:10.7.3" +"flat-cache@npm:^6.1.23": + version: 6.1.23 + resolution: "flat-cache@npm:6.1.23" dependencies: - ansi-colors: "npm:^4.1.3" - browser-stdout: "npm:^1.3.1" - chokidar: "npm:^3.5.3" - debug: "npm:^4.3.5" - diff: "npm:^5.2.0" - escape-string-regexp: "npm:^4.0.0" - find-up: "npm:^5.0.0" - glob: "npm:^8.1.0" - he: "npm:^1.2.0" - js-yaml: "npm:^4.1.0" - log-symbols: "npm:^4.1.0" - minimatch: "npm:^5.1.6" - ms: "npm:^2.1.3" - serialize-javascript: "npm:^6.0.2" - strip-json-comments: "npm:^3.1.1" - supports-color: "npm:^8.1.1" - workerpool: "npm:^6.5.1" - yargs: "npm:^16.2.0" - yargs-parser: "npm:^20.2.9" - yargs-unparser: "npm:^2.0.0" - bin: - _mocha: bin/_mocha - mocha: bin/mocha.js - checksum: 10c0/76a205905ec626262d903954daca31ba8e0dd4347092f627b98b8508dcdb5b30be62ec8f7a405fab3b2e691bdc099721c3291b330c3ee85b8ec40d3d179f8728 + cacheable: "npm:^2.5.0" + flatted: "npm:^3.4.2" + hookified: "npm:^1.15.0" + checksum: 10c0/c757c24f51334e1917958cd9d62d4ed3d520a3815d5a1ec50d993ac3a93fa81b6de7e28264231914bb0d990babd426e4d713d3e2f3d782dae65c42a2ded1e651 languageName: node linkType: hard -"ms@npm:2.0.0": - version: 2.0.0 - resolution: "ms@npm:2.0.0" - checksum: 10c0/f8fda810b39fd7255bbdc451c46286e549794fcc700dc9cd1d25658bbc4dc2563a5de6fe7c60f798a16a60c6ceb53f033cb353f493f0cf63e5199b702943159d +"flatted@npm:^3.4.2": + version: 3.4.4 + resolution: "flatted@npm:3.4.4" + checksum: 10c0/a3a52a88ea5a4c333e5a1f097dcd87a037fa31c236a77cf46222c2aa4036ef895ab9bc76138a66d9dc22bdb3652128f9598cd26e7439561bf19f67276e2bc37e languageName: node linkType: hard -"ms@npm:2.1.2": - version: 2.1.2 - resolution: "ms@npm:2.1.2" - checksum: 10c0/a437714e2f90dbf881b5191d35a6db792efbca5badf112f87b9e1c712aace4b4b9b742dd6537f3edf90fd6f684de897cec230abde57e87883766712ddda297cc +"forwarded@npm:0.2.0": + version: 0.2.0 + resolution: "forwarded@npm:0.2.0" + checksum: 10c0/9b67c3fac86acdbc9ae47ba1ddd5f2f81526fa4c8226863ede5600a3f7c7416ef451f6f1e240a3cc32d0fd79fcfe6beb08fd0da454f360032bde70bf80afbb33 languageName: node linkType: hard -"ms@npm:^2.1.1, ms@npm:^2.1.3": - version: 2.1.3 - resolution: "ms@npm:2.1.3" - checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 +"fresh@npm:^2.0.0": + version: 2.0.0 + resolution: "fresh@npm:2.0.0" + checksum: 10c0/0557548194cb9a809a435bf92bcfbc20c89e8b5eb38861b73ced36750437251e39a111fc3a18b98531be9dd91fe1411e4969f229dc579ec0251ce6c5d4900bbc languageName: node linkType: hard -"nanoid@npm:^3.3.7": - version: 3.3.7 - resolution: "nanoid@npm:3.3.7" - bin: - nanoid: bin/nanoid.cjs - checksum: 10c0/e3fb661aa083454f40500473bb69eedb85dc160e763150b9a2c567c7e9ff560ce028a9f833123b618a6ea742e311138b591910e795614a629029e86e180660f3 +"fsevents@npm:~2.3.3": + version: 2.3.3 + resolution: "fsevents@npm:2.3.3" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/a1f0c44595123ed717febbc478aa952e47adfc28e2092be66b8ab1635147254ca6cfe1df792a8997f22716d4cbafc73309899ff7bfac2ac3ad8cf2e4ecc3ec60 + conditions: os=darwin languageName: node linkType: hard -"natural-compare-lite@npm:^1.4.0": - version: 1.4.0 - resolution: "natural-compare-lite@npm:1.4.0" - checksum: 10c0/f6cef26f5044515754802c0fc475d81426f3b90fe88c20fabe08771ce1f736ce46e0397c10acb569a4dd0acb84c7f1ee70676122f95d5bfdd747af3a6c6bbaa8 +"fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": + version: 2.3.3 + resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" + dependencies: + node-gyp: "npm:latest" + conditions: os=darwin languageName: node linkType: hard -"natural-compare@npm:^1.4.0": - version: 1.4.0 - resolution: "natural-compare@npm:1.4.0" - checksum: 10c0/f5f9a7974bfb28a91afafa254b197f0f22c684d4a1731763dda960d2c8e375b36c7d690e0d9dc8fba774c537af14a7e979129bca23d88d052fbeb9466955e447 +"function-bind@npm:^1.1.2": + version: 1.1.2 + resolution: "function-bind@npm:1.1.2" + checksum: 10c0/d8680ee1e5fcd4c197e4ac33b2b4dce03c71f4d91717292785703db200f5c21f977c568d28061226f9b5900cbcd2c84463646134fd5337e7925e0942bc3f46d5 languageName: node linkType: hard -"negotiator@npm:0.6.3, negotiator@npm:^0.6.3": - version: 0.6.3 - resolution: "negotiator@npm:0.6.3" - checksum: 10c0/3ec9fd413e7bf071c937ae60d572bc67155262068ed522cf4b3be5edbe6ddf67d095ec03a3a14ebf8fc8e95f8e1d61be4869db0dbb0de696f6b837358bd43fc2 +"generator-function@npm:^2.0.0": + version: 2.0.1 + resolution: "generator-function@npm:2.0.1" + checksum: 10c0/8a9f59df0f01cfefafdb3b451b80555e5cf6d76487095db91ac461a0e682e4ff7a9dbce15f4ecec191e53586d59eece01949e05a4b4492879600bbbe8e28d6b8 languageName: node linkType: hard -"negotiator@npm:^1.0.0": - version: 1.0.0 - resolution: "negotiator@npm:1.0.0" - checksum: 10c0/4c559dd52669ea48e1914f9d634227c561221dd54734070791f999c52ed0ff36e437b2e07d5c1f6e32909fc625fe46491c16e4a8f0572567d4dd15c3a4fda04b +"get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.3.0": + version: 1.3.1 + resolution: "get-intrinsic@npm:1.3.1" + dependencies: + async-function: "npm:^1.0.0" + async-generator-function: "npm:^1.0.0" + call-bind-apply-helpers: "npm:^1.0.2" + es-define-property: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.1.1" + function-bind: "npm:^1.1.2" + generator-function: "npm:^2.0.0" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + hasown: "npm:^2.0.2" + math-intrinsics: "npm:^1.1.0" + checksum: 10c0/9f4ab0cf7efe0fd2c8185f52e6f637e708f3a112610c88869f8f041bb9ecc2ce44bf285dfdbdc6f4f7c277a5b88d8e94a432374d97cca22f3de7fc63795deb5d languageName: node linkType: hard -"nise@npm:^5.1.2": - version: 5.1.4 - resolution: "nise@npm:5.1.4" +"get-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "get-proto@npm:1.0.1" dependencies: - "@sinonjs/commons": "npm:^2.0.0" - "@sinonjs/fake-timers": "npm:^10.0.2" - "@sinonjs/text-encoding": "npm:^0.7.1" - just-extend: "npm:^4.0.2" - path-to-regexp: "npm:^1.7.0" - checksum: 10c0/a8f4892ba336d66bab010f52acf1ec8fd598f77401689174a49fdb04c958349133f7703b308e4e4a3b93adf6125efe1824d899a3903c9a108c5723d57b235a26 + dunder-proto: "npm:^1.0.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/9224acb44603c5526955e83510b9da41baf6ae73f7398875fba50edc5e944223a89c4a72b070fcd78beb5f7bdda58ecb6294adc28f7acfc0da05f76a2399643c languageName: node linkType: hard -"node-gyp@npm:latest": - version: 10.2.0 - resolution: "node-gyp@npm:10.2.0" +"glob-parent@npm:^6.0.2": + version: 6.0.2 + resolution: "glob-parent@npm:6.0.2" dependencies: - env-paths: "npm:^2.2.0" - exponential-backoff: "npm:^3.1.1" - glob: "npm:^10.3.10" - graceful-fs: "npm:^4.2.6" - make-fetch-happen: "npm:^13.0.0" - nopt: "npm:^7.0.0" - proc-log: "npm:^4.1.0" - semver: "npm:^7.3.5" - tar: "npm:^6.2.1" - which: "npm:^4.0.0" - bin: - node-gyp: bin/node-gyp.js - checksum: 10c0/00630d67dbd09a45aee0a5d55c05e3916ca9e6d427ee4f7bc392d2d3dc5fad7449b21fc098dd38260a53d9dcc9c879b36704a1994235d4707e7271af7e9a835b + is-glob: "npm:^4.0.3" + checksum: 10c0/317034d88654730230b3f43bb7ad4f7c90257a426e872ea0bf157473ac61c99bf5d205fad8f0185f989be8d2fa6d3c7dce1645d99d545b6ea9089c39f838e7f8 languageName: node linkType: hard -"node-releases@npm:^2.0.18": - version: 2.0.18 - resolution: "node-releases@npm:2.0.18" - checksum: 10c0/786ac9db9d7226339e1dc84bbb42007cb054a346bd9257e6aa154d294f01bc6a6cddb1348fa099f079be6580acbb470e3c048effd5f719325abd0179e566fd27 +"glob@npm:^13.0.0, glob@npm:^13.0.3": + version: 13.0.6 + resolution: "glob@npm:13.0.6" + dependencies: + minimatch: "npm:^10.2.2" + minipass: "npm:^7.1.3" + path-scurry: "npm:^2.0.2" + checksum: 10c0/269c236f11a9b50357fe7a8c6aadac667e01deb5242b19c84975628f05f4438d8ee1354bb62c5d6c10f37fd59911b54d7799730633a2786660d8c69f1d18120a languageName: node linkType: hard -"node-releases@npm:^2.0.8": - version: 2.0.10 - resolution: "node-releases@npm:2.0.10" - checksum: 10c0/90947653e8e3d85bda4bcbf28d019693ccfb5d5947838ca64e91208b51d7bfc13ba930b8216389a4faffbad8145b2c616bf1f7a09c97a1a9ac57fd6ef6d01c5c +"gopd@npm:^1.2.0": + version: 1.2.0 + resolution: "gopd@npm:1.2.0" + checksum: 10c0/50fff1e04ba2b7737c097358534eacadad1e68d24cccee3272e04e007bed008e68d2614f3987788428fd192a5ae3889d08fb2331417e4fc4a9ab366b2043cead languageName: node linkType: hard -"nopt@npm:^7.0.0": - version: 7.2.1 - resolution: "nopt@npm:7.2.1" - dependencies: - abbrev: "npm:^2.0.0" - bin: - nopt: bin/nopt.js - checksum: 10c0/a069c7c736767121242037a22a788863accfa932ab285a1eb569eb8cd534b09d17206f68c37f096ae785647435e0c5a5a0a67b42ec743e481a455e5ae6a6df81 +"graceful-fs@npm:^4.2.6": + version: 4.2.11 + resolution: "graceful-fs@npm:4.2.11" + checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 languageName: node linkType: hard -"normalize-path@npm:^3.0.0, normalize-path@npm:~3.0.0": - version: 3.0.0 - resolution: "normalize-path@npm:3.0.0" - checksum: 10c0/e008c8142bcc335b5e38cf0d63cfd39d6cf2d97480af9abdbe9a439221fd4d749763bab492a8ee708ce7a194bb00c9da6d0a115018672310850489137b3da046 +"has-flag@npm:^4.0.0": + version: 4.0.0 + resolution: "has-flag@npm:4.0.0" + checksum: 10c0/2e789c61b7888d66993e14e8331449e525ef42aac53c627cc53d1c3334e768bcb6abdc4f5f0de1478a25beec6f0bd62c7549058b7ac53e924040d4f301f02fd1 languageName: node linkType: hard -"object-assign@npm:^4, object-assign@npm:^4.1.1": - version: 4.1.1 - resolution: "object-assign@npm:4.1.1" - checksum: 10c0/1f4df9945120325d041ccf7b86f31e8bcc14e73d29171e37a7903050e96b81323784ec59f93f102ec635bcf6fa8034ba3ea0a8c7e69fa202b87ae3b6cec5a414 +"has-symbols@npm:^1.1.0": + version: 1.1.0 + resolution: "has-symbols@npm:1.1.0" + checksum: 10c0/dde0a734b17ae51e84b10986e651c664379018d10b91b6b0e9b293eddb32f0f069688c841fb40f19e9611546130153e0a2a48fd7f512891fb000ddfa36f5a20e languageName: node linkType: hard -"object-inspect@npm:^1.13.1": - version: 1.13.2 - resolution: "object-inspect@npm:1.13.2" - checksum: 10c0/b97835b4c91ec37b5fd71add84f21c3f1047d1d155d00c0fcd6699516c256d4fcc6ff17a1aced873197fe447f91a3964178fd2a67a1ee2120cdaf60e81a050b4 +"hashery@npm:^1.4.0, hashery@npm:^1.5.1": + version: 1.5.1 + resolution: "hashery@npm:1.5.1" + dependencies: + hookified: "npm:^1.15.0" + checksum: 10c0/ab4225b655a7b0d05df99b1a59d5b3a51fe433f82422ca25e6f3f4c4ddd30adb49ebd38e0047ef9bded93319c1e9fc857e16aa382e554929c871cb77d39fc463 languageName: node linkType: hard -"object-inspect@npm:^1.9.0": - version: 1.12.3 - resolution: "object-inspect@npm:1.12.3" - checksum: 10c0/752bb5f4dc595e214157ea8f442adb77bdb850ace762b078d151d8b6486331ab12364997a89ee6509be1023b15adf2b3774437a7105f8a5043dfda11ed622411 +"hasown@npm:^2.0.2, hasown@npm:^2.0.4": + version: 2.0.4 + resolution: "hasown@npm:2.0.4" + dependencies: + function-bind: "npm:^1.1.2" + checksum: 10c0/2d8de939e270b70618f8cebb69746620db10617dbb495bc66ddad326955ea24d3ca4af133aff3eb7c1853e0218f867bc2b050ec26fe02e3aea58f880ffc5e506 languageName: node linkType: hard -"object-is@npm:^1.1.5": - version: 1.1.6 - resolution: "object-is@npm:1.1.6" +"hoist-non-react-statics@npm:^3.3.1": + version: 3.3.2 + resolution: "hoist-non-react-statics@npm:3.3.2" dependencies: - call-bind: "npm:^1.0.7" - define-properties: "npm:^1.2.1" - checksum: 10c0/506af444c4dce7f8e31f34fc549e2fb8152d6b9c4a30c6e62852badd7f520b579c679af433e7a072f9d78eb7808d230dc12e1cf58da9154dfbf8813099ea0fe0 + react-is: "npm:^16.7.0" + checksum: 10c0/fe0889169e845d738b59b64badf5e55fa3cf20454f9203d1eb088df322d49d4318df774828e789898dcb280e8a5521bb59b3203385662ca5e9218a6ca5820e74 languageName: node linkType: hard -"object-keys@npm:^1.1.1": - version: 1.1.1 - resolution: "object-keys@npm:1.1.1" - checksum: 10c0/b11f7ccdbc6d406d1f186cdadb9d54738e347b2692a14439ca5ac70c225fa6db46db809711b78589866d47b25fc3e8dee0b4c722ac751e11180f9380e3d8601d +"hookified@npm:^1.15.0, hookified@npm:^1.15.1": + version: 1.15.1 + resolution: "hookified@npm:1.15.1" + checksum: 10c0/6b691374fa97ae57169fb29f90e723499fda5e85494654fbe55c4768b3ccbf3e14c0adc8d0f365f32c503b60d7c06f907781f5966c03d41c423575eb5e16860c languageName: node linkType: hard -"object.assign@npm:^4.1.4, object.assign@npm:^4.1.5": - version: 4.1.5 - resolution: "object.assign@npm:4.1.5" - dependencies: - call-bind: "npm:^1.0.5" - define-properties: "npm:^1.2.1" - has-symbols: "npm:^1.0.3" - object-keys: "npm:^1.1.1" - checksum: 10c0/60108e1fa2706f22554a4648299b0955236c62b3685c52abf4988d14fffb0e7731e00aa8c6448397e3eb63d087dcc124a9f21e1980f36d0b2667f3c18bacd469 +"hookified@npm:^2.1.1": + version: 2.2.0 + resolution: "hookified@npm:2.2.0" + checksum: 10c0/7017d2b66945490293a5aba239e7b39f39071dd940fa019348c7ffea92b91b8c267853c4c51680ed0f3687b33352582fa4d2cff6dd4c5a1c5c44b037276f07aa languageName: node linkType: hard -"object.entries@npm:^1.1.8": - version: 1.1.8 - resolution: "object.entries@npm:1.1.8" +"http-errors@npm:^2.0.0, http-errors@npm:^2.0.1, http-errors@npm:~2.0.1": + version: 2.0.1 + resolution: "http-errors@npm:2.0.1" dependencies: - call-bind: "npm:^1.0.7" - define-properties: "npm:^1.2.1" - es-object-atoms: "npm:^1.0.0" - checksum: 10c0/db9ea979d2956a3bc26c262da4a4d212d36f374652cc4c13efdd069c1a519c16571c137e2893d1c46e1cb0e15c88fd6419eaf410c945f329f09835487d7e65d3 + depd: "npm:~2.0.0" + inherits: "npm:~2.0.4" + setprototypeof: "npm:~1.2.0" + statuses: "npm:~2.0.2" + toidentifier: "npm:~1.0.1" + checksum: 10c0/fb38906cef4f5c83952d97661fe14dc156cb59fe54812a42cd448fa57b5c5dfcb38a40a916957737bd6b87aab257c0648d63eb5b6a9ca9f548e105b6072712d4 languageName: node linkType: hard -"object.fromentries@npm:^2.0.8": - version: 2.0.8 - resolution: "object.fromentries@npm:2.0.8" +"iconv-lite@npm:^0.7.2, iconv-lite@npm:~0.7.0": + version: 0.7.3 + resolution: "iconv-lite@npm:0.7.3" dependencies: - call-bind: "npm:^1.0.7" - define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.2" - es-object-atoms: "npm:^1.0.0" - checksum: 10c0/cd4327e6c3369cfa805deb4cbbe919bfb7d3aeebf0bcaba291bb568ea7169f8f8cdbcabe2f00b40db0c20cd20f08e11b5f3a5a36fb7dd3fe04850c50db3bf83b + safer-buffer: "npm:>= 2.1.2 < 3.0.0" + checksum: 10c0/be2fd2414f7e94be3a63063fa0ad5919c16bc7b6e00c77eea0765ced6db405739f38dd51016583058fba25cc1d0106ba30b83fbb7a7e32f824d4db76f23d8d33 languageName: node linkType: hard -"object.groupby@npm:^1.0.3": - version: 1.0.3 - resolution: "object.groupby@npm:1.0.3" - dependencies: - call-bind: "npm:^1.0.7" - define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.2" - checksum: 10c0/60d0455c85c736fbfeda0217d1a77525956f76f7b2495edeca9e9bbf8168a45783199e77b894d30638837c654d0cc410e0e02cbfcf445bc8de71c3da1ede6a9c +"ignore@npm:^5.2.0": + version: 5.3.2 + resolution: "ignore@npm:5.3.2" + checksum: 10c0/f9f652c957983634ded1e7f02da3b559a0d4cc210fca3792cb67f1b153623c9c42efdc1c4121af171e295444459fc4a9201101fb041b1104a3c000bccb188337 languageName: node linkType: hard -"object.values@npm:^1.1.6, object.values@npm:^1.2.0": - version: 1.2.0 - resolution: "object.values@npm:1.2.0" - dependencies: - call-bind: "npm:^1.0.7" - define-properties: "npm:^1.2.1" - es-object-atoms: "npm:^1.0.0" - checksum: 10c0/15809dc40fd6c5529501324fec5ff08570b7d70fb5ebbe8e2b3901afec35cf2b3dc484d1210c6c642cd3e7e0a5e18dd1d6850115337fef46bdae14ab0cb18ac3 +"ignore@npm:^7.0.5": + version: 7.0.12 + resolution: "ignore@npm:7.0.12" + checksum: 10c0/d3a1aab740f3e9e1b634d50e1ad9d7d5db36728ea56a9bf4ef2f3761f4c29a42543ce94761fd637ba8c25a5592ef778d03828bbdf2a599c05c4e32da6095b45c languageName: node linkType: hard -"on-finished@npm:2.4.1, on-finished@npm:^2.4.1": - version: 2.4.1 - resolution: "on-finished@npm:2.4.1" +"import-fresh@npm:^3.2.1": + version: 3.3.1 + resolution: "import-fresh@npm:3.3.1" dependencies: - ee-first: "npm:1.1.1" - checksum: 10c0/46fb11b9063782f2d9968863d9cbba33d77aa13c17f895f56129c274318b86500b22af3a160fe9995aa41317efcd22941b6eba747f718ced08d9a73afdb087b4 + parent-module: "npm:^1.0.0" + resolve-from: "npm:^4.0.0" + checksum: 10c0/bf8cc494872fef783249709385ae883b447e3eb09db0ebd15dcead7d9afe7224dad7bd7591c6b73b0b19b3c0f9640eb8ee884f01cfaf2887ab995b0b36a0cbec languageName: node linkType: hard -"once@npm:1.4.0, once@npm:^1.3.0": - version: 1.4.0 - resolution: "once@npm:1.4.0" - dependencies: - wrappy: "npm:1" - checksum: 10c0/5d48aca287dfefabd756621c5dfce5c91a549a93e9fdb7b8246bc4c4790aa2ec17b34a260530474635147aeb631a2dcc8b32c613df0675f96041cbb8244517d0 +"imurmurhash@npm:^0.1.4": + version: 0.1.4 + resolution: "imurmurhash@npm:0.1.4" + checksum: 10c0/8b51313850dd33605c6c9d3fd9638b714f4c4c40250cff658209f30d40da60f78992fb2df5dabee4acf589a6a82bbc79ad5486550754bd9ec4e3fc0d4a57d6a6 languageName: node linkType: hard -"optionator@npm:^0.9.3": - version: 0.9.4 - resolution: "optionator@npm:0.9.4" - dependencies: - deep-is: "npm:^0.1.3" - fast-levenshtein: "npm:^2.0.6" - levn: "npm:^0.4.1" - prelude-ls: "npm:^1.2.1" - type-check: "npm:^0.4.0" - word-wrap: "npm:^1.2.5" - checksum: 10c0/4afb687a059ee65b61df74dfe87d8d6815cd6883cb8b3d5883a910df72d0f5d029821f37025e4bccf4048873dbdb09acc6d303d27b8f76b1a80dd5a7d5334675 +"inherits@npm:~2.0.4": + version: 2.0.4 + resolution: "inherits@npm:2.0.4" + checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2 languageName: node linkType: hard -"p-limit@npm:^3.0.2": - version: 3.1.0 - resolution: "p-limit@npm:3.1.0" - dependencies: - yocto-queue: "npm:^0.1.0" - checksum: 10c0/9db675949dbdc9c3763c89e748d0ef8bdad0afbb24d49ceaf4c46c02c77d30db4e0652ed36d0a0a7a95154335fab810d95c86153105bb73b3a90448e2bb14e1a +"ipaddr.js@npm:1.9.1": + version: 1.9.1 + resolution: "ipaddr.js@npm:1.9.1" + checksum: 10c0/0486e775047971d3fdb5fb4f063829bac45af299ae0b82dcf3afa2145338e08290563a2a70f34b732d795ecc8311902e541a8530eeb30d75860a78ff4e94ce2a languageName: node linkType: hard -"p-locate@npm:^5.0.0": - version: 5.0.0 - resolution: "p-locate@npm:5.0.0" - dependencies: - p-limit: "npm:^3.0.2" - checksum: 10c0/2290d627ab7903b8b70d11d384fee714b797f6040d9278932754a6860845c4d3190603a0772a663c8cb5a7b21d1b16acb3a6487ebcafa9773094edc3dfe6009a +"is-arrayish@npm:^0.2.1": + version: 0.2.1 + resolution: "is-arrayish@npm:0.2.1" + checksum: 10c0/e7fb686a739068bb70f860b39b67afc62acc62e36bb61c5f965768abce1873b379c563e61dd2adad96ebb7edf6651111b385e490cf508378959b0ed4cac4e729 languageName: node linkType: hard -"p-map@npm:^4.0.0": - version: 4.0.0 - resolution: "p-map@npm:4.0.0" +"is-core-module@npm:^2.17.0": + version: 2.17.0 + resolution: "is-core-module@npm:2.17.0" dependencies: - aggregate-error: "npm:^3.0.0" - checksum: 10c0/592c05bd6262c466ce269ff172bb8de7c6975afca9b50c975135b974e9bdaafbfe80e61aaaf5be6d1200ba08b30ead04b88cfa7e25ff1e3b93ab28c9f62a2c75 + hasown: "npm:^2.0.4" + checksum: 10c0/3dc7c15ea42f846180cd2cdd57af62270e0a8d0ed31c17c85735d554089d39975e2e82e65fc7c8280a3c476ca3d0d09992d0860b4998abb769a277341feb6698 languageName: node linkType: hard -"package-json-from-dist@npm:^1.0.0": - version: 1.0.0 - resolution: "package-json-from-dist@npm:1.0.0" - checksum: 10c0/e3ffaf6ac1040ab6082a658230c041ad14e72fabe99076a2081bb1d5d41210f11872403fc09082daf4387fc0baa6577f96c9c0e94c90c394fd57794b66aa4033 +"is-extglob@npm:^2.1.1": + version: 2.1.1 + resolution: "is-extglob@npm:2.1.1" + checksum: 10c0/5487da35691fbc339700bbb2730430b07777a3c21b9ebaecb3072512dfd7b4ba78ac2381a87e8d78d20ea08affb3f1971b4af629173a6bf435ff8a4c47747912 languageName: node linkType: hard -"parent-module@npm:^1.0.0": - version: 1.0.1 - resolution: "parent-module@npm:1.0.1" +"is-glob@npm:^4.0.0, is-glob@npm:^4.0.3": + version: 4.0.3 + resolution: "is-glob@npm:4.0.3" dependencies: - callsites: "npm:^3.0.0" - checksum: 10c0/c63d6e80000d4babd11978e0d3fee386ca7752a02b035fd2435960ffaa7219dc42146f07069fb65e6e8bf1caef89daf9af7535a39bddf354d78bf50d8294f556 + is-extglob: "npm:^2.1.1" + checksum: 10c0/17fb4014e22be3bbecea9b2e3a76e9e34ff645466be702f1693e8f1ee1adac84710d0be0bd9f967d6354036fd51ab7c2741d954d6e91dae6bb69714de92c197a languageName: node linkType: hard -"parse-json@npm:^5.0.0": - version: 5.2.0 - resolution: "parse-json@npm:5.2.0" - dependencies: - "@babel/code-frame": "npm:^7.0.0" - error-ex: "npm:^1.3.1" - json-parse-even-better-errors: "npm:^2.3.0" - lines-and-columns: "npm:^1.1.6" - checksum: 10c0/77947f2253005be7a12d858aedbafa09c9ae39eb4863adf330f7b416ca4f4a08132e453e08de2db46459256fb66afaac5ee758b44fe6541b7cdaf9d252e59585 +"is-path-inside@npm:^4.0.0": + version: 4.0.0 + resolution: "is-path-inside@npm:4.0.0" + checksum: 10c0/51188d7e2b1d907a9a5f7c18d99a90b60870b951ed87cf97595d9aaa429d4c010652c3350bcbf31182e7f4b0eab9a1860b43e16729b13cb1a44baaa6cdb64c46 languageName: node linkType: hard -"parseurl@npm:^1.3.3, parseurl@npm:~1.3.3": - version: 1.3.3 - resolution: "parseurl@npm:1.3.3" - checksum: 10c0/90dd4760d6f6174adb9f20cf0965ae12e23879b5f5464f38e92fce8073354341e4b3b76fa3d878351efe7d01e617121955284cfd002ab087fba1a0726ec0b4f5 +"is-promise@npm:^4.0.0": + version: 4.0.0 + resolution: "is-promise@npm:4.0.0" + checksum: 10c0/ebd5c672d73db781ab33ccb155fb9969d6028e37414d609b115cc534654c91ccd061821d5b987eefaa97cf4c62f0b909bb2f04db88306de26e91bfe8ddc01503 languageName: node linkType: hard -"path-exists@npm:^4.0.0": - version: 4.0.0 - resolution: "path-exists@npm:4.0.0" - checksum: 10c0/8c0bd3f5238188197dc78dced15207a4716c51cc4e3624c44fc97acf69558f5ebb9a2afff486fe1b4ee148e0c133e96c5e11a9aa5c48a3006e3467da070e5e1b +"is-unicode-supported@npm:^0.1.0": + version: 0.1.0 + resolution: "is-unicode-supported@npm:0.1.0" + checksum: 10c0/00cbe3455c3756be68d2542c416cab888aebd5012781d6819749fefb15162ff23e38501fe681b3d751c73e8ff561ac09a5293eba6f58fdf0178462ce6dcb3453 languageName: node linkType: hard -"path-key@npm:^3.1.0": - version: 3.1.1 - resolution: "path-key@npm:3.1.1" - checksum: 10c0/748c43efd5a569c039d7a00a03b58eecd1d75f3999f5a28303d75f521288df4823bc057d8784eb72358b2895a05f29a070bc9f1f17d28226cc4e62494cc58c4c +"isexe@npm:^2.0.0": + version: 2.0.0 + resolution: "isexe@npm:2.0.0" + checksum: 10c0/228cfa503fadc2c31596ab06ed6aa82c9976eec2bfd83397e7eaf06d0ccf42cd1dfd6743bf9aeb01aebd4156d009994c5f76ea898d2832c1fe342da923ca457d languageName: node linkType: hard -"path-parse@npm:^1.0.7": - version: 1.0.7 - resolution: "path-parse@npm:1.0.7" - checksum: 10c0/11ce261f9d294cc7a58d6a574b7f1b935842355ec66fba3c3fd79e0f036462eaf07d0aa95bb74ff432f9afef97ce1926c720988c6a7451d8a584930ae7de86e1 +"isexe@npm:^4.0.0": + version: 4.0.0 + resolution: "isexe@npm:4.0.0" + checksum: 10c0/5884815115bceac452877659a9c7726382531592f43dc29e5d48b7c4100661aed54018cb90bd36cb2eaeba521092570769167acbb95c18d39afdccbcca06c5ce languageName: node linkType: hard -"path-scurry@npm:^1.11.1": - version: 1.11.1 - resolution: "path-scurry@npm:1.11.1" - dependencies: - lru-cache: "npm:^10.2.0" - minipass: "npm:^5.0.0 || ^6.0.2 || ^7.0.0" - checksum: 10c0/32a13711a2a505616ae1cc1b5076801e453e7aae6ac40ab55b388bb91b9d0547a52f5aaceff710ea400205f18691120d4431e520afbe4266b836fadede15872d +"js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": + version: 4.0.0 + resolution: "js-tokens@npm:4.0.0" + checksum: 10c0/e248708d377aa058eacf2037b07ded847790e6de892bbad3dac0abba2e759cb9f121b00099a65195616badcb6eca8d14d975cb3e89eb1cfda644756402c8aeed languageName: node linkType: hard -"path-to-regexp@npm:^1.7.0": - version: 1.8.0 - resolution: "path-to-regexp@npm:1.8.0" +"js-yaml@npm:^5.0.0": + version: 5.5.0 + resolution: "js-yaml@npm:5.5.0" dependencies: - isarray: "npm:0.0.1" - checksum: 10c0/7b25d6f27a8de03f49406d16195450f5ced694398adea1510b0f949d9660600d1769c5c6c83668583b7e6b503f3caf1ede8ffc08135dbe3e982f034f356fbb5c + argparse: "npm:^3.0.3" + bin: + js-yaml: bin/js-yaml.mjs + checksum: 10c0/311619f44dd788657e9b81d4b5c60ff367441f364045e3796bc604a41f25819852748fb173ae82f2553d53cb609d7fdb79e0a74148892cb2c085eebe91839b53 languageName: node linkType: hard -"path-to-regexp@npm:^8.0.0": - version: 8.2.0 - resolution: "path-to-regexp@npm:8.2.0" - checksum: 10c0/ef7d0a887b603c0a142fad16ccebdcdc42910f0b14830517c724466ad676107476bba2fe9fffd28fd4c141391ccd42ea426f32bb44c2c82ecaefe10c37b90f5a +"jsesc@npm:^3.0.2": + version: 3.1.0 + resolution: "jsesc@npm:3.1.0" + bin: + jsesc: bin/jsesc + checksum: 10c0/531779df5ec94f47e462da26b4cbf05eb88a83d9f08aac2ba04206508fc598527a153d08bd462bae82fc78b3eaa1a908e1a4a79f886e9238641c4cdefaf118b1 languageName: node linkType: hard -"path-type@npm:^4.0.0": - version: 4.0.0 - resolution: "path-type@npm:4.0.0" - checksum: 10c0/666f6973f332f27581371efaf303fd6c272cc43c2057b37aa99e3643158c7e4b2626549555d88626e99ea9e046f82f32e41bbde5f1508547e9a11b149b52387c +"json-parse-even-better-errors@npm:^2.3.0": + version: 2.3.1 + resolution: "json-parse-even-better-errors@npm:2.3.1" + checksum: 10c0/140932564c8f0b88455432e0f33c4cb4086b8868e37524e07e723f4eaedb9425bdc2bafd71bd1d9765bd15fd1e2d126972bc83990f55c467168c228c24d665f3 languageName: node linkType: hard -"pathval@npm:^2.0.0": - version: 2.0.0 - resolution: "pathval@npm:2.0.0" - checksum: 10c0/602e4ee347fba8a599115af2ccd8179836a63c925c23e04bd056d0674a64b39e3a081b643cc7bc0b84390517df2d800a46fcc5598d42c155fe4977095c2f77c5 +"json-schema-traverse@npm:^0.4.1": + version: 0.4.1 + resolution: "json-schema-traverse@npm:0.4.1" + checksum: 10c0/108fa90d4cc6f08243aedc6da16c408daf81793bf903e9fd5ab21983cda433d5d2da49e40711da016289465ec2e62e0324dcdfbc06275a607fe3233fde4942ce languageName: node linkType: hard -"picocolors@npm:^1.0.0": - version: 1.0.0 - resolution: "picocolors@npm:1.0.0" - checksum: 10c0/20a5b249e331c14479d94ec6817a182fd7a5680debae82705747b2db7ec50009a5f6648d0621c561b0572703f84dbef0858abcbd5856d3c5511426afcb1961f7 +"json-stable-stringify-without-jsonify@npm:^1.0.1": + version: 1.0.1 + resolution: "json-stable-stringify-without-jsonify@npm:1.0.1" + checksum: 10c0/cb168b61fd4de83e58d09aaa6425ef71001bae30d260e2c57e7d09a5fd82223e2f22a042dedaab8db23b7d9ae46854b08bb1f91675a8be11c5cffebef5fb66a5 languageName: node linkType: hard -"picocolors@npm:^1.0.1": - version: 1.1.0 - resolution: "picocolors@npm:1.1.0" - checksum: 10c0/86946f6032148801ef09c051c6fb13b5cf942eaf147e30ea79edb91dd32d700934edebe782a1078ff859fb2b816792e97ef4dab03d7f0b804f6b01a0df35e023 +"keyv@npm:^5.6.0": + version: 5.6.0 + resolution: "keyv@npm:5.6.0" + dependencies: + "@keyv/serialize": "npm:^1.1.1" + checksum: 10c0/c3ea795b6e03593ca57c8f70928a69bad14c13389a7fb75649a115ff55615244b04d8902798d841c17f0bb4a8a8866c97133b543b93f151b440170bba09176db languageName: node linkType: hard -"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.3.1": - version: 2.3.1 - resolution: "picomatch@npm:2.3.1" - checksum: 10c0/26c02b8d06f03206fc2ab8d16f19960f2ff9e81a658f831ecb656d8f17d9edc799e8364b1f4a7873e89d9702dff96204be0fa26fe4181f6843f040f819dac4be +"levn@npm:^0.4.1": + version: 0.4.1 + resolution: "levn@npm:0.4.1" + dependencies: + prelude-ls: "npm:^1.2.1" + type-check: "npm:~0.4.0" + checksum: 10c0/effb03cad7c89dfa5bd4f6989364bfc79994c2042ec5966cb9b95990e2edee5cd8969ddf42616a0373ac49fac1403437deaf6e9050fbbaa3546093a59b9ac94e languageName: node linkType: hard -"possible-typed-array-names@npm:^1.0.0": - version: 1.0.0 - resolution: "possible-typed-array-names@npm:1.0.0" - checksum: 10c0/d9aa22d31f4f7680e20269db76791b41c3a32c01a373e25f8a4813b4d45f7456bfc2b6d68f752dc4aab0e0bb0721cb3d76fb678c9101cb7a16316664bc2c73fd +"lightningcss-android-arm64@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-android-arm64@npm:1.33.0" + conditions: os=android & cpu=arm64 languageName: node linkType: hard -"postcss@npm:^8.4.41": - version: 8.4.44 - resolution: "postcss@npm:8.4.44" - dependencies: - nanoid: "npm:^3.3.7" - picocolors: "npm:^1.0.1" - source-map-js: "npm:^1.2.0" - checksum: 10c0/53c33338261a3d4f4198f8893e9dfe8b828d8d9186142ee85f02d228f04245c5bbe31239411a357a556ad20ed96f28db24d0921d63edc428fdc9133289371a1d +"lightningcss-darwin-arm64@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-darwin-arm64@npm:1.33.0" + conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"postgres@npm:^3.4.4": - version: 3.4.4 - resolution: "postgres@npm:3.4.4" - checksum: 10c0/5c01a1501a1cb8851d981b1949bffd0a0a82ac5fd8453c410fdf4d6c0f91d01e69a42b30da2d08a77a00d6ddac0ea6ce58a1aa0dd6bd3f76e9220b1176bdfe60 +"lightningcss-darwin-x64@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-darwin-x64@npm:1.33.0" + conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"prelude-ls@npm:^1.2.1": - version: 1.2.1 - resolution: "prelude-ls@npm:1.2.1" - checksum: 10c0/b00d617431e7886c520a6f498a2e14c75ec58f6d93ba48c3b639cf241b54232d90daa05d83a9e9b9fef6baa63cb7e1e4602c2372fea5bc169668401eb127d0cd +"lightningcss-freebsd-x64@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-freebsd-x64@npm:1.33.0" + conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"prettier-linter-helpers@npm:^1.0.0": - version: 1.0.0 - resolution: "prettier-linter-helpers@npm:1.0.0" - dependencies: - fast-diff: "npm:^1.1.2" - checksum: 10c0/81e0027d731b7b3697ccd2129470ed9913ecb111e4ec175a12f0fcfab0096516373bf0af2fef132af50cafb0a905b74ff57996d615f59512bb9ac7378fcc64ab +"lightningcss-linux-arm-gnueabihf@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-linux-arm-gnueabihf@npm:1.33.0" + conditions: os=linux & cpu=arm languageName: node linkType: hard -"prettier@npm:^3.3.3": - version: 3.3.3 - resolution: "prettier@npm:3.3.3" - bin: - prettier: bin/prettier.cjs - checksum: 10c0/b85828b08e7505716324e4245549b9205c0cacb25342a030ba8885aba2039a115dbcf75a0b7ca3b37bc9d101ee61fab8113fc69ca3359f2a226f1ecc07ad2e26 +"lightningcss-linux-arm64-gnu@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-linux-arm64-gnu@npm:1.33.0" + conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"proc-log@npm:^4.1.0, proc-log@npm:^4.2.0": - version: 4.2.0 - resolution: "proc-log@npm:4.2.0" - checksum: 10c0/17db4757c2a5c44c1e545170e6c70a26f7de58feb985091fb1763f5081cab3d01b181fb2dd240c9f4a4255a1d9227d163d5771b7e69c9e49a561692db865efb9 +"lightningcss-linux-arm64-musl@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-linux-arm64-musl@npm:1.33.0" + conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"promise-retry@npm:^2.0.1": - version: 2.0.1 - resolution: "promise-retry@npm:2.0.1" - dependencies: - err-code: "npm:^2.0.2" - retry: "npm:^0.12.0" - checksum: 10c0/9c7045a1a2928094b5b9b15336dcd2a7b1c052f674550df63cc3f36cd44028e5080448175b6f6ca32b642de81150f5e7b1a98b728f15cb069f2dd60ac2616b96 +"lightningcss-linux-x64-gnu@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-linux-x64-gnu@npm:1.33.0" + conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"prop-types@npm:^15.6.2, prop-types@npm:^15.8.1": - version: 15.8.1 - resolution: "prop-types@npm:15.8.1" - dependencies: - loose-envify: "npm:^1.4.0" - object-assign: "npm:^4.1.1" - react-is: "npm:^16.13.1" - checksum: 10c0/59ece7ca2fb9838031d73a48d4becb9a7cc1ed10e610517c7d8f19a1e02fa47f7c27d557d8a5702bec3cfeccddc853579832b43f449e54635803f277b1c78077 +"lightningcss-linux-x64-musl@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-linux-x64-musl@npm:1.33.0" + conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"proxy-addr@npm:~2.0.7": - version: 2.0.7 - resolution: "proxy-addr@npm:2.0.7" - dependencies: - forwarded: "npm:0.2.0" - ipaddr.js: "npm:1.9.1" - checksum: 10c0/c3eed999781a35f7fd935f398b6d8920b6fb00bbc14287bc6de78128ccc1a02c89b95b56742bf7cf0362cc333c61d138532049c7dedc7a328ef13343eff81210 +"lightningcss-win32-arm64-msvc@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-win32-arm64-msvc@npm:1.33.0" + conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"punycode@npm:^2.1.0": - version: 2.3.0 - resolution: "punycode@npm:2.3.0" - checksum: 10c0/8e6f7abdd3a6635820049e3731c623bbef3fedbf63bbc696b0d7237fdba4cefa069bc1fa62f2938b0fbae057550df7b5318f4a6bcece27f1907fc75c54160bee +"lightningcss-win32-x64-msvc@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-win32-x64-msvc@npm:1.33.0" + conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"qs@npm:6.13.0": - version: 6.13.0 - resolution: "qs@npm:6.13.0" +"lightningcss@npm:^1.33.0": + version: 1.33.0 + resolution: "lightningcss@npm:1.33.0" dependencies: - side-channel: "npm:^1.0.6" - checksum: 10c0/62372cdeec24dc83a9fb240b7533c0fdcf0c5f7e0b83343edd7310f0ab4c8205a5e7c56406531f2e47e1b4878a3821d652be4192c841de5b032ca83619d8f860 + detect-libc: "npm:^2.0.3" + lightningcss-android-arm64: "npm:1.33.0" + lightningcss-darwin-arm64: "npm:1.33.0" + lightningcss-darwin-x64: "npm:1.33.0" + lightningcss-freebsd-x64: "npm:1.33.0" + lightningcss-linux-arm-gnueabihf: "npm:1.33.0" + lightningcss-linux-arm64-gnu: "npm:1.33.0" + lightningcss-linux-arm64-musl: "npm:1.33.0" + lightningcss-linux-x64-gnu: "npm:1.33.0" + lightningcss-linux-x64-musl: "npm:1.33.0" + lightningcss-win32-arm64-msvc: "npm:1.33.0" + lightningcss-win32-x64-msvc: "npm:1.33.0" + dependenciesMeta: + lightningcss-android-arm64: + optional: true + lightningcss-darwin-arm64: + optional: true + lightningcss-darwin-x64: + optional: true + lightningcss-freebsd-x64: + optional: true + lightningcss-linux-arm-gnueabihf: + optional: true + lightningcss-linux-arm64-gnu: + optional: true + lightningcss-linux-arm64-musl: + optional: true + lightningcss-linux-x64-gnu: + optional: true + lightningcss-linux-x64-musl: + optional: true + lightningcss-win32-arm64-msvc: + optional: true + lightningcss-win32-x64-msvc: + optional: true + checksum: 10c0/ce1f8279fbae636dbf37fa6e7385d5f98ed881d72af3362f24afbd4685e19c1fcdfecf17e5dd77f2ebee3d0c23ade276230d85842d07292229a2cffba8ff20a3 languageName: node linkType: hard -"queue-microtask@npm:^1.2.2": - version: 1.2.3 - resolution: "queue-microtask@npm:1.2.3" - checksum: 10c0/900a93d3cdae3acd7d16f642c29a642aea32c2026446151f0778c62ac089d4b8e6c986811076e1ae180a694cedf077d453a11b58ff0a865629a4f82ab558e102 +"lines-and-columns@npm:^1.1.6": + version: 1.2.4 + resolution: "lines-and-columns@npm:1.2.4" + checksum: 10c0/3da6ee62d4cd9f03f5dc90b4df2540fb85b352081bee77fe4bbcd12c9000ead7f35e0a38b8d09a9bb99b13223446dd8689ff3c4959807620726d788701a83d2d languageName: node linkType: hard -"randombytes@npm:^2.1.0": - version: 2.1.0 - resolution: "randombytes@npm:2.1.0" +"locate-path@npm:^6.0.0": + version: 6.0.0 + resolution: "locate-path@npm:6.0.0" dependencies: - safe-buffer: "npm:^5.1.0" - checksum: 10c0/50395efda7a8c94f5dffab564f9ff89736064d32addf0cc7e8bf5e4166f09f8ded7a0849ca6c2d2a59478f7d90f78f20d8048bca3cdf8be09d8e8a10790388f3 + p-locate: "npm:^5.0.0" + checksum: 10c0/d3972ab70dfe58ce620e64265f90162d247e87159b6126b01314dd67be43d50e96a50b517bce2d9452a79409c7614054c277b5232377de50416564a77ac7aad3 languageName: node linkType: hard -"range-parser@npm:^1.2.1, range-parser@npm:~1.2.1": - version: 1.2.1 - resolution: "range-parser@npm:1.2.1" - checksum: 10c0/96c032ac2475c8027b7a4e9fe22dc0dfe0f6d90b85e496e0f016fbdb99d6d066de0112e680805075bd989905e2123b3b3d002765149294dce0c1f7f01fcc2ea0 +"loose-envify@npm:^1.4.0": + version: 1.4.0 + resolution: "loose-envify@npm:1.4.0" + dependencies: + js-tokens: "npm:^3.0.0 || ^4.0.0" + bin: + loose-envify: cli.js + checksum: 10c0/655d110220983c1a4b9c0c679a2e8016d4b67f6e9c7b5435ff5979ecdb20d0813f4dec0a08674fcbdd4846a3f07edbb50a36811fd37930b94aaa0d9daceb017e languageName: node linkType: hard -"raw-body@npm:^3.0.0": - version: 3.0.0 - resolution: "raw-body@npm:3.0.0" +"lowdb@npm:^7.0.1": + version: 7.0.1 + resolution: "lowdb@npm:7.0.1" dependencies: - bytes: "npm:3.1.2" - http-errors: "npm:2.0.0" - iconv-lite: "npm:0.6.3" - unpipe: "npm:1.0.0" - checksum: 10c0/f8daf4b724064a4811d118745a781ca0fb4676298b8adadfd6591155549cfea0a067523cf7dd3baeb1265fecc9ce5dfb2fc788c12c66b85202a336593ece0f87 + steno: "npm:^4.0.2" + checksum: 10c0/d61f554d6a507419772b07c91982bb72a0d3976dddef83e1d58fb24ff9049398962ec2c1917b3a863aedb46559081e0cd208d032ff3c43b1d59f41be9f4f4022 languageName: node linkType: hard -"react-dom@npm:^18.3.1": - version: 18.3.1 - resolution: "react-dom@npm:18.3.1" - dependencies: - loose-envify: "npm:^1.1.0" - scheduler: "npm:^0.23.2" - peerDependencies: - react: ^18.3.1 - checksum: 10c0/a752496c1941f958f2e8ac56239172296fcddce1365ce45222d04a1947e0cc5547df3e8447f855a81d6d39f008d7c32eab43db3712077f09e3f67c4874973e85 +"lru-cache@npm:^11.0.0": + version: 11.5.3 + resolution: "lru-cache@npm:11.5.3" + checksum: 10c0/2ad9bce6343a3544a1fd15364d8b35609aa52b2593d21fdc4877cabfe003b0f3deca4f9d6e9d13262f06e9e3d159de45c0a2f3edfc3adb46192333b5f146d8ed languageName: node linkType: hard -"react-dropzone@npm:^11.4.2": - version: 11.7.1 - resolution: "react-dropzone@npm:11.7.1" - dependencies: - attr-accept: "npm:^2.2.2" - file-selector: "npm:^0.4.0" - prop-types: "npm:^15.8.1" - peerDependencies: - react: ">= 16.8" - checksum: 10c0/81443f64dad24592f64ac804128df8e8875a54b10497b7690e8bae4525478e61b707e0a31129f0199829bc6ab05ff1c52070883e19cbf6f96e18ffc22d57da4c +"math-intrinsics@npm:^1.1.0": + version: 1.1.0 + resolution: "math-intrinsics@npm:1.1.0" + checksum: 10c0/7579ff94e899e2f76ab64491d76cf606274c874d8f2af4a442c016bd85688927fcfca157ba6bf74b08e9439dc010b248ce05b96cc7c126a354c3bae7fcb48b7f languageName: node linkType: hard -"react-is@npm:^16.13.1, react-is@npm:^16.7.0": - version: 16.13.1 - resolution: "react-is@npm:16.13.1" - checksum: 10c0/33977da7a5f1a287936a0c85639fec6ca74f4f15ef1e59a6bc20338fc73dc69555381e211f7a3529b8150a1f71e4225525b41b60b52965bda53ce7d47377ada1 +"media-typer@npm:^1.1.0": + version: 1.1.1 + resolution: "media-typer@npm:1.1.1" + checksum: 10c0/924644de220c3107fc53ec0299b7c1488503470265d32f1492535bd798cc369ba7ceaf9c2a4533361590dcf7208d1cc7b7032dbc1a5918773f5f5d912cab5211 languageName: node linkType: hard -"react-is@npm:^18.2.0": - version: 18.2.0 - resolution: "react-is@npm:18.2.0" - checksum: 10c0/6eb5e4b28028c23e2bfcf73371e72cd4162e4ac7ab445ddae2afe24e347a37d6dc22fae6e1748632cd43c6d4f9b8f86dcf26bf9275e1874f436d129952528ae0 +"merge-descriptors@npm:^2.0.0": + version: 2.0.0 + resolution: "merge-descriptors@npm:2.0.0" + checksum: 10c0/95389b7ced3f9b36fbdcf32eb946dc3dd1774c2fdf164609e55b18d03aa499b12bd3aae3a76c1c7185b96279e9803525550d3eb292b5224866060a288f335cb3 languageName: node linkType: hard -"react-refresh@npm:^0.14.2": - version: 0.14.2 - resolution: "react-refresh@npm:0.14.2" - checksum: 10c0/875b72ef56b147a131e33f2abd6ec059d1989854b3ff438898e4f9310bfcc73acff709445b7ba843318a953cb9424bcc2c05af2b3d80011cee28f25aef3e2ebb +"mime-db@npm:1.52.0": + version: 1.52.0 + resolution: "mime-db@npm:1.52.0" + checksum: 10c0/0557a01deebf45ac5f5777fe7740b2a5c309c6d62d40ceab4e23da9f821899ce7a900b7ac8157d4548ddbb7beffe9abc621250e6d182b0397ec7f10c7b91a5aa languageName: node linkType: hard -"react-router-dom@npm:^6.26.1": - version: 6.26.1 - resolution: "react-router-dom@npm:6.26.1" - dependencies: - "@remix-run/router": "npm:1.19.1" - react-router: "npm:6.26.1" - peerDependencies: - react: ">=16.8" - react-dom: ">=16.8" - checksum: 10c0/9d9d8ed54d1c95497c6fa35a6ab46992efeccf1cfc6f0f6089c6c9b040af3eae09568fbb80c690bae08051a955d92d7aa3a0e730f626eb69285114993d31d430 +"mime-db@npm:^1.54.0": + version: 1.54.0 + resolution: "mime-db@npm:1.54.0" + checksum: 10c0/8d907917bc2a90fa2df842cdf5dfeaf509adc15fe0531e07bb2f6ab15992416479015828d6a74200041c492e42cce3ebf78e5ce714388a0a538ea9c53eece284 languageName: node linkType: hard -"react-router@npm:6.26.1": - version: 6.26.1 - resolution: "react-router@npm:6.26.1" +"mime-types@npm:^3.0.0, mime-types@npm:^3.0.2": + version: 3.0.2 + resolution: "mime-types@npm:3.0.2" dependencies: - "@remix-run/router": "npm:1.19.1" - peerDependencies: - react: ">=16.8" - checksum: 10c0/463078e740462b42bb5ba8004448f33fc9e63778f432a4ed55c57b93c5b519e25fb17913ee8435b0fda33c6b9f75df8ef6fcb2c3a4f8db84fb546d202e29aa51 + mime-db: "npm:^1.54.0" + checksum: 10c0/35a0dd1035d14d185664f346efcdb72e93ef7a9b6e9ae808bd1f6358227010267fab52657b37562c80fc888ff76becb2b2938deb5e730818b7983bf8bd359767 languageName: node linkType: hard -"react-transition-group@npm:^4.4.5": - version: 4.4.5 - resolution: "react-transition-group@npm:4.4.5" +"mime-types@npm:~2.1.34": + version: 2.1.35 + resolution: "mime-types@npm:2.1.35" dependencies: - "@babel/runtime": "npm:^7.5.5" - dom-helpers: "npm:^5.0.1" - loose-envify: "npm:^1.4.0" - prop-types: "npm:^15.6.2" - peerDependencies: - react: ">=16.6.0" - react-dom: ">=16.6.0" - checksum: 10c0/2ba754ba748faefa15f87c96dfa700d5525054a0141de8c75763aae6734af0740e77e11261a1e8f4ffc08fd9ab78510122e05c21c2d79066c38bb6861a886c82 + mime-db: "npm:1.52.0" + checksum: 10c0/82fb07ec56d8ff1fc999a84f2f217aa46cb6ed1033fefaabd5785b9a974ed225c90dc72fff460259e66b95b73648596dbcc50d51ed69cdf464af2d237d3149b2 languageName: node linkType: hard -"react@npm:^18.3.1": - version: 18.3.1 - resolution: "react@npm:18.3.1" +"minimatch@npm:^10.2.2, minimatch@npm:^10.2.4, minimatch@npm:^10.2.5": + version: 10.2.6 + resolution: "minimatch@npm:10.2.6" dependencies: - loose-envify: "npm:^1.1.0" - checksum: 10c0/283e8c5efcf37802c9d1ce767f302dd569dd97a70d9bb8c7be79a789b9902451e0d16334b05d73299b20f048cbc3c7d288bbbde10b701fa194e2089c237dbea3 + brace-expansion: "npm:^5.0.8" + checksum: 10c0/4559a836243b98bd4d17ea9f7edae698717c76399eea7be374f3737f33164e4907f19e9726891ddeb122f750a5a7fa80d2ac43e851d6e5984dc4ff42ec127d3a languageName: node linkType: hard -"readdirp@npm:~3.6.0": - version: 3.6.0 - resolution: "readdirp@npm:3.6.0" - dependencies: - picomatch: "npm:^2.2.1" - checksum: 10c0/6fa848cf63d1b82ab4e985f4cf72bd55b7dcfd8e0a376905804e48c3634b7e749170940ba77b32804d5fe93b3cc521aa95a8d7e7d725f830da6d93f3669ce66b +"minipass@npm:^7.0.4, minipass@npm:^7.1.2, minipass@npm:^7.1.3": + version: 7.1.3 + resolution: "minipass@npm:7.1.3" + checksum: 10c0/539da88daca16533211ea5a9ee98dc62ff5742f531f54640dd34429e621955e91cc280a91a776026264b7f9f6735947629f920944e9c1558369e8bf22eb33fbb languageName: node linkType: hard -"reflect.getprototypeof@npm:^1.0.4": - version: 1.0.6 - resolution: "reflect.getprototypeof@npm:1.0.6" +"minizlib@npm:^3.1.0": + version: 3.1.0 + resolution: "minizlib@npm:3.1.0" dependencies: - call-bind: "npm:^1.0.7" - define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.1" - es-errors: "npm:^1.3.0" - get-intrinsic: "npm:^1.2.4" - globalthis: "npm:^1.0.3" - which-builtin-type: "npm:^1.1.3" - checksum: 10c0/baf4ef8ee6ff341600f4720b251cf5a6cb552d6a6ab0fdc036988c451bf16f920e5feb0d46bd4f530a5cce568f1f7aca2d77447ca798920749cfc52783c39b55 + minipass: "npm:^7.1.2" + checksum: 10c0/5aad75ab0090b8266069c9aabe582c021ae53eb33c6c691054a13a45db3b4f91a7fb1bd79151e6b4e9e9a86727b522527c0a06ec7d45206b745d54cd3097bcec languageName: node linkType: hard -"regenerate-unicode-properties@npm:^10.1.0": - version: 10.1.1 - resolution: "regenerate-unicode-properties@npm:10.1.1" +"mocha@npm:^12.0.3": + version: 12.0.3 + resolution: "mocha@npm:12.0.3" dependencies: - regenerate: "npm:^1.4.2" - checksum: 10c0/89adb5ee5ba081380c78f9057c02e156a8181969f6fcca72451efc45612e0c3df767b4333f8d8479c274d9c6fe52ec4854f0d8a22ef95dccbe87da8e5f2ac77d + browser-stdout: "npm:^1.3.1" + chokidar: "npm:^5.0.0" + debug: "npm:^4.3.5" + diff: "npm:^9.0.0" + find-up-simple: "npm:^1.0.1" + glob: "npm:^13.0.0" + is-path-inside: "npm:^4.0.0" + is-unicode-supported: "npm:^0.1.0" + js-yaml: "npm:^5.0.0" + minimatch: "npm:^10.2.2" + ms: "npm:^2.1.3" + picocolors: "npm:^1.1.1" + serialize-javascript: "npm:^7.1.1" + strip-json-comments: "npm:^5.0.3" + supports-color: "npm:^8.1.1" + workerpool: "npm:^10.0.0" + bin: + mocha: bin/mocha.js + checksum: 10c0/24dfc0c6f95524b81b1a97e1334d4c3bad4302b0c8909c2e15ff06ce2c0e4d6bd91a24187e7804dc37c1d167ce9c9aa722bc3c9c9da6b792510b4544581e2e21 languageName: node linkType: hard -"regenerate@npm:^1.4.2": - version: 1.4.2 - resolution: "regenerate@npm:1.4.2" - checksum: 10c0/f73c9eba5d398c818edc71d1c6979eaa05af7a808682749dd079f8df2a6d91a9b913db216c2c9b03e0a8ba2bba8701244a93f45211afbff691c32c7b275db1b8 +"ms@npm:^2.1.3": + version: 2.1.3 + resolution: "ms@npm:2.1.3" + checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 languageName: node linkType: hard -"regenerator-runtime@npm:^0.13.11": - version: 0.13.11 - resolution: "regenerator-runtime@npm:0.13.11" - checksum: 10c0/12b069dc774001fbb0014f6a28f11c09ebfe3c0d984d88c9bced77fdb6fedbacbca434d24da9ae9371bfbf23f754869307fb51a4c98a8b8b18e5ef748677ca24 +"nanoid@npm:^3.3.20": + version: 3.3.20 + resolution: "nanoid@npm:3.3.20" + bin: + nanoid: bin/nanoid.cjs + checksum: 10c0/856e8bf3bddb959d65997bd94908be030dbc5e6ed88fb3af2a638e51d1938f4d4f454c7c7231aba340775224f88b8cfd3ce1693c70471bfd05f5b6a2227c0c1e languageName: node linkType: hard -"regenerator-runtime@npm:^0.14.0": - version: 0.14.1 - resolution: "regenerator-runtime@npm:0.14.1" - checksum: 10c0/1b16eb2c4bceb1665c89de70dcb64126a22bc8eb958feef3cd68fe11ac6d2a4899b5cd1b80b0774c7c03591dc57d16631a7f69d2daa2ec98100e2f29f7ec4cc4 +"natural-compare@npm:^1.4.0": + version: 1.4.0 + resolution: "natural-compare@npm:1.4.0" + checksum: 10c0/f5f9a7974bfb28a91afafa254b197f0f22c684d4a1731763dda960d2c8e375b36c7d690e0d9dc8fba774c537af14a7e979129bca23d88d052fbeb9466955e447 languageName: node linkType: hard -"regenerator-transform@npm:^0.15.2": - version: 0.15.2 - resolution: "regenerator-transform@npm:0.15.2" - dependencies: - "@babel/runtime": "npm:^7.8.4" - checksum: 10c0/7cfe6931ec793269701994a93bab89c0cc95379191fad866270a7fea2adfec67ea62bb5b374db77058b60ba4509319d9b608664d0d288bd9989ca8dbd08fae90 +"negotiator@npm:0.6.3": + version: 0.6.3 + resolution: "negotiator@npm:0.6.3" + checksum: 10c0/3ec9fd413e7bf071c937ae60d572bc67155262068ed522cf4b3be5edbe6ddf67d095ec03a3a14ebf8fc8e95f8e1d61be4869db0dbb0de696f6b837358bd43fc2 languageName: node linkType: hard -"regexp.prototype.flags@npm:^1.5.1, regexp.prototype.flags@npm:^1.5.2": - version: 1.5.2 - resolution: "regexp.prototype.flags@npm:1.5.2" +"negotiator@npm:^1.0.0": + version: 1.1.0 + resolution: "negotiator@npm:1.1.0" dependencies: - call-bind: "npm:^1.0.6" - define-properties: "npm:^1.2.1" - es-errors: "npm:^1.3.0" - set-function-name: "npm:^2.0.1" - checksum: 10c0/0f3fc4f580d9c349f8b560b012725eb9c002f36daa0041b3fbf6f4238cb05932191a4d7d5db3b5e2caa336d5150ad0402ed2be81f711f9308fe7e1a9bf9bd552 + content-type: "npm:^2.1.0" + checksum: 10c0/656fa57de02f1a0c4c09f9e17eb78e8182547c07093e810ff8f16249b6ae31f2c546a3d457905e94f3276b25f0e2741ea1a3bc3912192932ed7e493adfea535e languageName: node linkType: hard -"regexpp@npm:^3.0.0": - version: 3.2.0 - resolution: "regexpp@npm:3.2.0" - checksum: 10c0/d1da82385c8754a1681416b90b9cca0e21b4a2babef159099b88f640637d789c69011d0bc94705dacab85b81133e929d027d85210e8b8b03f8035164dbc14710 +"node-gyp@npm:latest": + version: 13.1.0 + resolution: "node-gyp@npm:13.1.0" + dependencies: + env-paths: "npm:^2.2.0" + exponential-backoff: "npm:^3.1.1" + graceful-fs: "npm:^4.2.6" + nopt: "npm:^10.0.0" + proc-log: "npm:^7.0.0" + semver: "npm:^7.3.5" + tar: "npm:^7.5.7" + tinyglobby: "npm:^0.2.12" + undici: "npm:^8.4.1" + which: "npm:^7.0.0" + bin: + node-gyp: bin/node-gyp.js + checksum: 10c0/c6693e52e0e111b21f5cd8ff303b41b51f40338adcc79ac5c6a02399eba7039d50dc816dcafcc485870291a641f7cb75db8887a9bdd0de537fe635a3a81cba5b languageName: node linkType: hard -"regexpu-core@npm:^5.3.1": - version: 5.3.2 - resolution: "regexpu-core@npm:5.3.2" +"nopt@npm:^10.0.0": + version: 10.0.1 + resolution: "nopt@npm:10.0.1" dependencies: - "@babel/regjsgen": "npm:^0.8.0" - regenerate: "npm:^1.4.2" - regenerate-unicode-properties: "npm:^10.1.0" - regjsparser: "npm:^0.9.1" - unicode-match-property-ecmascript: "npm:^2.0.0" - unicode-match-property-value-ecmascript: "npm:^2.1.0" - checksum: 10c0/7945d5ab10c8bbed3ca383d4274687ea825aee4ab93a9c51c6e31e1365edd5ea807f6908f800ba017b66c462944ba68011164e7055207747ab651f8111ef3770 + abbrev: "npm:^5.0.0" + bin: + nopt: bin/nopt.js + checksum: 10c0/980d89257f9587f3e1f77877ddbf905d6aa3b738ec33e49a4fa1a059a0dd82eb28063982b150654a7ae9de386f2ead60e56172db7d37cf56de545f7392a2a26a languageName: node linkType: hard -"regjsparser@npm:^0.9.1": - version: 0.9.1 - resolution: "regjsparser@npm:0.9.1" - dependencies: - jsesc: "npm:~0.5.0" - bin: - regjsparser: bin/parser - checksum: 10c0/fe44fcf19a99fe4f92809b0b6179530e5ef313ff7f87df143b08ce9a2eb3c4b6189b43735d645be6e8f4033bfb015ed1ca54f0583bc7561bed53fd379feb8225 +"object-assign@npm:^4, object-assign@npm:^4.1.1": + version: 4.1.1 + resolution: "object-assign@npm:4.1.1" + checksum: 10c0/1f4df9945120325d041ccf7b86f31e8bcc14e73d29171e37a7903050e96b81323784ec59f93f102ec635bcf6fa8034ba3ea0a8c7e69fa202b87ae3b6cec5a414 languageName: node linkType: hard -"require-directory@npm:^2.1.1": - version: 2.1.1 - resolution: "require-directory@npm:2.1.1" - checksum: 10c0/83aa76a7bc1531f68d92c75a2ca2f54f1b01463cb566cf3fbc787d0de8be30c9dbc211d1d46be3497dac5785fe296f2dd11d531945ac29730643357978966e99 +"object-inspect@npm:^1.13.3, object-inspect@npm:^1.13.4": + version: 1.13.4 + resolution: "object-inspect@npm:1.13.4" + checksum: 10c0/d7f8711e803b96ea3191c745d6f8056ce1f2496e530e6a19a0e92d89b0fa3c76d910c31f0aa270432db6bd3b2f85500a376a83aaba849a8d518c8845b3211692 languageName: node linkType: hard -"require-from-string@npm:^2.0.2": - version: 2.0.2 - resolution: "require-from-string@npm:2.0.2" - checksum: 10c0/aaa267e0c5b022fc5fd4eef49d8285086b15f2a1c54b28240fdf03599cbd9c26049fee3eab894f2e1f6ca65e513b030a7c264201e3f005601e80c49fb2937ce2 +"on-finished@npm:^2.4.1": + version: 2.4.1 + resolution: "on-finished@npm:2.4.1" + dependencies: + ee-first: "npm:1.1.1" + checksum: 10c0/46fb11b9063782f2d9968863d9cbba33d77aa13c17f895f56129c274318b86500b22af3a160fe9995aa41317efcd22941b6eba747f718ced08d9a73afdb087b4 languageName: node linkType: hard -"reselect@npm:^4.1.6": - version: 4.1.7 - resolution: "reselect@npm:4.1.7" - checksum: 10c0/d94b6fec351b5f612d2424d8e7aaee9b62cb3a83a345c4e79e09aa35fb9874923a7e82ad9a2cb25491c1522ed07a1c32df9e585ae67c48630613133c7aa14ec7 +"once@npm:^1.4.0": + version: 1.4.0 + resolution: "once@npm:1.4.0" + dependencies: + wrappy: "npm:1" + checksum: 10c0/5d48aca287dfefabd756621c5dfce5c91a549a93e9fdb7b8246bc4c4790aa2ec17b34a260530474635147aeb631a2dcc8b32c613df0675f96041cbb8244517d0 languageName: node linkType: hard -"resolve-from@npm:^4.0.0": - version: 4.0.0 - resolution: "resolve-from@npm:4.0.0" - checksum: 10c0/8408eec31a3112ef96e3746c37be7d64020cda07c03a920f5024e77290a218ea758b26ca9529fd7b1ad283947f34b2291c1c0f6aa0ed34acfdda9c6014c8d190 +"optionator@npm:^0.9.3": + version: 0.9.4 + resolution: "optionator@npm:0.9.4" + dependencies: + deep-is: "npm:^0.1.3" + fast-levenshtein: "npm:^2.0.6" + levn: "npm:^0.4.1" + prelude-ls: "npm:^1.2.1" + type-check: "npm:^0.4.0" + word-wrap: "npm:^1.2.5" + checksum: 10c0/4afb687a059ee65b61df74dfe87d8d6815cd6883cb8b3d5883a910df72d0f5d029821f37025e4bccf4048873dbdb09acc6d303d27b8f76b1a80dd5a7d5334675 languageName: node linkType: hard -"resolve-pkg-maps@npm:^1.0.0": - version: 1.0.0 - resolution: "resolve-pkg-maps@npm:1.0.0" - checksum: 10c0/fb8f7bbe2ca281a73b7ef423a1cbc786fb244bd7a95cbe5c3fba25b27d327150beca8ba02f622baea65919a57e061eb5005204daa5f93ed590d9b77463a567ab +"p-limit@npm:^3.0.2": + version: 3.1.0 + resolution: "p-limit@npm:3.1.0" + dependencies: + yocto-queue: "npm:^0.1.0" + checksum: 10c0/9db675949dbdc9c3763c89e748d0ef8bdad0afbb24d49ceaf4c46c02c77d30db4e0652ed36d0a0a7a95154335fab810d95c86153105bb73b3a90448e2bb14e1a languageName: node linkType: hard -"resolve@npm:^1.10.1, resolve@npm:^1.14.2, resolve@npm:^1.22.4": - version: 1.22.8 - resolution: "resolve@npm:1.22.8" +"p-locate@npm:^5.0.0": + version: 5.0.0 + resolution: "p-locate@npm:5.0.0" dependencies: - is-core-module: "npm:^2.13.0" - path-parse: "npm:^1.0.7" - supports-preserve-symlinks-flag: "npm:^1.0.0" - bin: - resolve: bin/resolve - checksum: 10c0/07e179f4375e1fd072cfb72ad66d78547f86e6196c4014b31cb0b8bb1db5f7ca871f922d08da0fbc05b94e9fd42206f819648fa3b5b873ebbc8e1dc68fec433a + p-limit: "npm:^3.0.2" + checksum: 10c0/2290d627ab7903b8b70d11d384fee714b797f6040d9278932754a6860845c4d3190603a0772a663c8cb5a7b21d1b16acb3a6487ebcafa9773094edc3dfe6009a languageName: node linkType: hard -"resolve@npm:^1.19.0": - version: 1.22.1 - resolution: "resolve@npm:1.22.1" +"package-json-from-dist@npm:^1.0.1": + version: 1.0.1 + resolution: "package-json-from-dist@npm:1.0.1" + checksum: 10c0/62ba2785eb655fec084a257af34dbe24292ab74516d6aecef97ef72d4897310bc6898f6c85b5cd22770eaa1ce60d55a0230e150fb6a966e3ecd6c511e23d164b + languageName: node + linkType: hard + +"parent-module@npm:^1.0.0": + version: 1.0.1 + resolution: "parent-module@npm:1.0.1" dependencies: - is-core-module: "npm:^2.9.0" - path-parse: "npm:^1.0.7" - supports-preserve-symlinks-flag: "npm:^1.0.0" - bin: - resolve: bin/resolve - checksum: 10c0/6d58b1cb40f3fc80b9e45dd799d84cdc3829a993e4b9fa3b59d331e1dfacd0870e1851f4d0eb549d68c796e0b7087b43d1aec162653ccccff9e18191221a6e7d + callsites: "npm:^3.0.0" + checksum: 10c0/c63d6e80000d4babd11978e0d3fee386ca7752a02b035fd2435960ffaa7219dc42146f07069fb65e6e8bf1caef89daf9af7535a39bddf354d78bf50d8294f556 languageName: node linkType: hard -"resolve@npm:^2.0.0-next.5": - version: 2.0.0-next.5 - resolution: "resolve@npm:2.0.0-next.5" +"parse-json@npm:^5.0.0": + version: 5.2.0 + resolution: "parse-json@npm:5.2.0" dependencies: - is-core-module: "npm:^2.13.0" - path-parse: "npm:^1.0.7" - supports-preserve-symlinks-flag: "npm:^1.0.0" - bin: - resolve: bin/resolve - checksum: 10c0/a6c33555e3482ea2ec4c6e3d3bf0d78128abf69dca99ae468e64f1e30acaa318fd267fb66c8836b04d558d3e2d6ed875fe388067e7d8e0de647d3c21af21c43a + "@babel/code-frame": "npm:^7.0.0" + error-ex: "npm:^1.3.1" + json-parse-even-better-errors: "npm:^2.3.0" + lines-and-columns: "npm:^1.1.6" + checksum: 10c0/77947f2253005be7a12d858aedbafa09c9ae39eb4863adf330f7b416ca4f4a08132e453e08de2db46459256fb66afaac5ee758b44fe6541b7cdaf9d252e59585 languageName: node linkType: hard -"resolve@patch:resolve@npm%3A^1.10.1#optional!builtin, resolve@patch:resolve@npm%3A^1.14.2#optional!builtin, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin": - version: 1.22.8 - resolution: "resolve@patch:resolve@npm%3A1.22.8#optional!builtin::version=1.22.8&hash=c3c19d" - dependencies: - is-core-module: "npm:^2.13.0" - path-parse: "npm:^1.0.7" - supports-preserve-symlinks-flag: "npm:^1.0.0" - bin: - resolve: bin/resolve - checksum: 10c0/0446f024439cd2e50c6c8fa8ba77eaa8370b4180f401a96abf3d1ebc770ac51c1955e12764cde449fde3fff480a61f84388e3505ecdbab778f4bef5f8212c729 +"parseurl@npm:^1.3.3": + version: 1.3.3 + resolution: "parseurl@npm:1.3.3" + checksum: 10c0/90dd4760d6f6174adb9f20cf0965ae12e23879b5f5464f38e92fce8073354341e4b3b76fa3d878351efe7d01e617121955284cfd002ab087fba1a0726ec0b4f5 languageName: node linkType: hard -"resolve@patch:resolve@npm%3A^1.19.0#optional!builtin": - version: 1.22.1 - resolution: "resolve@patch:resolve@npm%3A1.22.1#optional!builtin::version=1.22.1&hash=c3c19d" - dependencies: - is-core-module: "npm:^2.9.0" - path-parse: "npm:^1.0.7" - supports-preserve-symlinks-flag: "npm:^1.0.0" - bin: - resolve: bin/resolve - checksum: 10c0/0d8ccceba5537769c42aa75e4aa75ae854aac866a11d7e9ffdb1663f0158ee646a0d48fc2818ed5e7fb364d64220a1fb9092a160e11e00cbdd5fbab39a13092c +"path-exists@npm:^4.0.0": + version: 4.0.0 + resolution: "path-exists@npm:4.0.0" + checksum: 10c0/8c0bd3f5238188197dc78dced15207a4716c51cc4e3624c44fc97acf69558f5ebb9a2afff486fe1b4ee148e0c133e96c5e11a9aa5c48a3006e3467da070e5e1b languageName: node linkType: hard -"resolve@patch:resolve@npm%3A^2.0.0-next.5#optional!builtin": - version: 2.0.0-next.5 - resolution: "resolve@patch:resolve@npm%3A2.0.0-next.5#optional!builtin::version=2.0.0-next.5&hash=c3c19d" - dependencies: - is-core-module: "npm:^2.13.0" - path-parse: "npm:^1.0.7" - supports-preserve-symlinks-flag: "npm:^1.0.0" - bin: - resolve: bin/resolve - checksum: 10c0/78ad6edb8309a2bfb720c2c1898f7907a37f858866ce11a5974643af1203a6a6e05b2fa9c53d8064a673a447b83d42569260c306d43628bff5bb101969708355 +"path-key@npm:^3.1.0": + version: 3.1.1 + resolution: "path-key@npm:3.1.1" + checksum: 10c0/748c43efd5a569c039d7a00a03b58eecd1d75f3999f5a28303d75f521288df4823bc057d8784eb72358b2895a05f29a070bc9f1f17d28226cc4e62494cc58c4c languageName: node linkType: hard -"retry@npm:^0.12.0": - version: 0.12.0 - resolution: "retry@npm:0.12.0" - checksum: 10c0/59933e8501727ba13ad73ef4a04d5280b3717fd650408460c987392efe9d7be2040778ed8ebe933c5cbd63da3dcc37919c141ef8af0a54a6e4fca5a2af177bfe +"path-parse@npm:^1.0.7": + version: 1.0.7 + resolution: "path-parse@npm:1.0.7" + checksum: 10c0/11ce261f9d294cc7a58d6a574b7f1b935842355ec66fba3c3fd79e0f036462eaf07d0aa95bb74ff432f9afef97ce1926c720988c6a7451d8a584930ae7de86e1 languageName: node linkType: hard -"reusify@npm:^1.0.4": - version: 1.0.4 - resolution: "reusify@npm:1.0.4" - checksum: 10c0/c19ef26e4e188f408922c46f7ff480d38e8dfc55d448310dfb518736b23ed2c4f547fb64a6ed5bdba92cd7e7ddc889d36ff78f794816d5e71498d645ef476107 +"path-scurry@npm:^2.0.2": + version: 2.0.2 + resolution: "path-scurry@npm:2.0.2" + dependencies: + lru-cache: "npm:^11.0.0" + minipass: "npm:^7.1.2" + checksum: 10c0/b35ad37cf6557a87fd057121ce2be7695380c9138d93e87ae928609da259ea0a170fac6f3ef1eb3ece8a068e8b7f2f3adf5bb2374cf4d4a57fe484954fcc9482 languageName: node linkType: hard -"rimraf@npm:^4.1.1": - version: 4.1.2 - resolution: "rimraf@npm:4.1.2" - bin: - rimraf: dist/cjs/src/bin.js - checksum: 10c0/71a492251d9553cb93a0e48029e70ab4d961bf56ceaeaed05dc0d7b7283f611bcc00ae66dfcbe428011ee445abc8fef849b72c6fd4ef108970954b97aca806b3 - languageName: node - linkType: hard - -"rollup@npm:^4.20.0": - version: 4.21.2 - resolution: "rollup@npm:4.21.2" - dependencies: - "@rollup/rollup-android-arm-eabi": "npm:4.21.2" - "@rollup/rollup-android-arm64": "npm:4.21.2" - "@rollup/rollup-darwin-arm64": "npm:4.21.2" - "@rollup/rollup-darwin-x64": "npm:4.21.2" - "@rollup/rollup-linux-arm-gnueabihf": "npm:4.21.2" - "@rollup/rollup-linux-arm-musleabihf": "npm:4.21.2" - "@rollup/rollup-linux-arm64-gnu": "npm:4.21.2" - "@rollup/rollup-linux-arm64-musl": "npm:4.21.2" - "@rollup/rollup-linux-powerpc64le-gnu": "npm:4.21.2" - "@rollup/rollup-linux-riscv64-gnu": "npm:4.21.2" - "@rollup/rollup-linux-s390x-gnu": "npm:4.21.2" - "@rollup/rollup-linux-x64-gnu": "npm:4.21.2" - "@rollup/rollup-linux-x64-musl": "npm:4.21.2" - "@rollup/rollup-win32-arm64-msvc": "npm:4.21.2" - "@rollup/rollup-win32-ia32-msvc": "npm:4.21.2" - "@rollup/rollup-win32-x64-msvc": "npm:4.21.2" - "@types/estree": "npm:1.0.5" - fsevents: "npm:~2.3.2" - dependenciesMeta: - "@rollup/rollup-android-arm-eabi": - optional: true - "@rollup/rollup-android-arm64": - optional: true - "@rollup/rollup-darwin-arm64": - optional: true - "@rollup/rollup-darwin-x64": - optional: true - "@rollup/rollup-linux-arm-gnueabihf": - optional: true - "@rollup/rollup-linux-arm-musleabihf": - optional: true - "@rollup/rollup-linux-arm64-gnu": - optional: true - "@rollup/rollup-linux-arm64-musl": - optional: true - "@rollup/rollup-linux-powerpc64le-gnu": - optional: true - "@rollup/rollup-linux-riscv64-gnu": - optional: true - "@rollup/rollup-linux-s390x-gnu": - optional: true - "@rollup/rollup-linux-x64-gnu": - optional: true - "@rollup/rollup-linux-x64-musl": - optional: true - "@rollup/rollup-win32-arm64-msvc": - optional: true - "@rollup/rollup-win32-ia32-msvc": - optional: true - "@rollup/rollup-win32-x64-msvc": - optional: true - fsevents: - optional: true - bin: - rollup: dist/bin/rollup - checksum: 10c0/c9d97f7a21cde110371b2e890a31a996fee09b81e639e79372b962a9638ae653d2d24186b94632fc5dfab8a0582e1d0639dfe34b8b75051facd86915a9585a5f +"path-to-regexp@npm:^8.0.0": + version: 8.4.2 + resolution: "path-to-regexp@npm:8.4.2" + checksum: 10c0/05b115c49b47ad252ce05faa32930f643f23769c68b8bcfe78ad833545140c48bbffb3266986d6c8d5db13a64cf12e07e0d72d9882cab830efeefa553533ebaf languageName: node linkType: hard -"router@npm:^2.0.0": - version: 2.0.0 - resolution: "router@npm:2.0.0" - dependencies: - array-flatten: "npm:3.0.0" - is-promise: "npm:4.0.0" - methods: "npm:~1.1.2" - parseurl: "npm:~1.3.3" - path-to-regexp: "npm:^8.0.0" - setprototypeof: "npm:1.2.0" - utils-merge: "npm:1.0.1" - checksum: 10c0/6d5345a98c7f9b4427599478701d3cbff4f92954dce870c81036e12ae5f204b570507821c00cdb30b39365b91bc2b3830e95aa11fc181476030faa25fafaca84 +"path-type@npm:^4.0.0": + version: 4.0.0 + resolution: "path-type@npm:4.0.0" + checksum: 10c0/666f6973f332f27581371efaf303fd6c272cc43c2057b37aa99e3643158c7e4b2626549555d88626e99ea9e046f82f32e41bbde5f1508547e9a11b149b52387c languageName: node linkType: hard -"run-parallel@npm:^1.1.9": - version: 1.2.0 - resolution: "run-parallel@npm:1.2.0" - dependencies: - queue-microtask: "npm:^1.2.2" - checksum: 10c0/200b5ab25b5b8b7113f9901bfe3afc347e19bb7475b267d55ad0eb86a62a46d77510cb0f232507c9e5d497ebda569a08a9867d0d14f57a82ad5564d991588b39 +"picocolors@npm:^1.1.1": + version: 1.1.1 + resolution: "picocolors@npm:1.1.1" + checksum: 10c0/e2e3e8170ab9d7c7421969adaa7e1b31434f789afb9b3f115f6b96d91945041ac3ceb02e9ec6fe6510ff036bcc0bf91e69a1772edc0b707e12b19c0f2d6bcf58 languageName: node linkType: hard -"safe-array-concat@npm:^1.1.2": - version: 1.1.2 - resolution: "safe-array-concat@npm:1.1.2" - dependencies: - call-bind: "npm:^1.0.7" - get-intrinsic: "npm:^1.2.4" - has-symbols: "npm:^1.0.3" - isarray: "npm:^2.0.5" - checksum: 10c0/12f9fdb01c8585e199a347eacc3bae7b5164ae805cdc8c6707199dbad5b9e30001a50a43c4ee24dc9ea32dbb7279397850e9208a7e217f4d8b1cf5d90129dec9 +"picomatch@npm:^4.0.4, picomatch@npm:^4.0.7": + version: 4.0.7 + resolution: "picomatch@npm:4.0.7" + checksum: 10c0/beb6ae02c43ae44e84883b90830196d9046b1726ead292adcf7f57945e0bb0d992d68563d87e03b484b6f3c9a5c6defda7523477f047d7f0e663f126cc01787f languageName: node linkType: hard -"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.1.0": - version: 5.2.1 - resolution: "safe-buffer@npm:5.2.1" - checksum: 10c0/6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3 +"postcss@npm:^8.5.29": + version: 8.5.30 + resolution: "postcss@npm:8.5.30" + dependencies: + nanoid: "npm:^3.3.20" + picocolors: "npm:^1.1.1" + source-map-js: "npm:^1.2.2" + checksum: 10c0/219cdc50ba3f2b6d0f8b779c4939514cb9d9f53cc9eb92dae1a06f2d6eb910b2ce339a3ee93e7c893b89e50241509fd92c04f0639af1cdf41a1e13983a476029 languageName: node linkType: hard -"safe-regex-test@npm:^1.0.3": - version: 1.0.3 - resolution: "safe-regex-test@npm:1.0.3" - dependencies: - call-bind: "npm:^1.0.6" - es-errors: "npm:^1.3.0" - is-regex: "npm:^1.1.4" - checksum: 10c0/900bf7c98dc58f08d8523b7012b468e4eb757afa624f198902c0643d7008ba777b0bdc35810ba0b758671ce887617295fb742b3f3968991b178ceca54cb07603 +"postgres@npm:^3.4.9": + version: 3.4.9 + resolution: "postgres@npm:3.4.9" + checksum: 10c0/b767dd2258df0f6523ce1ec0bce0a87e93c6fb961c97be0059f8ea2bc4c57c6057689258361a0988f0bb61d636483d35f0c591620566218f7083c78ab7955862 languageName: node linkType: hard -"safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0": - version: 2.1.2 - resolution: "safer-buffer@npm:2.1.2" - checksum: 10c0/7e3c8b2e88a1841c9671094bbaeebd94448111dd90a81a1f606f3f67708a6ec57763b3b47f06da09fc6054193e0e6709e77325415dc8422b04497a8070fa02d4 +"prelude-ls@npm:^1.2.1": + version: 1.2.1 + resolution: "prelude-ls@npm:1.2.1" + checksum: 10c0/b00d617431e7886c520a6f498a2e14c75ec58f6d93ba48c3b639cf241b54232d90daa05d83a9e9b9fef6baa63cb7e1e4602c2372fea5bc169668401eb127d0cd languageName: node linkType: hard -"scheduler@npm:^0.23.2": - version: 0.23.2 - resolution: "scheduler@npm:0.23.2" +"prettier-linter-helpers@npm:^1.0.1": + version: 1.0.1 + resolution: "prettier-linter-helpers@npm:1.0.1" dependencies: - loose-envify: "npm:^1.1.0" - checksum: 10c0/26383305e249651d4c58e6705d5f8425f153211aef95f15161c151f7b8de885f24751b377e4a0b3dd42cce09aad3f87a61dab7636859c0d89b7daf1a1e2a5c78 + fast-diff: "npm:^1.1.2" + checksum: 10c0/91cea965681bc5f62c9d26bd3ca6358b81557261d4802e96ec1cf0acbd99d4b61632d53320cd2c3ec7d7f7805a81345644108a41ef46ddc9688e783a9ac792d1 languageName: node linkType: hard -"semver@npm:^6.1.0, semver@npm:^6.3.1": - version: 6.3.1 - resolution: "semver@npm:6.3.1" +"prettier@npm:^3.9.10": + version: 3.9.10 + resolution: "prettier@npm:3.9.10" bin: - semver: bin/semver.js - checksum: 10c0/e3d79b609071caa78bcb6ce2ad81c7966a46a7431d9d58b8800cfa9cb6a63699b3899a0e4bcce36167a284578212d9ae6942b6929ba4aa5015c079a67751d42d + prettier: bin/prettier.cjs + checksum: 10c0/0b3531593aa6fb36ac6baf629cb058bda472c59a6938774f931fe55283a07c62e6f56e621b043a82d8a3e21fc284c08c34f9e539fc70072635f79b7bae1a1067 languageName: node linkType: hard -"semver@npm:^6.3.0": - version: 6.3.0 - resolution: "semver@npm:6.3.0" - bin: - semver: ./bin/semver.js - checksum: 10c0/1f4959e15bcfbaf727e964a4920f9260141bb8805b399793160da4e7de128e42a7d1f79c1b7d5cd21a6073fba0d55feb9966f5fef3e5ccb8e1d7ead3d7527458 +"proc-log@npm:^7.0.0": + version: 7.0.0 + resolution: "proc-log@npm:7.0.0" + checksum: 10c0/b89c2d862604f35fec795477b0c7e376feab3ba0d4f4d291c4e959567442697cf451ac557d0623c1cc38af45a78128b983410f397a10c5d3a67f76c33de4754b languageName: node linkType: hard -"semver@npm:^7.3.5": - version: 7.3.8 - resolution: "semver@npm:7.3.8" +"prop-types@npm:^15.6.2, prop-types@npm:^15.8.1": + version: 15.8.1 + resolution: "prop-types@npm:15.8.1" dependencies: - lru-cache: "npm:^6.0.0" - bin: - semver: bin/semver.js - checksum: 10c0/7e581d679530db31757301c2117721577a2bb36a301a443aac833b8efad372cda58e7f2a464fe4412ae1041cc1f63a6c1fe0ced8c57ce5aca1e0b57bb0d627b9 + loose-envify: "npm:^1.4.0" + object-assign: "npm:^4.1.1" + react-is: "npm:^16.13.1" + checksum: 10c0/59ece7ca2fb9838031d73a48d4becb9a7cc1ed10e610517c7d8f19a1e02fa47f7c27d557d8a5702bec3cfeccddc853579832b43f449e54635803f277b1c78077 languageName: node linkType: hard -"semver@npm:^7.3.7, semver@npm:^7.6.0": - version: 7.6.3 - resolution: "semver@npm:7.6.3" - bin: - semver: bin/semver.js - checksum: 10c0/88f33e148b210c153873cb08cfe1e281d518aaa9a666d4d148add6560db5cd3c582f3a08ccb91f38d5f379ead256da9931234ed122057f40bb5766e65e58adaf +"proxy-addr@npm:^2.0.8": + version: 2.0.8 + resolution: "proxy-addr@npm:2.0.8" + dependencies: + forwarded: "npm:0.2.0" + ipaddr.js: "npm:1.9.1" + checksum: 10c0/58398f36e5b1f72b1011ca21844c0c41a9a3b6f2f0d101bcdb19abb3f3dd9626b27a96e53da777ded901eb67faed0304a4bc1d435b235e436f66f8e5e1fe1f5e languageName: node linkType: hard -"send@npm:^1.0.0, send@npm:^1.1.0": - version: 1.1.0 - resolution: "send@npm:1.1.0" - dependencies: - debug: "npm:^4.3.5" - destroy: "npm:^1.2.0" - encodeurl: "npm:^2.0.0" - escape-html: "npm:^1.0.3" - etag: "npm:^1.8.1" - fresh: "npm:^0.5.2" - http-errors: "npm:^2.0.0" - mime-types: "npm:^2.1.35" - ms: "npm:^2.1.3" - on-finished: "npm:^2.4.1" - range-parser: "npm:^1.2.1" - statuses: "npm:^2.0.1" - checksum: 10c0/0d73408bccfd008bb50cb97225434cf565f653b66cb7961befa962a321a936eaefa6c481a1a9c30606f341afb1f08d990bcbf44949f48a68e06d63344eb91105 +"punycode@npm:^2.1.0": + version: 2.3.1 + resolution: "punycode@npm:2.3.1" + checksum: 10c0/14f76a8206bc3464f794fb2e3d3cc665ae416c01893ad7a02b23766eb07159144ee612ad67af5e84fa4479ccfe67678c4feb126b0485651b302babf66f04f9e9 languageName: node linkType: hard -"serialize-javascript@npm:^6.0.2": - version: 6.0.2 - resolution: "serialize-javascript@npm:6.0.2" +"qified@npm:^0.10.1": + version: 0.10.1 + resolution: "qified@npm:0.10.1" dependencies: - randombytes: "npm:^2.1.0" - checksum: 10c0/2dd09ef4b65a1289ba24a788b1423a035581bef60817bea1f01eda8e3bda623f86357665fe7ac1b50f6d4f583f97db9615b3f07b2a2e8cbcb75033965f771dd2 + hookified: "npm:^2.1.1" + checksum: 10c0/4a39d45492c65a4b9795381acede1bd566028ce9764ead5b41d776b391a898bf0855dd2b59d724a6711fe253ba3cacf7c9a8832887cf9dc6d48a2c5e4997c82d languageName: node linkType: hard -"serve-static@npm:^2.1.0": - version: 2.1.0 - resolution: "serve-static@npm:2.1.0" +"qs@npm:^6.15.2, qs@npm:^6.16.0": + version: 6.16.1 + resolution: "qs@npm:6.16.1" dependencies: - encodeurl: "npm:^2.0.0" - escape-html: "npm:^1.0.3" - parseurl: "npm:^1.3.3" - send: "npm:^1.0.0" - checksum: 10c0/f5b36fd3a763cdb4bf367c9be2eff587c0e52685fcf9109aa28428457c4aad0f83d8b6a1bf4929488963270026f3647f2d221c898e6dbdf9f4f5b09ed0b1f51a + es-define-property: "npm:^1.0.1" + side-channel: "npm:^1.1.1" + checksum: 10c0/84ee9124b251ab2953132fbea1d70c966aa6e6496cccabedbbb83afa412bf4177bddebc779ca3ddafe23da83b2f593d530b41be6a726e971404fabb20ff22c94 languageName: node linkType: hard -"set-function-length@npm:^1.2.1": - version: 1.2.2 - resolution: "set-function-length@npm:1.2.2" - dependencies: - define-data-property: "npm:^1.1.4" - es-errors: "npm:^1.3.0" - function-bind: "npm:^1.1.2" - get-intrinsic: "npm:^1.2.4" - gopd: "npm:^1.0.1" - has-property-descriptors: "npm:^1.0.2" - checksum: 10c0/82850e62f412a258b71e123d4ed3873fa9377c216809551192bb6769329340176f109c2eeae8c22a8d386c76739855f78e8716515c818bcaef384b51110f0f3c +"range-parser@npm:^1.2.1": + version: 1.3.0 + resolution: "range-parser@npm:1.3.0" + checksum: 10c0/295494bb6685f9ab50f41a5f4fa5ce445aeeb9673b491bf178460fcc3a812dcf0d164cf1c83074f13a71a87d91ead5e097afd53e0630bc49a5101ed60f2a7529 languageName: node linkType: hard -"set-function-name@npm:^2.0.1, set-function-name@npm:^2.0.2": - version: 2.0.2 - resolution: "set-function-name@npm:2.0.2" +"raw-body@npm:^3.0.2": + version: 3.0.2 + resolution: "raw-body@npm:3.0.2" dependencies: - define-data-property: "npm:^1.1.4" - es-errors: "npm:^1.3.0" - functions-have-names: "npm:^1.2.3" - has-property-descriptors: "npm:^1.0.2" - checksum: 10c0/fce59f90696c450a8523e754abb305e2b8c73586452619c2bad5f7bf38c7b6b4651895c9db895679c5bef9554339cf3ef1c329b66ece3eda7255785fbe299316 + bytes: "npm:~3.1.2" + http-errors: "npm:~2.0.1" + iconv-lite: "npm:~0.7.0" + unpipe: "npm:~1.0.0" + checksum: 10c0/d266678d08e1e7abea62c0ce5864344e980fa81c64f6b481e9842c5beaed2cdcf975f658a3ccd67ad35fc919c1f6664ccc106067801850286a6cbe101de89f29 languageName: node linkType: hard -"setprototypeof@npm:1.2.0": - version: 1.2.0 - resolution: "setprototypeof@npm:1.2.0" - checksum: 10c0/68733173026766fa0d9ecaeb07f0483f4c2dc70ca376b3b7c40b7cda909f94b0918f6c5ad5ce27a9160bdfb475efaa9d5e705a11d8eaae18f9835d20976028bc +"react-dom@npm:^19.3.1": + version: 19.3.1 + resolution: "react-dom@npm:19.3.1" + dependencies: + scheduler: "npm:^0.28.0" + peerDependencies: + react: ^19.3.1 + checksum: 10c0/382700916313b2ebc46acee80642f300bb8601eac6a9e7fefade5b8b30e66b05bf3aaa56a3c964589fcd578d27b486e847470c2697d879264e766661ff49fac7 languageName: node linkType: hard -"shebang-command@npm:^2.0.0": - version: 2.0.0 - resolution: "shebang-command@npm:2.0.0" +"react-dropzone@npm:^20.1.4": + version: 20.1.4 + resolution: "react-dropzone@npm:20.1.4" dependencies: - shebang-regex: "npm:^3.0.0" - checksum: 10c0/a41692e7d89a553ef21d324a5cceb5f686d1f3c040759c50aab69688634688c5c327f26f3ecf7001ebfd78c01f3c7c0a11a7c8bfd0a8bc9f6240d4f40b224e4e + attr-accept: "npm:^4.0.0" + file-selector: "npm:^5.0.1" + peerDependencies: + "@types/react": "*" + react: ">= 18" + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/7a7db2101054d3f2aec404f28fc97a57b236691c97b02096cf2c5849c258b642c776674e8ebcf302ecfb7638ef7be6f602d9abde963e65fd7037ac78d85c90fe languageName: node linkType: hard -"shebang-regex@npm:^3.0.0": - version: 3.0.0 - resolution: "shebang-regex@npm:3.0.0" - checksum: 10c0/1dbed0726dd0e1152a92696c76c7f06084eb32a90f0528d11acd764043aacf76994b2fb30aa1291a21bd019d6699164d048286309a278855ee7bec06cf6fb690 +"react-is@npm:^16.13.1, react-is@npm:^16.7.0": + version: 16.13.1 + resolution: "react-is@npm:16.13.1" + checksum: 10c0/33977da7a5f1a287936a0c85639fec6ca74f4f15ef1e59a6bc20338fc73dc69555381e211f7a3529b8150a1f71e4225525b41b60b52965bda53ce7d47377ada1 languageName: node linkType: hard -"side-channel@npm:^1.0.4": - version: 1.0.4 - resolution: "side-channel@npm:1.0.4" - dependencies: - call-bind: "npm:^1.0.0" - get-intrinsic: "npm:^1.0.2" - object-inspect: "npm:^1.9.0" - checksum: 10c0/054a5d23ee35054b2c4609b9fd2a0587760737782b5d765a9c7852264710cc39c6dcb56a9bbd6c12cd84071648aea3edb2359d2f6e560677eedadce511ac1da5 +"react-is@npm:^19.2.3": + version: 19.3.1 + resolution: "react-is@npm:19.3.1" + checksum: 10c0/428e02d986d56f1859c57756084489f6d2900fd686905003c405b32f1168738b78772bfa4bb6c706f5ac821199171522c28bf6434281f524f901459390a19606 languageName: node linkType: hard -"side-channel@npm:^1.0.6": - version: 1.0.6 - resolution: "side-channel@npm:1.0.6" +"react-router-dom@npm:^7.18.4": + version: 7.18.4 + resolution: "react-router-dom@npm:7.18.4" dependencies: - call-bind: "npm:^1.0.7" - es-errors: "npm:^1.3.0" - get-intrinsic: "npm:^1.2.4" - object-inspect: "npm:^1.13.1" - checksum: 10c0/d2afd163dc733cc0a39aa6f7e39bf0c436293510dbccbff446733daeaf295857dbccf94297092ec8c53e2503acac30f0b78830876f0485991d62a90e9cad305f + react-router: "npm:7.18.4" + peerDependencies: + react: ">=18" + react-dom: ">=18" + checksum: 10c0/b337acffb669fd977423e3ef2a0551a5d68c2447c718942b232cae367fd599723525e412b4ad0d08607189f962571dfc7ef0373d47fcf0ae301a860510e5e1f1 languageName: node linkType: hard -"signal-exit@npm:^4.0.1": - version: 4.1.0 - resolution: "signal-exit@npm:4.1.0" - checksum: 10c0/41602dce540e46d599edba9d9860193398d135f7ff72cab629db5171516cfae628d21e7bfccde1bbfdf11c48726bc2a6d1a8fb8701125852fbfda7cf19c6aa83 +"react-router@npm:7.18.4": + version: 7.18.4 + resolution: "react-router@npm:7.18.4" + dependencies: + cookie: "npm:^1.0.1" + set-cookie-parser: "npm:^2.6.0" + peerDependencies: + react: ">=18" + react-dom: ">=18" + peerDependenciesMeta: + react-dom: + optional: true + checksum: 10c0/352d103f9d738c7bf56cbb135c0d3d44e6325d15e360f58b6e844c1f5f8cbcd4ccb2de347f14e53984683fc237505a045633e9cb92c5457a1162dae054e08a32 languageName: node linkType: hard -"sinon@npm:^15.0.1": - version: 15.0.1 - resolution: "sinon@npm:15.0.1" +"react-transition-group@npm:^4.4.5": + version: 4.4.5 + resolution: "react-transition-group@npm:4.4.5" dependencies: - "@sinonjs/commons": "npm:^2.0.0" - "@sinonjs/fake-timers": "npm:10.0.2" - "@sinonjs/samsam": "npm:^7.0.1" - diff: "npm:^5.0.0" - nise: "npm:^5.1.2" - supports-color: "npm:^7.2.0" - checksum: 10c0/469855ed4b410dc64afa846d05ff17a0754d16d1aecd0771190bb095b0b9ceaba5c4ca09277cb8abc7ab2f5c52b28358ceeb839deb29595990074cff3e0b9e2b + "@babel/runtime": "npm:^7.5.5" + dom-helpers: "npm:^5.0.1" + loose-envify: "npm:^1.4.0" + prop-types: "npm:^15.6.2" + peerDependencies: + react: ">=16.6.0" + react-dom: ">=16.6.0" + checksum: 10c0/2ba754ba748faefa15f87c96dfa700d5525054a0141de8c75763aae6734af0740e77e11261a1e8f4ffc08fd9ab78510122e05c21c2d79066c38bb6861a886c82 languageName: node linkType: hard -"slash@npm:^3.0.0": - version: 3.0.0 - resolution: "slash@npm:3.0.0" - checksum: 10c0/e18488c6a42bdfd4ac5be85b2ced3ccd0224773baae6ad42cfbb9ec74fc07f9fa8396bd35ee638084ead7a2a0818eb5e7151111544d4731ce843019dab4be47b +"react@npm:^19.3.1": + version: 19.3.1 + resolution: "react@npm:19.3.1" + checksum: 10c0/f0ed036d1223562c09c8347858ca712733ea6e27a30db709ee9c3f3f176d3e9e767afce735d59956addf3d030102e256bba82dea635fc1bdb0e01107e98fef10 languageName: node linkType: hard -"smart-buffer@npm:^4.2.0": - version: 4.2.0 - resolution: "smart-buffer@npm:4.2.0" - checksum: 10c0/a16775323e1404dd43fabafe7460be13a471e021637bc7889468eb45ce6a6b207261f454e4e530a19500cc962c4cc5348583520843b363f4193cee5c00e1e539 +"readdirp@npm:^5.0.0": + version: 5.1.1 + resolution: "readdirp@npm:5.1.1" + checksum: 10c0/5e99755684b9104761e48801828f4d2614efa33b1698629929c5985184dc63a7f8233c827832c40dc7be16b00cc871d303cf9de396d2fb74b9465ee2aa2424a1 languageName: node linkType: hard -"socket.io-adapter@npm:~2.5.2": - version: 2.5.2 - resolution: "socket.io-adapter@npm:2.5.2" - dependencies: - ws: "npm:~8.11.0" - checksum: 10c0/4272b643f58dd7d64d67bf56420a48046a262701427579e72a76880f15612617036a6c31ad7e4314cf477520aa4bcaf5485043223a75d9e0b653d7c5cc22a328 +"reselect@npm:^5.1.1": + version: 5.3.0 + resolution: "reselect@npm:5.3.0" + checksum: 10c0/c79728e4f315152bfb2d82456d35075b705392d9e72c485d7d9b1061bd80af628416216d80f02a42f24dbb24d738aec7fec45806930aaa1d17c32e9875fa1bd6 languageName: node linkType: hard -"socket.io-client@npm:^4.7.5": - version: 4.7.5 - resolution: "socket.io-client@npm:4.7.5" - dependencies: - "@socket.io/component-emitter": "npm:~3.1.0" - debug: "npm:~4.3.2" - engine.io-client: "npm:~6.5.2" - socket.io-parser: "npm:~4.2.4" - checksum: 10c0/d5dc90ee63755fbbb0a1cb3faf575c9ce20d98e809a43a4c9c3ce03a56b8810335ae38e678ceb0650ac434d55e72ea6449c2e5d6db8bc7258f7c529148fac99d +"resolve-from@npm:^4.0.0": + version: 4.0.0 + resolution: "resolve-from@npm:4.0.0" + checksum: 10c0/8408eec31a3112ef96e3746c37be7d64020cda07c03a920f5024e77290a218ea758b26ca9529fd7b1ad283947f34b2291c1c0f6aa0ed34acfdda9c6014c8d190 languageName: node linkType: hard -"socket.io-parser@npm:~4.2.4": - version: 4.2.4 - resolution: "socket.io-parser@npm:4.2.4" +"resolve@npm:^1.19.0": + version: 1.22.13 + resolution: "resolve@npm:1.22.13" dependencies: - "@socket.io/component-emitter": "npm:~3.1.0" - debug: "npm:~4.3.1" - checksum: 10c0/9383b30358fde4a801ea4ec5e6860915c0389a091321f1c1f41506618b5cf7cd685d0a31c587467a0c4ee99ef98c2b99fb87911f9dfb329716c43b587f29ca48 + es-errors: "npm:^1.3.0" + is-core-module: "npm:^2.17.0" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/8dac5b33a1f9f241500fcaf42f9033cdcaf3738c908a60a50a976ff2f6129063cbc5368b4092e42d2d264ad22aa94aaff207f3efa8d0185c68454439b11a1973 languageName: node linkType: hard -"socket.io@npm:^4.7.5": - version: 4.7.5 - resolution: "socket.io@npm:4.7.5" +"resolve@patch:resolve@npm%3A^1.19.0#optional!builtin": + version: 1.22.13 + resolution: "resolve@patch:resolve@npm%3A1.22.13#optional!builtin::version=1.22.13&hash=9bd1a5" dependencies: - accepts: "npm:~1.3.4" - base64id: "npm:~2.0.0" - cors: "npm:~2.8.5" - debug: "npm:~4.3.2" - engine.io: "npm:~6.5.2" - socket.io-adapter: "npm:~2.5.2" - socket.io-parser: "npm:~4.2.4" - checksum: 10c0/221a2cd25f6077d6672cb8b19921336e1acf06788d4bade74953dc96dbfd8b788a5f721b051341a34ee81ef8e1b2028d39ad5257516776400a3f8f3f01255c5e + es-errors: "npm:^1.3.0" + is-core-module: "npm:^2.17.0" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/bc58ee02e647a4250453eeb067796bdc087f6a89d4d5780bfe479f402ec11b9461babc155a6452020b56f3845cf49b1afd9c95fe3fc8347cba6567aa9b10c106 languageName: node linkType: hard -"socks-proxy-agent@npm:^8.0.3": - version: 8.0.4 - resolution: "socks-proxy-agent@npm:8.0.4" +"rimraf@npm:^6.1.3": + version: 6.1.3 + resolution: "rimraf@npm:6.1.3" dependencies: - agent-base: "npm:^7.1.1" - debug: "npm:^4.3.4" - socks: "npm:^2.8.3" - checksum: 10c0/345593bb21b95b0508e63e703c84da11549f0a2657d6b4e3ee3612c312cb3a907eac10e53b23ede3557c6601d63252103494caa306b66560f43af7b98f53957a + glob: "npm:^13.0.3" + package-json-from-dist: "npm:^1.0.1" + bin: + rimraf: dist/esm/bin.mjs + checksum: 10c0/4a56537850102e20ba5d5eb49f366b4b7b2435389734b4b8480cf0e0eb0f6f5d0c44120a171aeb0d8f9ab40312a10d2262f3f50acbad803e32caef61b6cf86fc + languageName: node + linkType: hard + +"rolldown@npm:~1.2.12": + version: 1.2.14 + resolution: "rolldown@npm:1.2.14" + dependencies: + "@oxc-project/types": "npm:=0.154.0" + "@rolldown/binding-android-arm-eabi": "npm:1.2.14" + "@rolldown/binding-android-arm64": "npm:1.2.14" + "@rolldown/binding-darwin-arm64": "npm:1.2.14" + "@rolldown/binding-darwin-x64": "npm:1.2.14" + "@rolldown/binding-freebsd-x64": "npm:1.2.14" + "@rolldown/binding-linux-arm-gnueabihf": "npm:1.2.14" + "@rolldown/binding-linux-arm64-gnu": "npm:1.2.14" + "@rolldown/binding-linux-arm64-musl": "npm:1.2.14" + "@rolldown/binding-linux-ppc64-gnu": "npm:1.2.14" + "@rolldown/binding-linux-s390x-gnu": "npm:1.2.14" + "@rolldown/binding-linux-x64-gnu": "npm:1.2.14" + "@rolldown/binding-linux-x64-musl": "npm:1.2.14" + "@rolldown/binding-openharmony-arm64": "npm:1.2.14" + "@rolldown/binding-win32-arm64-msvc": "npm:1.2.14" + "@rolldown/binding-win32-x64-msvc": "npm:1.2.14" + "@rolldown/pluginutils": "npm:^1.0.0" + dependenciesMeta: + "@rolldown/binding-android-arm-eabi": + optional: true + "@rolldown/binding-android-arm64": + optional: true + "@rolldown/binding-darwin-arm64": + optional: true + "@rolldown/binding-darwin-x64": + optional: true + "@rolldown/binding-freebsd-x64": + optional: true + "@rolldown/binding-linux-arm-gnueabihf": + optional: true + "@rolldown/binding-linux-arm64-gnu": + optional: true + "@rolldown/binding-linux-arm64-musl": + optional: true + "@rolldown/binding-linux-ppc64-gnu": + optional: true + "@rolldown/binding-linux-s390x-gnu": + optional: true + "@rolldown/binding-linux-x64-gnu": + optional: true + "@rolldown/binding-linux-x64-musl": + optional: true + "@rolldown/binding-openharmony-arm64": + optional: true + "@rolldown/binding-win32-arm64-msvc": + optional: true + "@rolldown/binding-win32-x64-msvc": + optional: true + bin: + rolldown: ./bin/cli.mjs + checksum: 10c0/61782e6764bb05e525517dc8b2d69b465033a48a6998b1fb21de01e787938f3f7a2aa1e8eee5d83c924d131cc629eeaea9fa56b69dd469f97b3c6bb7fb6ddcb2 languageName: node linkType: hard -"socks@npm:^2.8.3": - version: 2.8.3 - resolution: "socks@npm:2.8.3" +"router@npm:^2.2.0": + version: 2.2.0 + resolution: "router@npm:2.2.0" dependencies: - ip-address: "npm:^9.0.5" - smart-buffer: "npm:^4.2.0" - checksum: 10c0/d54a52bf9325165770b674a67241143a3d8b4e4c8884560c4e0e078aace2a728dffc7f70150660f51b85797c4e1a3b82f9b7aa25e0a0ceae1a243365da5c51a7 + debug: "npm:^4.4.0" + depd: "npm:^2.0.0" + is-promise: "npm:^4.0.0" + parseurl: "npm:^1.3.3" + path-to-regexp: "npm:^8.0.0" + checksum: 10c0/3279de7450c8eae2f6e095e9edacbdeec0abb5cb7249c6e719faa0db2dba43574b4fff5892d9220631c9abaff52dd3cad648cfea2aaace845e1a071915ac8867 languageName: node linkType: hard -"source-map-js@npm:^1.2.0": - version: 1.2.0 - resolution: "source-map-js@npm:1.2.0" - checksum: 10c0/7e5f896ac10a3a50fe2898e5009c58ff0dc102dcb056ed27a354623a0ece8954d4b2649e1a1b2b52ef2e161d26f8859c7710350930751640e71e374fe2d321a4 +"safer-buffer@npm:>= 2.1.2 < 3.0.0": + version: 2.1.2 + resolution: "safer-buffer@npm:2.1.2" + checksum: 10c0/7e3c8b2e88a1841c9671094bbaeebd94448111dd90a81a1f606f3f67708a6ec57763b3b47f06da09fc6054193e0e6709e77325415dc8422b04497a8070fa02d4 languageName: node linkType: hard -"source-map@npm:^0.5.7": - version: 0.5.7 - resolution: "source-map@npm:0.5.7" - checksum: 10c0/904e767bb9c494929be013017380cbba013637da1b28e5943b566031e29df04fba57edf3f093e0914be094648b577372bd8ad247fa98cfba9c600794cd16b599 +"scheduler@npm:^0.28.0": + version: 0.28.0 + resolution: "scheduler@npm:0.28.0" + checksum: 10c0/783c8e5ce89d41273a02ed6dc0e3d24bd60e05c9e8f7e3adfc6c82e7c69935500d90e343f1ee26d592fc7504e8a2532573c6368def3e93bfe812391fa53efaf4 languageName: node linkType: hard -"sprintf-js@npm:^1.1.3": - version: 1.1.3 - resolution: "sprintf-js@npm:1.1.3" - checksum: 10c0/09270dc4f30d479e666aee820eacd9e464215cdff53848b443964202bf4051490538e5dd1b42e1a65cf7296916ca17640aebf63dae9812749c7542ee5f288dec +"semver@npm:^7.3.5, semver@npm:^7.7.3": + version: 7.8.5 + resolution: "semver@npm:7.8.5" + bin: + semver: bin/semver.js + checksum: 10c0/b1f3127a5be8125a94f37188b361c212466c292c6910adce3ec106cff5dc211ccaedc4739c11bb70fda59d6fc1f040a9bca289f4e093451521a2372e5231fe0c languageName: node linkType: hard -"ssri@npm:^10.0.0": - version: 10.0.6 - resolution: "ssri@npm:10.0.6" +"send@npm:^1.1.0, send@npm:^1.2.0": + version: 1.2.1 + resolution: "send@npm:1.2.1" dependencies: - minipass: "npm:^7.0.3" - checksum: 10c0/e5a1e23a4057a86a97971465418f22ea89bd439ac36ade88812dd920e4e61873e8abd6a9b72a03a67ef50faa00a2daf1ab745c5a15b46d03e0544a0296354227 + debug: "npm:^4.4.3" + encodeurl: "npm:^2.0.0" + escape-html: "npm:^1.0.3" + etag: "npm:^1.8.1" + fresh: "npm:^2.0.0" + http-errors: "npm:^2.0.1" + mime-types: "npm:^3.0.2" + ms: "npm:^2.1.3" + on-finished: "npm:^2.4.1" + range-parser: "npm:^1.2.1" + statuses: "npm:^2.0.2" + checksum: 10c0/fbbbbdc902a913d65605274be23f3d604065cfc3ee3d78bf9fc8af1dc9fc82667c50d3d657f5e601ac657bac9b396b50ee97bd29cd55436320cf1cddebdcec72 languageName: node linkType: hard -"stakingbrain@workspace:.": - version: 0.0.0-use.local - resolution: "stakingbrain@workspace:." +"serialize-javascript@npm:^7.1.1": + version: 7.1.2 + resolution: "serialize-javascript@npm:7.1.2" + checksum: 10c0/568ea22e16893a26cd2506433b8d98bb82f4af37e149330a3deb042bd417516988e6ca9a5010739e1a8dc4866caae320f582dfd2a1a0f0ab69ba3c03f1e155ba + languageName: node + linkType: hard + +"serve-static@npm:^2.2.0": + version: 2.2.1 + resolution: "serve-static@npm:2.2.1" dependencies: - "@types/chai": "npm:^4.3.19" - "@types/mocha": "npm:^10.0.7" - "@types/node": "npm:^22.5.3" - "@typescript-eslint/eslint-plugin": "npm:^8.4.0" - "@typescript-eslint/parser": "npm:^8.4.0" - chai: "npm:^5.1.1" - eslint: "npm:^9.9.1" - eslint-config-prettier: "npm:^9.1.0" - eslint-config-react-app: "npm:^7.0.1" - eslint-plugin-import: "npm:^2.30.0" - eslint-plugin-node: "npm:^11.1.0" - eslint-plugin-prettier: "npm:^5.2.1" - mocha: "npm:^10.7.3" - prettier: "npm:^3.3.3" - rimraf: "npm:^4.1.1" - tsx: "npm:^4.19.0" - typescript: "npm:^5.5.4" - languageName: unknown - linkType: soft + encodeurl: "npm:^2.0.0" + escape-html: "npm:^1.0.3" + parseurl: "npm:^1.3.3" + send: "npm:^1.2.0" + checksum: 10c0/37986096e8572e2dfaad35a3925fa8da0c0969f8814fd7788e84d4d388bc068cf0c06d1658509788e55bed942a6b6d040a8a267fa92bb9ffb1179f8bacde5fd7 + languageName: node + linkType: hard -"statuses@npm:2.0.1, statuses@npm:^2.0.1": - version: 2.0.1 - resolution: "statuses@npm:2.0.1" - checksum: 10c0/34378b207a1620a24804ce8b5d230fea0c279f00b18a7209646d5d47e419d1cc23e7cbf33a25a1e51ac38973dc2ac2e1e9c647a8e481ef365f77668d72becfd0 +"set-cookie-parser@npm:^2.6.0": + version: 2.7.2 + resolution: "set-cookie-parser@npm:2.7.2" + checksum: 10c0/4381a9eb7ee951dfe393fe7aacf76b9a3b4e93a684d2162ab35594fa4053cc82a4d7d7582bf397718012c9adcf839b8cd8f57c6c42901ea9effe33c752da4a45 languageName: node linkType: hard -"steno@npm:^4.0.2": - version: 4.0.2 - resolution: "steno@npm:4.0.2" - checksum: 10c0/fda6bb192bb73ce9453586080aae3fc75506e1c10c9285df49849a016bb9378c6146610f27976708437a6c5492619ec09efb9def6d5397624ae0ad70e57ccaf1 +"setprototypeof@npm:~1.2.0": + version: 1.2.0 + resolution: "setprototypeof@npm:1.2.0" + checksum: 10c0/68733173026766fa0d9ecaeb07f0483f4c2dc70ca376b3b7c40b7cda909f94b0918f6c5ad5ce27a9160bdfb475efaa9d5e705a11d8eaae18f9835d20976028bc languageName: node linkType: hard -"stop-iteration-iterator@npm:^1.0.0": - version: 1.0.0 - resolution: "stop-iteration-iterator@npm:1.0.0" +"shebang-command@npm:^2.0.0": + version: 2.0.0 + resolution: "shebang-command@npm:2.0.0" dependencies: - internal-slot: "npm:^1.0.4" - checksum: 10c0/c4158d6188aac510d9e92925b58709207bd94699e9c31186a040c80932a687f84a51356b5895e6dc72710aad83addb9411c22171832c9ae0e6e11b7d61b0dfb9 + shebang-regex: "npm:^3.0.0" + checksum: 10c0/a41692e7d89a553ef21d324a5cceb5f686d1f3c040759c50aab69688634688c5c327f26f3ecf7001ebfd78c01f3c7c0a11a7c8bfd0a8bc9f6240d4f40b224e4e languageName: node linkType: hard -"string-natural-compare@npm:^3.0.1": - version: 3.0.1 - resolution: "string-natural-compare@npm:3.0.1" - checksum: 10c0/85a6a9195736be500af5d817c7ea36b7e1ac278af079a807f70f79a56602359ee6743ca409af6291b94557de550ff60d1ec31b3c4fc8e7a08d0e12cdab57c149 +"shebang-regex@npm:^3.0.0": + version: 3.0.0 + resolution: "shebang-regex@npm:3.0.0" + checksum: 10c0/1dbed0726dd0e1152a92696c76c7f06084eb32a90f0528d11acd764043aacf76994b2fb30aa1291a21bd019d6699164d048286309a278855ee7bec06cf6fb690 languageName: node linkType: hard -"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0": - version: 4.2.3 - resolution: "string-width@npm:4.2.3" +"side-channel-list@npm:^1.0.1": + version: 1.0.1 + resolution: "side-channel-list@npm:1.0.1" dependencies: - emoji-regex: "npm:^8.0.0" - is-fullwidth-code-point: "npm:^3.0.0" - strip-ansi: "npm:^6.0.1" - checksum: 10c0/1e525e92e5eae0afd7454086eed9c818ee84374bb80328fc41217ae72ff5f065ef1c9d7f72da41de40c75fa8bb3dee63d92373fd492c84260a552c636392a47b + es-errors: "npm:^1.3.0" + object-inspect: "npm:^1.13.4" + checksum: 10c0/d346c787fd2f9f1c2fdea14f00e8250118db0e7596d85a6cb9faa75f105d31a73a8f7a341c93d7df2a2429098c3d37a77bd3be9e88c37094b8c01807bc77c7a2 languageName: node linkType: hard -"string-width@npm:^5.0.1, string-width@npm:^5.1.2": - version: 5.1.2 - resolution: "string-width@npm:5.1.2" +"side-channel-map@npm:^1.0.1": + version: 1.0.1 + resolution: "side-channel-map@npm:1.0.1" dependencies: - eastasianwidth: "npm:^0.2.0" - emoji-regex: "npm:^9.2.2" - strip-ansi: "npm:^7.0.1" - checksum: 10c0/ab9c4264443d35b8b923cbdd513a089a60de339216d3b0ed3be3ba57d6880e1a192b70ae17225f764d7adbf5994e9bb8df253a944736c15a0240eff553c678ca + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.5" + object-inspect: "npm:^1.13.3" + checksum: 10c0/010584e6444dd8a20b85bc926d934424bd809e1a3af941cace229f7fdcb751aada0fb7164f60c2e22292b7fa3c0ff0bce237081fd4cdbc80de1dc68e95430672 languageName: node linkType: hard -"string.prototype.includes@npm:^2.0.0": - version: 2.0.0 - resolution: "string.prototype.includes@npm:2.0.0" +"side-channel-weakmap@npm:^1.0.2": + version: 1.0.2 + resolution: "side-channel-weakmap@npm:1.0.2" dependencies: - define-properties: "npm:^1.1.3" - es-abstract: "npm:^1.17.5" - checksum: 10c0/32dff118c9e9dcc87e240b05462fa8ee7248d9e335c0015c1442fe18152261508a2146d9bb87ddae56abab69148a83c61dfaea33f53853812a6a2db737689ed2 + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.5" + object-inspect: "npm:^1.13.3" + side-channel-map: "npm:^1.0.1" + checksum: 10c0/71362709ac233e08807ccd980101c3e2d7efe849edc51455030327b059f6c4d292c237f94dc0685031dd11c07dd17a68afde235d6cf2102d949567f98ab58185 languageName: node linkType: hard -"string.prototype.matchall@npm:^4.0.11": - version: 4.0.11 - resolution: "string.prototype.matchall@npm:4.0.11" +"side-channel@npm:^1.1.1": + version: 1.1.1 + resolution: "side-channel@npm:1.1.1" dependencies: - call-bind: "npm:^1.0.7" - define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.2" es-errors: "npm:^1.3.0" - es-object-atoms: "npm:^1.0.0" - get-intrinsic: "npm:^1.2.4" - gopd: "npm:^1.0.1" - has-symbols: "npm:^1.0.3" - internal-slot: "npm:^1.0.7" - regexp.prototype.flags: "npm:^1.5.2" - set-function-name: "npm:^2.0.2" - side-channel: "npm:^1.0.6" - checksum: 10c0/915a2562ac9ab5e01b7be6fd8baa0b2b233a0a9aa975fcb2ec13cc26f08fb9a3e85d5abdaa533c99c6fc4c5b65b914eba3d80c4aff9792a4c9fed403f28f7d9d + object-inspect: "npm:^1.13.4" + side-channel-list: "npm:^1.0.1" + side-channel-map: "npm:^1.0.1" + side-channel-weakmap: "npm:^1.0.2" + checksum: 10c0/dc0ab81d67f61bda9247d053ce93f41c3fd8ad2bdcb9cf9d8d2f8540d488f26d87a5e99ebfc07eea49ec025867b2452b705442d974b1478f0395e69f6bfb3270 languageName: node linkType: hard -"string.prototype.repeat@npm:^1.0.0": - version: 1.0.0 - resolution: "string.prototype.repeat@npm:1.0.0" +"sinon@npm:^22.1.0": + version: 22.1.0 + resolution: "sinon@npm:22.1.0" dependencies: - define-properties: "npm:^1.1.3" - es-abstract: "npm:^1.17.5" - checksum: 10c0/94c7978566cffa1327d470fd924366438af9b04b497c43a9805e476e2e908aa37a1fd34cc0911156c17556dab62159d12c7b92b3cc304c3e1281fe4c8e668f40 + "@sinonjs/commons": "npm:^3.0.1" + "@sinonjs/fake-timers": "npm:^15.4.0" + "@sinonjs/samsam": "npm:^10.0.2" + diff: "npm:^9.0.0" + checksum: 10c0/bede89de1fc72ec5e94f1fa00094c8d74a066c83521cb0ca3e2edc34830552df9618c285484ebb518061c3db2fa6604ac9231826d7f2fbfb99d21d72a4f34899 languageName: node linkType: hard -"string.prototype.trim@npm:^1.2.9": - version: 1.2.9 - resolution: "string.prototype.trim@npm:1.2.9" +"socket.io-adapter@npm:~2.5.2": + version: 2.5.8 + resolution: "socket.io-adapter@npm:2.5.8" dependencies: - call-bind: "npm:^1.0.7" - define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.0" - es-object-atoms: "npm:^1.0.0" - checksum: 10c0/dcef1a0fb61d255778155006b372dff8cc6c4394bc39869117e4241f41a2c52899c0d263ffc7738a1f9e61488c490b05c0427faa15151efad721e1a9fb2663c2 + debug: "npm:~4.4.1" + ws: "npm:~8.21.0" + checksum: 10c0/17fc68a7953d6b640b78b2191d8a575b30c7efb9fdec91b4f0788e2f9d093dfb9a84f6fcb396f9b922c32dd0584b01c2d3410a8f69b4cae3f309530b8859bc8a languageName: node linkType: hard -"string.prototype.trimend@npm:^1.0.8": - version: 1.0.8 - resolution: "string.prototype.trimend@npm:1.0.8" +"socket.io-client@npm:^4.8.4": + version: 4.8.4 + resolution: "socket.io-client@npm:4.8.4" dependencies: - call-bind: "npm:^1.0.7" - define-properties: "npm:^1.2.1" - es-object-atoms: "npm:^1.0.0" - checksum: 10c0/0a0b54c17c070551b38e756ae271865ac6cc5f60dabf2e7e343cceae7d9b02e1a1120a824e090e79da1b041a74464e8477e2da43e2775c85392be30a6f60963c + "@socket.io/component-emitter": "npm:~3.1.0" + debug: "npm:~4.4.1" + engine.io-client: "npm:~6.6.1" + socket.io-parser: "npm:~4.2.4" + checksum: 10c0/53f6266d85e375902f1ca417fb2c9ad523463fcd9c4b7aa66fb31792809c4b0d7b81cb760e9fac1b7cbb7bc4283feb39b37867e8ef2a5dcad99302f71ca2346f languageName: node linkType: hard -"string.prototype.trimstart@npm:^1.0.8": - version: 1.0.8 - resolution: "string.prototype.trimstart@npm:1.0.8" +"socket.io-parser@npm:~4.2.4": + version: 4.2.7 + resolution: "socket.io-parser@npm:4.2.7" dependencies: - call-bind: "npm:^1.0.7" - define-properties: "npm:^1.2.1" - es-object-atoms: "npm:^1.0.0" - checksum: 10c0/d53af1899959e53c83b64a5fd120be93e067da740e7e75acb433849aa640782fb6c7d4cd5b84c954c84413745a3764df135a8afeb22908b86a835290788d8366 + "@socket.io/component-emitter": "npm:~3.1.0" + debug: "npm:~4.4.1" + checksum: 10c0/16a5579718b871114ca644d688987dd3cf9292010c949fb083633eefbc1d8fdaf424691d6511d746e5c10f5c88cbd8911cd0b1e807242159f40989be90842b4e languageName: node linkType: hard -"strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": - version: 6.0.1 - resolution: "strip-ansi@npm:6.0.1" +"socket.io@npm:^4.8.4": + version: 4.8.4 + resolution: "socket.io@npm:4.8.4" dependencies: - ansi-regex: "npm:^5.0.1" - checksum: 10c0/1ae5f212a126fe5b167707f716942490e3933085a5ff6c008ab97ab2f272c8025d3aa218b7bd6ab25729ca20cc81cddb252102f8751e13482a5199e873680952 + accepts: "npm:~1.3.4" + cors: "npm:~2.8.5" + debug: "npm:~4.4.1" + engine.io: "npm:~6.6.0" + socket.io-adapter: "npm:~2.5.2" + socket.io-parser: "npm:~4.2.4" + checksum: 10c0/08aa50d99754e223732ab29523573a27afd00013ae4da91a31dcdf3dc1d3ed49e703b70d12e66a6f3d4ce6b84a170a9432533dab21864eded916dec019275989 languageName: node linkType: hard -"strip-ansi@npm:^7.0.1": - version: 7.1.0 - resolution: "strip-ansi@npm:7.1.0" - dependencies: - ansi-regex: "npm:^6.0.1" - checksum: 10c0/a198c3762e8832505328cbf9e8c8381de14a4fa50a4f9b2160138158ea88c0f5549fb50cb13c651c3088f47e63a108b34622ec18c0499b6c8c3a5ddf6b305ac4 +"source-map-js@npm:^1.2.2": + version: 1.2.2 + resolution: "source-map-js@npm:1.2.2" + checksum: 10c0/d5d6dec62266834f83dd4f8a2606a196fb1a006ca5d53b0ef80ee513b7eba2f4e2ae241f4bc129ae7e9b80dea24f6e3d92b446b7e6e96be7653cab8888168a94 languageName: node linkType: hard -"strip-bom@npm:^3.0.0": - version: 3.0.0 - resolution: "strip-bom@npm:3.0.0" - checksum: 10c0/51201f50e021ef16672593d7434ca239441b7b760e905d9f33df6e4f3954ff54ec0e0a06f100d028af0982d6f25c35cd5cda2ce34eaebccd0250b8befb90d8f1 +"source-map@npm:^0.5.7": + version: 0.5.7 + resolution: "source-map@npm:0.5.7" + checksum: 10c0/904e767bb9c494929be013017380cbba013637da1b28e5943b566031e29df04fba57edf3f093e0914be094648b577372bd8ad247fa98cfba9c600794cd16b599 languageName: node linkType: hard -"strip-json-comments@npm:^3.1.1": - version: 3.1.1 - resolution: "strip-json-comments@npm:3.1.1" - checksum: 10c0/9681a6257b925a7fa0f285851c0e613cc934a50661fa7bb41ca9cbbff89686bb4a0ee366e6ecedc4daafd01e83eee0720111ab294366fe7c185e935475ebcecd +"stakingbrain@workspace:.": + version: 0.0.0-use.local + resolution: "stakingbrain@workspace:." + dependencies: + "@eslint/js": "npm:^10.0.1" + "@types/chai": "npm:^5.2.3" + "@types/mocha": "npm:^10.0.10" + "@types/node": "npm:^24.19.2" + chai: "npm:^6.3.0" + dotenv: "npm:^18.0.8" + eslint: "npm:^10.13.0" + eslint-config-prettier: "npm:^10.1.8" + eslint-plugin-prettier: "npm:^5.5.7" + mocha: "npm:^12.0.3" + prettier: "npm:^3.9.10" + rimraf: "npm:^6.1.3" + tsx: "npm:^4.23.15" + typescript: "npm:~6.0.3" + typescript-eslint: "npm:^8.71.2" + dependenciesMeta: + esbuild: + built: true + languageName: unknown + linkType: soft + +"statuses@npm:^2.0.1, statuses@npm:^2.0.2, statuses@npm:~2.0.2": + version: 2.0.2 + resolution: "statuses@npm:2.0.2" + checksum: 10c0/a9947d98ad60d01f6b26727570f3bcceb6c8fa789da64fe6889908fe2e294d57503b14bf2b5af7605c2d36647259e856635cd4c49eab41667658ec9d0080ec3f languageName: node linkType: hard -"stylis@npm:4.1.3": - version: 4.1.3 - resolution: "stylis@npm:4.1.3" - checksum: 10c0/3e4670f26f79bcfba628dcc2756d9d415edfcbf4ec51e40f3b628fd15286222257317cad57390752964eba85cca6163a7621ce90038d68dd630a674479e52334 +"steno@npm:^4.0.2": + version: 4.0.2 + resolution: "steno@npm:4.0.2" + checksum: 10c0/fda6bb192bb73ce9453586080aae3fc75506e1c10c9285df49849a016bb9378c6146610f27976708437a6c5492619ec09efb9def6d5397624ae0ad70e57ccaf1 languageName: node linkType: hard -"supports-color@npm:^5.3.0": - version: 5.5.0 - resolution: "supports-color@npm:5.5.0" - dependencies: - has-flag: "npm:^3.0.0" - checksum: 10c0/6ae5ff319bfbb021f8a86da8ea1f8db52fac8bd4d499492e30ec17095b58af11f0c55f8577390a749b1c4dde691b6a0315dab78f5f54c9b3d83f8fb5905c1c05 +"strip-json-comments@npm:^5.0.3": + version: 5.0.3 + resolution: "strip-json-comments@npm:5.0.3" + checksum: 10c0/daaf20b29f69fb51112698f4a9a662490dbb78d5baf6127c75a0a83c2ac6c078a8c0f74b389ad5e0519d6fc359c4a57cb9971b1ae201aef62ce45a13247791e0 languageName: node linkType: hard -"supports-color@npm:^7.1.0, supports-color@npm:^7.2.0": - version: 7.2.0 - resolution: "supports-color@npm:7.2.0" - dependencies: - has-flag: "npm:^4.0.0" - checksum: 10c0/afb4c88521b8b136b5f5f95160c98dee7243dc79d5432db7efc27efb219385bbc7d9427398e43dd6cc730a0f87d5085ce1652af7efbe391327bc0a7d0f7fc124 +"stylis@npm:4.2.0": + version: 4.2.0 + resolution: "stylis@npm:4.2.0" + checksum: 10c0/a7128ad5a8ed72652c6eba46bed4f416521bc9745a460ef5741edc725252cebf36ee45e33a8615a7057403c93df0866ab9ee955960792db210bb80abd5ac6543 languageName: node linkType: hard @@ -8742,154 +3821,67 @@ __metadata: languageName: node linkType: hard -"synckit@npm:^0.9.1": - version: 0.9.1 - resolution: "synckit@npm:0.9.1" - dependencies: - "@pkgr/core": "npm:^0.1.0" - tslib: "npm:^2.6.2" - checksum: 10c0/d8b89e1bf30ba3ffb469d8418c836ad9c0c062bf47028406b4d06548bc66af97155ea2303b96c93bf5c7c0f0d66153a6fbd6924c76521b434e6a9898982abc2e - languageName: node - linkType: hard - -"tar@npm:^6.1.11": - version: 6.1.13 - resolution: "tar@npm:6.1.13" +"synckit@npm:^0.12.3": + version: 0.12.3 + resolution: "synckit@npm:0.12.3" dependencies: - chownr: "npm:^2.0.0" - fs-minipass: "npm:^2.0.0" - minipass: "npm:^4.0.0" - minizlib: "npm:^2.1.1" - mkdirp: "npm:^1.0.3" - yallist: "npm:^4.0.0" - checksum: 10c0/eee5f264f3f3c27cd8d4934f80c568470f92811c416144ab671bb36b45a8ed55fbfbbd31f0146f3eddaca91fd564c9a7ec4d2086940968b836f4a2c54146c060 + "@dual-bundle/import-meta-resolve": "npm:^4.2.1" + "@pkgr/core": "npm:^0.3.6" + checksum: 10c0/e7c3c5bd8272c55c0e108c296dbd0d326e9f22614227079b46ff7bd51c0970d810b222a3a14f5b0f1797d8af2c82058b9a927ac63a06219ead8535d271b382e1 languageName: node linkType: hard -"tar@npm:^6.2.1": - version: 6.2.1 - resolution: "tar@npm:6.2.1" +"tar@npm:^7.5.7": + version: 7.5.22 + resolution: "tar@npm:7.5.22" dependencies: - chownr: "npm:^2.0.0" - fs-minipass: "npm:^2.0.0" - minipass: "npm:^5.0.0" - minizlib: "npm:^2.1.1" - mkdirp: "npm:^1.0.3" - yallist: "npm:^4.0.0" - checksum: 10c0/a5eca3eb50bc11552d453488344e6507156b9193efd7635e98e867fab275d527af53d8866e2370cd09dfe74378a18111622ace35af6a608e5223a7d27fe99537 - languageName: node - linkType: hard - -"text-table@npm:^0.2.0": - version: 0.2.0 - resolution: "text-table@npm:0.2.0" - checksum: 10c0/02805740c12851ea5982686810702e2f14369a5f4c5c40a836821e3eefc65ffeec3131ba324692a37608294b0fd8c1e55a2dd571ffed4909822787668ddbee5c - languageName: node - linkType: hard - -"to-fast-properties@npm:^2.0.0": - version: 2.0.0 - resolution: "to-fast-properties@npm:2.0.0" - checksum: 10c0/b214d21dbfb4bce3452b6244b336806ffea9c05297148d32ebb428d5c43ce7545bdfc65a1ceb58c9ef4376a65c0cb2854d645f33961658b3e3b4f84910ddcdd7 + "@isaacs/fs-minipass": "npm:^4.0.0" + chownr: "npm:^3.0.0" + minipass: "npm:^7.1.2" + minizlib: "npm:^3.1.0" + yallist: "npm:^5.0.0" + checksum: 10c0/1311f6be85a8157ac4c9147bae43e13923d2a1aae15e4aa1bd5239e4e03d2cf53cfe103dde7f35832fbb4c938b042856bc8e9a0afd29abd05e2d1608788c4fea languageName: node linkType: hard -"to-regex-range@npm:^5.0.1": - version: 5.0.1 - resolution: "to-regex-range@npm:5.0.1" +"tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.15, tinyglobby@npm:^0.2.17": + version: 0.2.17 + resolution: "tinyglobby@npm:0.2.17" dependencies: - is-number: "npm:^7.0.0" - checksum: 10c0/487988b0a19c654ff3e1961b87f471702e708fa8a8dd02a298ef16da7206692e8552a0250e8b3e8759270f62e9d8314616f6da274734d3b558b1fc7b7724e892 + fdir: "npm:^6.5.0" + picomatch: "npm:^4.0.4" + checksum: 10c0/7f7bb0f197c88bc4b20c231e0deca4240ca3bf313a88f5a7fee93a872b84966a4d50220947c0455ad07a60b3b360961c5b7fd979222aeb716a9f99b412002e4c languageName: node linkType: hard -"toidentifier@npm:1.0.1": +"toidentifier@npm:~1.0.1": version: 1.0.1 resolution: "toidentifier@npm:1.0.1" checksum: 10c0/93937279934bd66cc3270016dd8d0afec14fb7c94a05c72dc57321f8bd1fa97e5bea6d1f7c89e728d077ca31ea125b78320a616a6c6cd0e6b9cb94cb864381c1 languageName: node linkType: hard -"ts-api-utils@npm:^1.3.0": - version: 1.3.0 - resolution: "ts-api-utils@npm:1.3.0" - peerDependencies: - typescript: ">=4.2.0" - checksum: 10c0/f54a0ba9ed56ce66baea90a3fa087a484002e807f28a8ccb2d070c75e76bde64bd0f6dce98b3802834156306050871b67eec325cb4e918015a360a3f0868c77c - languageName: node - linkType: hard - -"tsconfck@npm:^3.0.3": - version: 3.1.3 - resolution: "tsconfck@npm:3.1.3" - peerDependencies: - typescript: ^5.0.0 - peerDependenciesMeta: - typescript: - optional: true - bin: - tsconfck: bin/tsconfck.js - checksum: 10c0/64f7a8ed0a6d36b0902dfc0075e791d2242f7634644f124343ec0dec4f3f70092f929c5a9f59496d51883aa81bb1e595deb92a219593575d2e75b849064713d1 - languageName: node - linkType: hard - -"tsconfig-paths@npm:^3.15.0": - version: 3.15.0 - resolution: "tsconfig-paths@npm:3.15.0" - dependencies: - "@types/json5": "npm:^0.0.29" - json5: "npm:^1.0.2" - minimist: "npm:^1.2.6" - strip-bom: "npm:^3.0.0" - checksum: 10c0/5b4f301a2b7a3766a986baf8fc0e177eb80bdba6e396792ff92dc23b5bca8bb279fc96517dcaaef63a3b49bebc6c4c833653ec58155780bc906bdbcf7dda0ef5 - languageName: node - linkType: hard - -"tslib@npm:^1.8.1": - version: 1.14.1 - resolution: "tslib@npm:1.14.1" - checksum: 10c0/69ae09c49eea644bc5ebe1bca4fa4cc2c82b7b3e02f43b84bd891504edf66dbc6b2ec0eef31a957042de2269139e4acff911e6d186a258fb14069cd7f6febce2 - languageName: node - linkType: hard - -"tslib@npm:^2.0.3": +"ts-api-utils@npm:^2.5.0": version: 2.5.0 - resolution: "tslib@npm:2.5.0" - checksum: 10c0/e32fc99cc730dd514e53c44e668d76016e738f0bcc726aad5dbd2d335cf19b87a95a9b1e4f0a9993e370f1d702b5e471cdd4acabcac428a3099d496b9af2021e - languageName: node - linkType: hard - -"tslib@npm:^2.6.2": - version: 2.7.0 - resolution: "tslib@npm:2.7.0" - checksum: 10c0/469e1d5bf1af585742128827000711efa61010b699cb040ab1800bcd3ccdd37f63ec30642c9e07c4439c1db6e46345582614275daca3e0f4abae29b0083f04a6 - languageName: node - linkType: hard - -"tsutils@npm:^3.21.0": - version: 3.21.0 - resolution: "tsutils@npm:3.21.0" - dependencies: - tslib: "npm:^1.8.1" + resolution: "ts-api-utils@npm:2.5.0" peerDependencies: - typescript: ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - checksum: 10c0/02f19e458ec78ead8fffbf711f834ad8ecd2cc6ade4ec0320790713dccc0a412b99e7fd907c4cda2a1dc602c75db6f12e0108e87a5afad4b2f9e90a24cabd5a2 + typescript: ">=4.8.4" + checksum: 10c0/767849383c114e7f1971fa976b20e73ac28fd0c70d8d65c0004790bf4d8f89888c7e4cf6d5949f9c1beae9bc3c64835bef77bbe27fddf45a3c7b60cebcf85c8c languageName: node linkType: hard -"tsx@npm:^4.19.0": - version: 4.19.0 - resolution: "tsx@npm:4.19.0" +"tsx@npm:^4.23.15": + version: 4.23.15 + resolution: "tsx@npm:4.23.15" dependencies: - esbuild: "npm:~0.23.0" + esbuild: "npm:~0.28.0" fsevents: "npm:~2.3.3" - get-tsconfig: "npm:^4.7.5" dependenciesMeta: fsevents: optional: true bin: tsx: dist/cli.mjs - checksum: 10c0/d14463a78067c6db84c677b79b14861de6d7f6fb0ffa5727cc500c4552459e936395a3854ad0112af0fd7b263bcedd62ce3929b036188eb10cd9902a607ffe34 + checksum: 10c0/3c5fc53e578da87d3a63565286b1ecbb0e01b9bb646daec1957294ef890d14f22cc67bb4b09b5daffab0e7ce0217b1e7645393feb0f524b1ddf26ae9595d7e75 languageName: node linkType: hard @@ -8902,209 +3894,94 @@ __metadata: languageName: node linkType: hard -"type-detect@npm:4.0.8, type-detect@npm:^4.0.8": +"type-detect@npm:4.0.8": version: 4.0.8 resolution: "type-detect@npm:4.0.8" checksum: 10c0/8fb9a51d3f365a7de84ab7f73b653534b61b622aa6800aecdb0f1095a4a646d3f5eb295322127b6573db7982afcd40ab492d038cf825a42093a58b1e1353e0bd languageName: node linkType: hard -"type-is@npm:^2.0.0": - version: 2.0.0 - resolution: "type-is@npm:2.0.0" - dependencies: - content-type: "npm:^1.0.5" - media-typer: "npm:^1.1.0" - mime-types: "npm:^3.0.0" - checksum: 10c0/c1fa697c8cb77bcb3f4aa3673a3fdbe3f1c2fe51ce8c9736e9773f25c3118562723369b471063c3945722bcda5d8bf969a0693ab7b6e7da012ff667294efe988 - languageName: node - linkType: hard - -"type-is@npm:~1.6.18": - version: 1.6.18 - resolution: "type-is@npm:1.6.18" - dependencies: - media-typer: "npm:0.3.0" - mime-types: "npm:~2.1.24" - checksum: 10c0/a23daeb538591b7efbd61ecf06b6feb2501b683ffdc9a19c74ef5baba362b4347e42f1b4ed81f5882a8c96a3bfff7f93ce3ffaf0cbbc879b532b04c97a55db9d - languageName: node - linkType: hard - -"typed-array-buffer@npm:^1.0.2": - version: 1.0.2 - resolution: "typed-array-buffer@npm:1.0.2" - dependencies: - call-bind: "npm:^1.0.7" - es-errors: "npm:^1.3.0" - is-typed-array: "npm:^1.1.13" - checksum: 10c0/9e043eb38e1b4df4ddf9dde1aa64919ae8bb909571c1cc4490ba777d55d23a0c74c7d73afcdd29ec98616d91bb3ae0f705fad4421ea147e1daf9528200b562da - languageName: node - linkType: hard - -"typed-array-byte-length@npm:^1.0.1": - version: 1.0.1 - resolution: "typed-array-byte-length@npm:1.0.1" - dependencies: - call-bind: "npm:^1.0.7" - for-each: "npm:^0.3.3" - gopd: "npm:^1.0.1" - has-proto: "npm:^1.0.3" - is-typed-array: "npm:^1.1.13" - checksum: 10c0/fcebeffb2436c9f355e91bd19e2368273b88c11d1acc0948a2a306792f1ab672bce4cfe524ab9f51a0505c9d7cd1c98eff4235c4f6bfef6a198f6cfc4ff3d4f3 +"type-detect@npm:^4.1.0": + version: 4.1.0 + resolution: "type-detect@npm:4.1.0" + checksum: 10c0/df8157ca3f5d311edc22885abc134e18ff8ffbc93d6a9848af5b682730ca6a5a44499259750197250479c5331a8a75b5537529df5ec410622041650a7f293e2a languageName: node linkType: hard -"typed-array-byte-offset@npm:^1.0.2": - version: 1.0.2 - resolution: "typed-array-byte-offset@npm:1.0.2" +"type-is@npm:^2.1.0": + version: 2.1.0 + resolution: "type-is@npm:2.1.0" dependencies: - available-typed-arrays: "npm:^1.0.7" - call-bind: "npm:^1.0.7" - for-each: "npm:^0.3.3" - gopd: "npm:^1.0.1" - has-proto: "npm:^1.0.3" - is-typed-array: "npm:^1.1.13" - checksum: 10c0/d2628bc739732072e39269389a758025f75339de2ed40c4f91357023c5512d237f255b633e3106c461ced41907c1bf9a533c7e8578066b0163690ca8bc61b22f + content-type: "npm:^2.0.0" + media-typer: "npm:^1.1.0" + mime-types: "npm:^3.0.0" + checksum: 10c0/a6018f8f509de48f2c7429305e3a920e73b374fa93127dd0877ae1c2df65a5d33907caac8afb0c37a9b9fc7c49f29e3f55d668963dc845d966930b667c07f50e languageName: node linkType: hard -"typed-array-length@npm:^1.0.6": - version: 1.0.6 - resolution: "typed-array-length@npm:1.0.6" +"typescript-eslint@npm:^8.71.2": + version: 8.71.2 + resolution: "typescript-eslint@npm:8.71.2" dependencies: - call-bind: "npm:^1.0.7" - for-each: "npm:^0.3.3" - gopd: "npm:^1.0.1" - has-proto: "npm:^1.0.3" - is-typed-array: "npm:^1.1.13" - possible-typed-array-names: "npm:^1.0.0" - checksum: 10c0/74253d7dc488eb28b6b2711cf31f5a9dcefc9c41b0681fd1c178ed0a1681b4468581a3626d39cd4df7aee3d3927ab62be06aa9ca74e5baf81827f61641445b77 + "@typescript-eslint/eslint-plugin": "npm:8.71.2" + "@typescript-eslint/parser": "npm:8.71.2" + "@typescript-eslint/typescript-estree": "npm:8.71.2" + "@typescript-eslint/utils": "npm:8.71.2" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/58717a8c912206860abd0c3db6fb18a42cbc04d12c06486154c2e67f444a98e1334a1f9736677cb8d576bd73b86ecba202f3eb700b6b00930d997dc7be868200 languageName: node linkType: hard -"typescript@npm:^5.5.4": - version: 5.5.4 - resolution: "typescript@npm:5.5.4" +"typescript@npm:~6.0.3": + version: 6.0.3 + resolution: "typescript@npm:6.0.3" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10c0/422be60f89e661eab29ac488c974b6cc0a660fb2228003b297c3d10c32c90f3bcffc1009b43876a082515a3c376b1eefcce823d6e78982e6878408b9a923199c + checksum: 10c0/4a25ff5045b984370f48f196b3a0120779b1b343d40b9a68d114ea5e5fff099809b2bb777576991a63a5cd59cf7bffd96ff6fe10afcefbcb8bd6fb96ad4b6606 languageName: node linkType: hard -"typescript@patch:typescript@npm%3A^5.5.4#optional!builtin": - version: 5.5.4 - resolution: "typescript@patch:typescript@npm%3A5.5.4#optional!builtin::version=5.5.4&hash=379a07" +"typescript@patch:typescript@npm%3A~6.0.3#optional!builtin": + version: 6.0.3 + resolution: "typescript@patch:typescript@npm%3A6.0.3#optional!builtin::version=6.0.3&hash=5786d5" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10c0/73409d7b9196a5a1217b3aaad929bf76294d3ce7d6e9766dd880ece296ee91cf7d7db6b16c6c6c630ee5096eccde726c0ef17c7dfa52b01a243e57ae1f09ef07 - languageName: node - linkType: hard - -"unbox-primitive@npm:^1.0.2": - version: 1.0.2 - resolution: "unbox-primitive@npm:1.0.2" - dependencies: - call-bind: "npm:^1.0.2" - has-bigints: "npm:^1.0.2" - has-symbols: "npm:^1.0.3" - which-boxed-primitive: "npm:^1.0.2" - checksum: 10c0/81ca2e81134167cc8f75fa79fbcc8a94379d6c61de67090986a2273850989dd3bae8440c163121b77434b68263e34787a675cbdcb34bb2f764c6b9c843a11b66 - languageName: node - linkType: hard - -"undici-types@npm:~6.19.2": - version: 6.19.8 - resolution: "undici-types@npm:6.19.8" - checksum: 10c0/078afa5990fba110f6824823ace86073b4638f1d5112ee26e790155f481f2a868cc3e0615505b6f4282bdf74a3d8caad715fd809e870c2bb0704e3ea6082f344 + checksum: 10c0/2f25c74e65663c248fa1ade2b8459d9ce5372ff9dad07067310f132966ebec1d93f6c42f0baf77a6b6a7a91460463f708e6887013aaade22111037457c6b25df languageName: node linkType: hard -"unicode-canonical-property-names-ecmascript@npm:^2.0.0": - version: 2.0.0 - resolution: "unicode-canonical-property-names-ecmascript@npm:2.0.0" - checksum: 10c0/0fe812641bcfa3ae433025178a64afb5d9afebc21a922dafa7cba971deebb5e4a37350423890750132a85c936c290fb988146d0b1bd86838ad4897f4fc5bd0de - languageName: node - linkType: hard - -"unicode-match-property-ecmascript@npm:^2.0.0": - version: 2.0.0 - resolution: "unicode-match-property-ecmascript@npm:2.0.0" - dependencies: - unicode-canonical-property-names-ecmascript: "npm:^2.0.0" - unicode-property-aliases-ecmascript: "npm:^2.0.0" - checksum: 10c0/4d05252cecaf5c8e36d78dc5332e03b334c6242faf7cf16b3658525441386c0a03b5f603d42cbec0f09bb63b9fd25c9b3b09667aee75463cac3efadae2cd17ec - languageName: node - linkType: hard - -"unicode-match-property-value-ecmascript@npm:^2.1.0": - version: 2.1.0 - resolution: "unicode-match-property-value-ecmascript@npm:2.1.0" - checksum: 10c0/f5b9499b9e0ffdc6027b744d528f17ec27dd7c15da03254ed06851feec47e0531f20d410910c8a49af4a6a190f4978413794c8d75ce112950b56d583b5d5c7f2 - languageName: node - linkType: hard - -"unicode-property-aliases-ecmascript@npm:^2.0.0": - version: 2.1.0 - resolution: "unicode-property-aliases-ecmascript@npm:2.1.0" - checksum: 10c0/50ded3f8c963c7785e48c510a3b7c6bc4e08a579551489aa0349680a35b1ceceec122e33b2b6c1b579d0be2250f34bb163ac35f5f8695fe10bbc67fb757f0af8 +"undici-types@npm:>=7.24.0 <7.24.7": + version: 7.24.6 + resolution: "undici-types@npm:7.24.6" + checksum: 10c0/d9cd8befb643ac904615c280a095ba4240531f6bb4a5e75a22a7483630ca8d3f1016d2ab6ace6ceda1f63b3a2db2fe037fafe121d6917a0187573aa548ff78ca languageName: node linkType: hard -"unique-filename@npm:^3.0.0": - version: 3.0.0 - resolution: "unique-filename@npm:3.0.0" - dependencies: - unique-slug: "npm:^4.0.0" - checksum: 10c0/6363e40b2fa758eb5ec5e21b3c7fb83e5da8dcfbd866cc0c199d5534c42f03b9ea9ab069769cc388e1d7ab93b4eeef28ef506ab5f18d910ef29617715101884f +"undici-types@npm:~8.9.0": + version: 8.9.0 + resolution: "undici-types@npm:8.9.0" + checksum: 10c0/51c712c20c087368f62c6fc0f2bc49fa76bfe5dec28ff01584bb677ff29b0622569783c02d43e151143808c48c0503f804093bd2c491b10f2a830138034cb451 languageName: node linkType: hard -"unique-slug@npm:^4.0.0": - version: 4.0.0 - resolution: "unique-slug@npm:4.0.0" - dependencies: - imurmurhash: "npm:^0.1.4" - checksum: 10c0/cb811d9d54eb5821b81b18205750be84cb015c20a4a44280794e915f5a0a70223ce39066781a354e872df3572e8155c228f43ff0cce94c7cbf4da2cc7cbdd635 +"undici@npm:^8.4.1": + version: 8.12.2 + resolution: "undici@npm:8.12.2" + checksum: 10c0/25b0e0a69585b2b2e2678fdcd07942bbe15b14c6686d496712b0ee37ce15753af90ab7335ee42c8e65ec60b72bf3a21adc72bbabf86c1eaca74f27a3779532de languageName: node linkType: hard -"unpipe@npm:1.0.0, unpipe@npm:~1.0.0": +"unpipe@npm:~1.0.0": version: 1.0.0 resolution: "unpipe@npm:1.0.0" checksum: 10c0/193400255bd48968e5c5383730344fbb4fa114cdedfab26e329e50dd2d81b134244bb8a72c6ac1b10ab0281a58b363d06405632c9d49ca9dfd5e90cbd7d0f32c languageName: node linkType: hard -"update-browserslist-db@npm:^1.0.10": - version: 1.0.10 - resolution: "update-browserslist-db@npm:1.0.10" - dependencies: - escalade: "npm:^3.1.1" - picocolors: "npm:^1.0.0" - peerDependencies: - browserslist: ">= 4.21.0" - bin: - browserslist-lint: cli.js - checksum: 10c0/e6fa55b515a674cc3b6c045d1f37f72780ddbbbb48b3094391fb2e43357b859ca5cee4c7d3055fd654d442ef032777d0972494a9a2e6c30d3660ee57b7138ae9 - languageName: node - linkType: hard - -"update-browserslist-db@npm:^1.1.0": - version: 1.1.0 - resolution: "update-browserslist-db@npm:1.1.0" - dependencies: - escalade: "npm:^3.1.2" - picocolors: "npm:^1.0.1" - peerDependencies: - browserslist: ">= 4.21.0" - bin: - update-browserslist-db: cli.js - checksum: 10c0/a7452de47785842736fb71547651c5bbe5b4dc1e3722ccf48a704b7b34e4dcf633991eaa8e4a6a517ffb738b3252eede3773bef673ef9021baa26b056d63a5b9 - languageName: node - linkType: hard - "uri-js@npm:^4.2.2": version: 4.4.1 resolution: "uri-js@npm:4.4.1" @@ -9114,62 +3991,58 @@ __metadata: languageName: node linkType: hard -"utils-merge@npm:1.0.1": - version: 1.0.1 - resolution: "utils-merge@npm:1.0.1" - checksum: 10c0/02ba649de1b7ca8854bfe20a82f1dfbdda3fb57a22ab4a8972a63a34553cf7aa51bc9081cf7e001b035b88186d23689d69e71b510e610a09a4c66f68aa95b672 +"use-sync-external-store@npm:^1.6.0": + version: 1.7.0 + resolution: "use-sync-external-store@npm:1.7.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10c0/6bae7cb778ce611131ec830c4e469fbc2872acefc25d2e4cf1c28b8628690689770742a9f2f0642c95134f9c77041fee60f0352f81ca43f96643b0ed01774726 languageName: node linkType: hard -"vary@npm:^1, vary@npm:~1.1.2": +"vary@npm:^1, vary@npm:^1.1.2": version: 1.1.2 resolution: "vary@npm:1.1.2" checksum: 10c0/f15d588d79f3675135ba783c91a4083dcd290a2a5be9fcb6514220a1634e23df116847b1cc51f66bfb0644cf9353b2abb7815ae499bab06e46dd33c1a6bf1f4f languageName: node linkType: hard -"vite-tsconfig-paths@npm:^5.0.1": - version: 5.0.1 - resolution: "vite-tsconfig-paths@npm:5.0.1" - dependencies: - debug: "npm:^4.1.1" - globrex: "npm:^0.1.2" - tsconfck: "npm:^3.0.3" - peerDependencies: - vite: "*" - peerDependenciesMeta: - vite: - optional: true - checksum: 10c0/3c68a4d5df21ed4ef81749c20e91c5978989ed06bffc01688b3f1a0fe65951b461a68f0c017ad930a088cfe7a8cc04d0c8d955dfb8719d5edc7fb0ba9bf38a73 - languageName: node - linkType: hard - -"vite@npm:^5.4.2": - version: 5.4.2 - resolution: "vite@npm:5.4.2" +"vite@npm:^8.3.4": + version: 8.3.4 + resolution: "vite@npm:8.3.4" dependencies: - esbuild: "npm:^0.21.3" fsevents: "npm:~2.3.3" - postcss: "npm:^8.4.41" - rollup: "npm:^4.20.0" - peerDependencies: - "@types/node": ^18.0.0 || >=20.0.0 - less: "*" - lightningcss: ^1.21.0 - sass: "*" - sass-embedded: "*" - stylus: "*" - sugarss: "*" - terser: ^5.4.0 + lightningcss: "npm:^1.33.0" + picomatch: "npm:^4.0.7" + postcss: "npm:^8.5.29" + rolldown: "npm:~1.2.12" + tinyglobby: "npm:^0.2.17" + peerDependencies: + "@types/node": ^20.19.0 || >=22.12.0 + "@vitejs/devtools": ^0.7.1 + esbuild: ^0.27.0 || ^0.28.0 + jiti: ">=1.21.0" + less: ^4.0.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 dependenciesMeta: fsevents: optional: true peerDependenciesMeta: "@types/node": optional: true - less: + "@vitejs/devtools": optional: true - lightningcss: + esbuild: + optional: true + jiti: + optional: true + less: optional: true sass: optional: true @@ -9181,67 +4054,13 @@ __metadata: optional: true terser: optional: true + tsx: + optional: true + yaml: + optional: true bin: vite: bin/vite.js - checksum: 10c0/23e347ca8aa6f0a774227e4eb7abae228f12c6806a727b046aa75e7ee37ffc2d68cff74360e12a42c347f79adc294e2363bc723b957bf4b382b5a8fb39e4df9d - languageName: node - linkType: hard - -"which-boxed-primitive@npm:^1.0.2": - version: 1.0.2 - resolution: "which-boxed-primitive@npm:1.0.2" - dependencies: - is-bigint: "npm:^1.0.1" - is-boolean-object: "npm:^1.1.0" - is-number-object: "npm:^1.0.4" - is-string: "npm:^1.0.5" - is-symbol: "npm:^1.0.3" - checksum: 10c0/0a62a03c00c91dd4fb1035b2f0733c341d805753b027eebd3a304b9cb70e8ce33e25317add2fe9b5fea6f53a175c0633ae701ff812e604410ddd049777cd435e - languageName: node - linkType: hard - -"which-builtin-type@npm:^1.1.3": - version: 1.1.4 - resolution: "which-builtin-type@npm:1.1.4" - dependencies: - function.prototype.name: "npm:^1.1.6" - has-tostringtag: "npm:^1.0.2" - is-async-function: "npm:^2.0.0" - is-date-object: "npm:^1.0.5" - is-finalizationregistry: "npm:^1.0.2" - is-generator-function: "npm:^1.0.10" - is-regex: "npm:^1.1.4" - is-weakref: "npm:^1.0.2" - isarray: "npm:^2.0.5" - which-boxed-primitive: "npm:^1.0.2" - which-collection: "npm:^1.0.2" - which-typed-array: "npm:^1.1.15" - checksum: 10c0/a4a76d20d869a81b1dbb4adea31edc7e6c1a4466d3ab7c2cd757c9219d48d3723b04076c85583257b0f0f8e3ebe5af337248b8ceed57b9051cb97bce5bd881d1 - languageName: node - linkType: hard - -"which-collection@npm:^1.0.1, which-collection@npm:^1.0.2": - version: 1.0.2 - resolution: "which-collection@npm:1.0.2" - dependencies: - is-map: "npm:^2.0.3" - is-set: "npm:^2.0.3" - is-weakmap: "npm:^2.0.2" - is-weakset: "npm:^2.0.3" - checksum: 10c0/3345fde20964525a04cdf7c4a96821f85f0cc198f1b2ecb4576e08096746d129eb133571998fe121c77782ac8f21cbd67745a3d35ce100d26d4e684c142ea1f2 - languageName: node - linkType: hard - -"which-typed-array@npm:^1.1.13, which-typed-array@npm:^1.1.14, which-typed-array@npm:^1.1.15": - version: 1.1.15 - resolution: "which-typed-array@npm:1.1.15" - dependencies: - available-typed-arrays: "npm:^1.0.7" - call-bind: "npm:^1.0.7" - for-each: "npm:^0.3.3" - gopd: "npm:^1.0.1" - has-tostringtag: "npm:^1.0.2" - checksum: 10c0/4465d5348c044032032251be54d8988270e69c6b7154f8fcb2a47ff706fe36f7624b3a24246b8d9089435a8f4ec48c1c1025c5d6b499456b9e5eff4f48212983 + checksum: 10c0/63d7351fa21580fb646f0b594c64e97a56cdf15c55a57372a7adefe6287995fa7384cb5ddca14695ff7dca738ba00e0a3352fc24df711d9c501c5576f40f0867 languageName: node linkType: hard @@ -9256,14 +4075,14 @@ __metadata: languageName: node linkType: hard -"which@npm:^4.0.0": - version: 4.0.0 - resolution: "which@npm:4.0.0" +"which@npm:^7.0.0": + version: 7.0.0 + resolution: "which@npm:7.0.0" dependencies: - isexe: "npm:^3.1.1" + isexe: "npm:^4.0.0" bin: node-which: bin/which.js - checksum: 10c0/449fa5c44ed120ccecfe18c433296a4978a7583bf2391c50abce13f76878d2476defde04d0f79db8165bdf432853c1f8389d0485ca6e8ebce3bbcded513d5e6a + checksum: 10c0/ca0b54f198f78bbc4b7c02e34bda8d335cb352e0adb4cbca1c37b1a957af3a879a82c4c27ca6525bc942f548d8b64f816ef6528360af9f3de55ffb9b979b620d languageName: node linkType: hard @@ -9274,32 +4093,10 @@ __metadata: languageName: node linkType: hard -"workerpool@npm:^6.5.1": - version: 6.5.1 - resolution: "workerpool@npm:6.5.1" - checksum: 10c0/58e8e969782292cb3a7bfba823f1179a7615250a0cefb4841d5166234db1880a3d0fe83a31dd8d648329ec92c2d0cd1890ad9ec9e53674bb36ca43e9753cdeac - languageName: node - linkType: hard - -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": - version: 7.0.0 - resolution: "wrap-ansi@npm:7.0.0" - dependencies: - ansi-styles: "npm:^4.0.0" - string-width: "npm:^4.1.0" - strip-ansi: "npm:^6.0.0" - checksum: 10c0/d15fc12c11e4cbc4044a552129ebc75ee3f57aa9c1958373a4db0292d72282f54373b536103987a4a7594db1ef6a4f10acf92978f79b98c49306a4b58c77d4da - languageName: node - linkType: hard - -"wrap-ansi@npm:^8.1.0": - version: 8.1.0 - resolution: "wrap-ansi@npm:8.1.0" - dependencies: - ansi-styles: "npm:^6.1.0" - string-width: "npm:^5.0.1" - strip-ansi: "npm:^7.0.1" - checksum: 10c0/138ff58a41d2f877eae87e3282c0630fc2789012fc1af4d6bd626eeb9a2f9a65ca92005e6e69a75c7b85a68479fe7443c7dbe1eb8fbaa681a4491364b7c55c60 +"workerpool@npm:^10.0.0": + version: 10.0.3 + resolution: "workerpool@npm:10.0.3" + checksum: 10c0/a20a274ac9afc6c329f5b1661914a8ebd160320cfbd816b723b6ef385e41b186ef0bf2d6d82a77556fc33599ce14e40c7fdfd44a2a4daff488257cdd396fa9e9 languageName: node linkType: hard @@ -9310,24 +4107,9 @@ __metadata: languageName: node linkType: hard -"ws@npm:~8.11.0": - version: 8.11.0 - resolution: "ws@npm:8.11.0" - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - checksum: 10c0/b672b312f357afba8568b9dbb9e08b9e8a20845659b35fa6b340dc848efe371379f5e22bb1dc89c4b2940d5e2dc52dd1de85dde41776875fce115a448f94754f - languageName: node - linkType: hard - -"ws@npm:~8.17.1": - version: 8.17.1 - resolution: "ws@npm:8.17.1" +"ws@npm:~8.21.0": + version: 8.21.3 + resolution: "ws@npm:8.21.3" peerDependencies: bufferutil: ^4.0.1 utf-8-validate: ">=5.0.2" @@ -9336,76 +4118,28 @@ __metadata: optional: true utf-8-validate: optional: true - checksum: 10c0/f4a49064afae4500be772abdc2211c8518f39e1c959640457dcee15d4488628620625c783902a52af2dd02f68558da2868fd06e6fd0e67ebcd09e6881b1b5bfe - languageName: node - linkType: hard - -"xmlhttprequest-ssl@npm:~2.0.0": - version: 2.0.0 - resolution: "xmlhttprequest-ssl@npm:2.0.0" - checksum: 10c0/b64ab371459bd5e3a4827e3c7535759047d285fd310aea6fd028973d547133f3be0d473c1fdae9f14d89bf509267759198ae1fbe89802079a7e217ddd990d734 - languageName: node - linkType: hard - -"y18n@npm:^5.0.5": - version: 5.0.8 - resolution: "y18n@npm:5.0.8" - checksum: 10c0/4df2842c36e468590c3691c894bc9cdbac41f520566e76e24f59401ba7d8b4811eb1e34524d57e54bc6d864bcb66baab7ffd9ca42bf1eda596618f9162b91249 + checksum: 10c0/7b28dc2863ea0e2cece68d142a3eee90361021b73f750431e6d8076bb7dede5fdfdb75b3d29534b62f411261147b76b1bc80fb5cc63ab0aabd467280e85b22e0 languageName: node linkType: hard -"yallist@npm:^3.0.2": - version: 3.1.1 - resolution: "yallist@npm:3.1.1" - checksum: 10c0/c66a5c46bc89af1625476f7f0f2ec3653c1a1791d2f9407cfb4c2ba812a1e1c9941416d71ba9719876530e3340a99925f697142989371b72d93b9ee628afd8c1 +"xmlhttprequest-ssl@npm:~2.1.1": + version: 2.1.2 + resolution: "xmlhttprequest-ssl@npm:2.1.2" + checksum: 10c0/70d60869323e823f473a238f78fd108437edbc3690ecd5859c39c83217080090a18899b272e515769c0d1f518cc64cbed6b6995b23fdd7ba13b297d530b6e631 languageName: node linkType: hard -"yallist@npm:^4.0.0": - version: 4.0.0 - resolution: "yallist@npm:4.0.0" - checksum: 10c0/2286b5e8dbfe22204ab66e2ef5cc9bbb1e55dfc873bbe0d568aa943eb255d131890dfd5bf243637273d31119b870f49c18fcde2c6ffbb7a7a092b870dc90625a +"yallist@npm:^5.0.0": + version: 5.0.0 + resolution: "yallist@npm:5.0.0" + checksum: 10c0/a499c81ce6d4a1d260d4ea0f6d49ab4da09681e32c3f0472dee16667ed69d01dae63a3b81745a24bd78476ec4fcf856114cb4896ace738e01da34b2c42235416 languageName: node linkType: hard "yaml@npm:^1.10.0": - version: 1.10.2 - resolution: "yaml@npm:1.10.2" - checksum: 10c0/5c28b9eb7adc46544f28d9a8d20c5b3cb1215a886609a2fd41f51628d8aaa5878ccd628b755dbcd29f6bb4921bd04ffbc6dcc370689bb96e594e2f9813d2605f - languageName: node - linkType: hard - -"yargs-parser@npm:^20.2.2, yargs-parser@npm:^20.2.9": - version: 20.2.9 - resolution: "yargs-parser@npm:20.2.9" - checksum: 10c0/0685a8e58bbfb57fab6aefe03c6da904a59769bd803a722bb098bd5b0f29d274a1357762c7258fb487512811b8063fb5d2824a3415a0a4540598335b3b086c72 - languageName: node - linkType: hard - -"yargs-unparser@npm:^2.0.0": - version: 2.0.0 - resolution: "yargs-unparser@npm:2.0.0" - dependencies: - camelcase: "npm:^6.0.0" - decamelize: "npm:^4.0.0" - flat: "npm:^5.0.2" - is-plain-obj: "npm:^2.1.0" - checksum: 10c0/a5a7d6dc157efa95122e16780c019f40ed91d4af6d2bac066db8194ed0ec5c330abb115daa5a79ff07a9b80b8ea80c925baacf354c4c12edd878c0529927ff03 - languageName: node - linkType: hard - -"yargs@npm:^16.2.0": - version: 16.2.0 - resolution: "yargs@npm:16.2.0" - dependencies: - cliui: "npm:^7.0.2" - escalade: "npm:^3.1.1" - get-caller-file: "npm:^2.0.5" - require-directory: "npm:^2.1.1" - string-width: "npm:^4.2.0" - y18n: "npm:^5.0.5" - yargs-parser: "npm:^20.2.2" - checksum: 10c0/b1dbfefa679848442454b60053a6c95d62f2d2e21dd28def92b647587f415969173c6e99a0f3bab4f1b67ee8283bf735ebe3544013f09491186ba9e8a9a2b651 + version: 1.10.3 + resolution: "yaml@npm:1.10.3" + checksum: 10c0/c309ff85a0a569a981d71ab9cf0fef68672a16b9cdf40639d1c3b30034f6cd16ee428602bd6d64ecf006f8c8bee499023cac236538f79898aa99fb5db529a2ed languageName: node linkType: hard