diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0190a98 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.{reg,plist,mobileconfig}] +end_of_line = crlf diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..03ddfd4 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,14 @@ +# Normalize line endings to LF in the repository (prevents CRLF/LF drift). +* text=auto eol=lf + +# Binary assets — never normalize. +*.png binary +*.jpg binary +*.jpeg binary +*.ico binary +*.gif binary +*.pem binary +*.crx binary + +# Windows registry files need CRLF. +*.reg text eol=crlf diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..4d7efb2 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,16 @@ +# This is the CODEOWNERS file for the repository. +# It defines who is responsible (owner) for reviewing changes to certain files or folders. +# When someone opens a pull request that modifies these files, +# GitHub will automatically request a review from the listed owners. + +# Syntax: +# pattern owner(s) +# - Patterns work like .gitignore rules (wildcards, folders, extensions). +# - Owners can be GitHub usernames (@username) or organization teams (@org/team). +# - Multiple owners can be assigned, separated by spaces. + +# ------------------------------------------------------------------- +# Default rule: assign all files (*) in the repository to @bauer-group/core. +# This means every pull request will automatically request a review from you, +# unless a more specific rule matches first. +* @bauer-group/core diff --git a/.github/config/commitlint.config.js b/.github/config/commitlint.config.js new file mode 100644 index 0000000..7a452f8 --- /dev/null +++ b/.github/config/commitlint.config.js @@ -0,0 +1,16 @@ +/** + * Conventional Commits enforcement, read by + * bauer-group/automation-templates/.github/workflows/modules-pr-validation.yml. + * + * Kept in sync with the root commitlint.config.js, which serves the local + * simple-git-hooks commit-msg hook. Two files because the two consumers look in + * different places; without this one the CI commitlint step degrades silently + * (the module runs it under continue-on-error). + */ +export default { + extends: ['@commitlint/config-conventional'], + rules: { + 'subject-case': [0], + 'body-max-line-length': [0], + }, +}; diff --git a/.github/config/release/semantic-release.json b/.github/config/release/semantic-release.json new file mode 100644 index 0000000..8131291 --- /dev/null +++ b/.github/config/release/semantic-release.json @@ -0,0 +1,27 @@ +{ + "branches": ["main"], + "plugins": [ + "@semantic-release/commit-analyzer", + "@semantic-release/release-notes-generator", + [ + "@semantic-release/changelog", + { + "changelogFile": "CHANGELOG.md" + } + ], + [ + "@semantic-release/npm", + { + "npmPublish": false + } + ], + [ + "@semantic-release/git", + { + "assets": ["CHANGELOG.md", "package.json", "package-lock.json"], + "message": "chore(release): ${nextRelease.version}\n\n${nextRelease.notes}" + } + ], + "@semantic-release/github" + ] +} diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..06d05a2 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,74 @@ +# ============================================================================= +# Dependabot Configuration +# ============================================================================= +# Watches: +# 1. GitHub Actions versions +# 2. npm dependencies (runtime + build tooling) +# +# COMMIT PREFIXES DECIDE WHETHER AN UPDATE SHIPS +# release.yml only publishes assets when semantic-release actually cuts a +# release (`if: needs.release.outputs.release-created == 'true'`). A +# `chore(...)` update therefore lands on main but never reaches a published +# .crx — it rides along with whatever releasing commit happens to come next, +# which for a security patch can be weeks. +# +# So npm uses `fix(deps)` and cuts its own patch: in a bundled extension +# nearly everything except pure linting tooling ends up inside the shipped +# artifact, the build chain (wxt, vite, tailwind) very much included. GitHub +# Actions stays on `chore(ci)` — it never enters the bundle. +# +# Grouping bounds the cost: one PR per ecosystem per week, so at most one +# patch release a week originates from dependency traffic. +# ============================================================================= + +version: 2 +updates: + # --------------------------------------------------------------------------- + # GitHub Actions + # --------------------------------------------------------------------------- + - package-ecosystem: 'github-actions' + directory: '/' + schedule: + interval: 'weekly' + day: 'sunday' + time: '06:30' + timezone: 'Etc/UTC' + labels: + - 'dependencies' + - 'github-actions' + - 'dependabot' + commit-message: + prefix: 'chore(ci)' + groups: + github-actions: + patterns: + - '*' + + # --------------------------------------------------------------------------- + # npm + # --------------------------------------------------------------------------- + - package-ecosystem: 'npm' + directory: '/' + schedule: + interval: 'weekly' + day: 'sunday' + time: '06:30' + timezone: 'Etc/UTC' + labels: + - 'dependencies' + - 'npm' + - 'dependabot' + commit-message: + prefix: 'fix(deps)' + groups: + npm: + patterns: + - '*' + ignore: + # TypeScript 7 breaks `npm run lint`: typescript-eslint@8 declares peer + # `typescript: ">=4.8.4 <6.1.0"`, TS 7 shipped without a stable + # programmatic API, and typescript-eslint closed TS7 support as + # "not planned". Without this entry the PR arrives weekly looking + # entirely harmless. Revisit when typescript-eslint ships TS7 support. + - dependency-name: 'typescript' + versions: ['>=7'] diff --git a/.github/workflows/ai-issue-summary.yml b/.github/workflows/ai-issue-summary.yml new file mode 100644 index 0000000..bcdde4b --- /dev/null +++ b/.github/workflows/ai-issue-summary.yml @@ -0,0 +1,33 @@ +name: 🤖 Issue AI Summary + +on: + issues: + types: [opened] + + pull_request_target: + types: [opened] + +permissions: + issues: write + pull-requests: write + contents: read + models: read + +jobs: + summarize-new-issue: + name: 🧠 Generate AI Summary + if: github.event_name == 'issues' || github.event_name == 'pull_request_target' + uses: bauer-group/automation-templates/.github/workflows/modules-ai-issue-summary.yml@main + with: + summary-type: 'technical' + add-labels: true + add-priority: true + translate: '' + comment-template: | + ## AI Analysis + + {summary} + + --- + *This summary was automatically generated by AI to help with triage and may not be 100% accurate.* + secrets: inherit diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..2e2d1aa --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,94 @@ +# ============================================================================= +# CI Gate +# ============================================================================= +# WHAT: Lint, format, typecheck, test with coverage, audit, and build all three +# browser targets. +# WHY: These gates used to live inside release.yml and therefore ran AFTER +# code had landed on main. This runs them before a merge is possible, +# and release.yml reuses this exact file via workflow_call so the +# pre-merge gate and the release gate cannot drift apart. +# WHEN: Every PR, every push to main, on demand, and as the release gate. +# ============================================================================= + +name: 🔍 CI + +on: + pull_request: + branches: [main] + types: [opened, synchronize, reopened, ready_for_review] + push: + branches: [main] + workflow_dispatch: + workflow_call: + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + gate: + name: 🧪 Quality Gate + # nodejs-build.yml calls modules-code-quality.yml, which declares + # `pull-requests: write`. GitHub validates a called workflow's permissions + # when the run is CREATED — before any job `if:` — so this is required even + # though Sonar is off. Omitting the block does not help: a partial block + # sets every unnamed scope to none, and the repo default is already + # pull-requests: none. Without it: startup_failure, no job, no log. + permissions: + contents: read + pull-requests: write + uses: bauer-group/automation-templates/.github/workflows/nodejs-build.yml@main + with: + package-manager: 'npm' + frozen-lockfile: true + + # Node 24 is the floor declared in .nvmrc and engines.node; 26 becomes LTS + # on 2026-10-28 and is tested now so the promotion is a non-event. + # + # The versions are literals here rather than `node-version-file: .nvmrc` + # ON PURPOSE, against the usual house rule. nodejs-build.yml resolves the + # runtime as: + # node-version: ${{ inputs.node-version-file == '' && (... matrix.node ...) || '' }} + # so passing node-version-file blanks node-version and setup-node reads + # .nvmrc instead — collapsing BOTH matrix legs onto Node 24. You get two + # identical green jobs and no forward coverage at all. The release job in + # release.yml is the one that pins .nvmrc, and it must stay single. + enable-matrix: true + matrix-node: '["24", "26"]' + + run-lint: true + lint-command: 'npm run lint' + run-format-check: true + format-command: 'npm run format:check' + run-typecheck: true + typecheck-command: 'npm run typecheck' + + # `npm run test:coverage` runs the suite once WITH coverage, and + # vitest.config.ts fails it below 80% on src/lib. test-coverage stays + # false deliberately: enabling it re-runs the whole suite a second time, + # and its `coverage-threshold` input is an unimplemented stub in the + # reusable ("# Add coverage threshold check logic here"), so relying on + # it would be a gate that silently passes everything. + run-tests: true + test-command: 'npm run test:coverage' + test-coverage: false + + run-audit: true + audit-level: 'high' + + # WXT builds one browser per invocation; three sequential builds take a + # few seconds each and keep this on the reusable rather than forcing a + # hand-rolled matrix. + build-command: 'npm run build && npm run build:firefox && npm run build:edge' + + # The reusable's default artifact glob is dist|build|lib|coverage. WXT + # emits .output/, so without this nothing would be uploaded while the + # step still reported success. + upload-artifacts: true + artifact-name: 'extension-builds' + artifact-path: '.output' + artifact-retention-days: 14 + secrets: inherit diff --git a/.github/workflows/crome-extension-release.yml b/.github/workflows/crome-extension-release.yml deleted file mode 100644 index db87d7e..0000000 --- a/.github/workflows/crome-extension-release.yml +++ /dev/null @@ -1,64 +0,0 @@ -name: Build & Release Chrome Extension - -on: - push: - branches: - - main - - master - -permissions: - contents: write - packages: read - -jobs: - build-release: - runs-on: ubuntu-latest - steps: - - name: Checkout Repository - uses: actions/checkout@v4 - - - name: Cache node_modules - uses: actions/cache@v4 - with: - path: node_modules - key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} - - - name: Use Node.js - uses: actions/setup-node@v4 - with: - node-version: 20 - - - name: Install Dependencies - run: npm ci - - - name: Build Extension (updates version, creates ZIP) - run: npm run build - - - name: Extract Version from manifest.json - id: get_version - run: echo "version=$(jq -r .version src/manifest.json)" >> $GITHUB_OUTPUT - - - name: Commit updated manifest.json - run: | - git config user.email "github-actions[bot]@users.noreply.github.com" - git config user.name "github-actions[bot]" - git add src/manifest.json - git commit -m "chore(manifest): bump version to ${{ steps.get_version.outputs.version }} [skip ci]" || echo "No changes to commit" - git push origin ${{ github.ref_name }} - - - name: Create Tag - run: | - git config user.email "github-actions[bot]@users.noreply.github.com" - git config user.name "github-actions[bot]" - git tag v${{ steps.get_version.outputs.version }} - git push origin v${{ steps.get_version.outputs.version }} - - - name: Create GitHub Release - uses: softprops/action-gh-release@v2 - with: - tag_name: v${{ steps.get_version.outputs.version }} - name: v${{ steps.get_version.outputs.version }} - files: | - dist-zip/*.zip - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/dependabot-maintenance.yml b/.github/workflows/dependabot-maintenance.yml new file mode 100644 index 0000000..34ec056 --- /dev/null +++ b/.github/workflows/dependabot-maintenance.yml @@ -0,0 +1,43 @@ +# ============================================================================= +# Dependabot Maintenance +# ============================================================================= +# WHAT: Approves Dependabot PRs and enables GitHub's native auto-merge. +# WHY: Auto-merge waits for the required status checks, so ci.yml still +# decides. This only removes the human click from an update that has +# already proven itself green. +# WHEN: Every Dependabot pull request. +# +# REQUIRES: repository setting `allow_auto_merge = true` AND branch protection +# with required status checks on main. Without required checks, auto-merge +# merges immediately and this degrades into a rubber stamp. +# +# The module is named docker-maintenance-* for historical reasons; it is +# ecosystem-agnostic and gates purely on the actor being dependabot[bot]. +# ============================================================================= + +name: 🤖 Dependabot Maintenance + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + +permissions: + contents: read + +jobs: + automerge: + name: 🔀 Auto-merge Dependabot PRs + # The module gates on the actor internally too; checking here as well + # avoids spinning up a runner for every human PR. + if: github.actor == 'dependabot[bot]' + permissions: + contents: write + pull-requests: write + uses: bauer-group/automation-templates/.github/workflows/docker-maintenance-dependabot.yml@main + with: + # Squash keeps one commit per update, and Dependabot's PR title carries + # the conventional prefix from dependabot.yml (fix(deps) / chore(ci)) — + # so the squashed subject is what semantic-release reads. + merge-method: 'squash' + auto-approve: true + secrets: inherit diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml new file mode 100644 index 0000000..51019ce --- /dev/null +++ b/.github/workflows/pr-validation.yml @@ -0,0 +1,59 @@ +# ============================================================================= +# Pull Request Gates +# ============================================================================= +# WHAT: Conventional-commit validation, secret scanning, license compliance, +# and dependency review. +# WHY: These only make sense with a PR context — they diff head against base. +# ci.yml cannot host them: it also runs on push and as the release gate, +# where there is no PR to diff against. +# WHEN: Every pull request targeting main. +# ============================================================================= + +name: 🔍 PR Gates + +on: + pull_request: + branches: [main] + types: [opened, synchronize, reopened, ready_for_review] + +concurrency: + group: pr-gates-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + validate: + name: ✅ Commits, Secrets & Licenses + permissions: + contents: read + pull-requests: write + security-events: write + checks: write + uses: bauer-group/automation-templates/.github/workflows/modules-pr-validation.yml@main + with: + # Reads .github/config/commitlint.config.js. Without that file the step + # degrades silently under continue-on-error rather than failing. + enable-commit-lint: true + enable-security-scan: true + fail-on-security-issues: true + # Reported, not blocking: this repo ships MIT but pulls a broad React / + # Radix tree whose transitive licence metadata is noisy. A hard fail here + # would block PRs on someone else's package.json typo. + enable-license-check: true + fail-on-license-issues: false + secrets: inherit + + dependency-review: + name: 🛡️ Dependency Review + permissions: + contents: read + pull-requests: write + uses: bauer-group/automation-templates/.github/workflows/modules-dependency-review.yml@main + with: + fail-on-severity: 'high' + comment-summary-in-pr: 'on-failure' + # Runtime scope only. A dev-only advisory does not reach the shipped + # .crx, and blocking a PR on one trains people to bypass the gate. + fail-on-scopes: 'runtime' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..814572b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,163 @@ +# ============================================================================= +# Release +# ============================================================================= +# WHAT: Gate → semantic release → publish signed assets → (optional) Chrome +# Web Store. +# WHY: The previous version called `npm version patch` unconditionally, so a +# feat: never produced a MINOR and a BREAKING CHANGE: never produced a +# MAJOR — the repo enforced Conventional Commits through commitlint and +# then discarded the information they carry. Its publish job also +# re-checked-out the pre-bump commit, so it could package a version +# other than the one released. +# WHEN: Push to main, or manual dispatch. +# +# NO concurrency group here on purpose: semantic-release hard-resets to origin, +# and cancelling it mid-flight can leave a tag pushed with no release attached. +# ============================================================================= + +name: 🚀 Release + +on: + push: + branches: [main] + paths-ignore: + - '*.md' + - 'docs/**' + workflow_dispatch: + inputs: + force-release: + description: 'force create release' + type: boolean + default: false + +permissions: + contents: read + +jobs: + gate: + name: 🔍 Gate + # The same file every PR runs — see ci.yml. pull-requests: write is the + # permission ceiling nodejs-build.yml imposes further down the chain. + permissions: + contents: read + pull-requests: write + uses: ./.github/workflows/ci.yml + secrets: inherit + + release: + name: 📦 Semantic Release + needs: [gate] + if: needs.gate.result == 'success' + permissions: + contents: write + issues: write + pull-requests: write + uses: bauer-group/automation-templates/.github/workflows/modules-semantic-release.yml@main + with: + target-branch: 'main' + node-version: '24' + force-release: ${{ inputs.force-release || false }} + secrets: inherit + + publish-assets: + name: 📤 Publish Release Assets + needs: [release] + if: needs.release.outputs.release-created == 'true' + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: write + steps: + - name: 📥 Checkout release tag + uses: actions/checkout@v7 + with: + # The released tag, NOT github.sha. semantic-release commits the + # version bump itself, so building from the pushed commit produces a + # .crx whose manifest version does not match the release it is + # attached to. + ref: ${{ needs.release.outputs.tag-name }} + + - name: 📦 Setup Node.js + uses: actions/setup-node@v6 + with: + node-version-file: '.nvmrc' + cache: npm + + - name: 📚 Install dependencies + run: npm ci + + - name: 🏗️ Build & zip (Chrome, Firefox, Edge) + run: | + npm run build + npm run zip + npm run zip:firefox + npm run zip:edge + + - name: 🔏 Sign CRX + generate updates.xml + env: + EXTENSION_PRIVATE_KEY: ${{ secrets.EXTENSION_PRIVATE_KEY }} + GITHUB_REPOSITORY: ${{ github.repository }} + # Hard-fails when the secret is absent — the runner sets CI=true. A + # green release with no .crx leaves every force-installed client + # silently pinned to the old version. + run: npm run sign:crx + + - name: 📎 Attach assets to the release + uses: softprops/action-gh-release@v3 + with: + tag_name: ${{ needs.release.outputs.tag-name }} + files: | + .output/*-chrome.zip + .output/*-firefox.zip + .output/*-edge.zip + .output/header-authenticator.crx + .output/updates.xml + + publish-web-store: + name: 📤 Publish to Chrome Web Store + needs: [release, publish-assets] + # Wired but off. Set the repository variable PUBLISH_WEB_STORE=true and add + # the CWS_* secrets once a listing exists. v1 was never published from this + # repo and v2 pins a new extension ID, so there is nothing to update yet. + if: needs.release.outputs.release-created == 'true' && vars.PUBLISH_WEB_STORE == 'true' + # Best-effort: a Web Store hiccup stays a visible red job but must not + # invalidate a release whose GitHub assets already published correctly. + continue-on-error: true + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + steps: + - name: 📥 Checkout release tag + uses: actions/checkout@v7 + with: + ref: ${{ needs.release.outputs.tag-name }} + + - name: 📦 Setup Node.js + uses: actions/setup-node@v6 + with: + node-version-file: '.nvmrc' + cache: npm + + - name: 📚 Install dependencies + run: npm ci + + - name: 🏗️ Build & zip (Chrome) + run: | + npm run build + npm run zip + + - name: 📤 Upload to Chrome Web Store + env: + CWS_CLIENT_ID: ${{ secrets.CWS_CLIENT_ID }} + CWS_CLIENT_SECRET: ${{ secrets.CWS_CLIENT_SECRET }} + CWS_REFRESH_TOKEN: ${{ secrets.CWS_REFRESH_TOKEN }} + CWS_EXTENSION_ID: ${{ secrets.CWS_EXTENSION_ID }} + run: | + ZIP="$(ls .output/*-chrome.zip | head -1)" + npx --yes chrome-webstore-upload-cli@3 upload \ + --source "$ZIP" \ + --extension-id "$CWS_EXTENSION_ID" \ + --client-id "$CWS_CLIENT_ID" \ + --client-secret "$CWS_CLIENT_SECRET" \ + --refresh-token "$CWS_REFRESH_TOKEN" diff --git a/.gitignore b/.gitignore index d492607..194fb0c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,135 +1,28 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-debug.log* -.pnpm-debug.log* - -# Diagnostic reports (https://nodejs.org/api/report.html) -report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage -*.lcov - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories +# Dependencies node_modules/ -jspm_packages/ - -# Snowpack dependency directory (https://snowpack.dev/) -web_modules/ - -# TypeScript cache -*.tsbuildinfo - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional stylelint cache -.stylelintcache - -# Microbundle cache -.rpt2_cache/ -.rts2_cache_cjs/ -.rts2_cache_es/ -.rts2_cache_umd/ -# Optional REPL history -.node_repl_history +# WXT build artifacts & generated types (NEVER committed) +.output/ +.wxt/ -# Output of 'npm pack' -*.tgz +# Test/coverage +coverage/ -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variable files +# Signing keys & secrets (NEVER committed) +.keys/ +*.pem .env -.env.development.local -.env.test.local -.env.production.local -.env.local - -# parcel-bundler cache (https://parceljs.org/) -.cache -.parcel-cache - -# Next.js build output -.next -out - -# Nuxt.js build / generate output -.nuxt -dist -dist-zip - -# Gatsby files -.cache/ -# Comment in the public line in if your project uses Gatsby and not Next.js -# https://nextjs.org/blog/next-9-1#public-directory-support -# public - -# vuepress build output -.vuepress/dist - -# vuepress v2.x temp and cache directory -.temp -.cache - -# Docusaurus cache and generated files -.docusaurus - -# Serverless directories -.serverless/ - -# FuseBox cache -.fusebox/ - -# DynamoDB Local files -.dynamodb/ - -# TernJS port file -.tern-port - -# Stores VSCode versions used for testing VSCode extensions -.vscode-test - -# yarn v2 -.yarn/cache -.yarn/unplugged -.yarn/build-state.yml -.yarn/install-state.gz -.pnp.* - -# IDE -.vscode/ -.claude/ +.env.* +!.env.example +!.env.template + +# Build leftovers +dist/ +stats.html +*.zip +*.crx + +# OS / editor +.DS_Store +Thumbs.db +*.log diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..a45fd52 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +24 diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..6c6d738 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,8 @@ +.wxt +.output +node_modules +dist +coverage +.keys +pnpm-lock.yaml +package-lock.json diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..4cbc711 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,7 @@ +{ + "semi": true, + "singleQuote": true, + "trailingComma": "all", + "printWidth": 100, + "tabWidth": 2 +} diff --git a/LICENSE.TXT b/LICENSE similarity index 93% rename from LICENSE.TXT rename to LICENSE index aeb26f6..2b6c3c1 100644 --- a/LICENSE.TXT +++ b/LICENSE @@ -1,8 +1,6 @@ -The MIT License (MIT) +MIT License -Copyright (c) 2025 BAUER GROUP - -All rights reserved. +Copyright (c) BAUER GROUP Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.MD b/README.MD deleted file mode 100644 index f1e51c9..0000000 --- a/README.MD +++ /dev/null @@ -1,86 +0,0 @@ -# IP Chrome HeaderAuth - -**IP Chrome HeaderAuth** is a Chrome extension designed to send authentication headers for application access. This tool is useful for environments requiring header-based authentication, such as corporate web applications or internal tools. - -## Features - -- Sends custom authentication headers with web requests. -- Easily configurable via extension options. -- Supports TypeScript for maintainable code and future enhancements. - -## Getting Started - -### Prerequisites - -- Node.js (recommended v20+) -- npm (Node Package Manager) -- Chrome browser - -### Installation - -1. **Clone the repository:** - ```bash - git clone https://github.com/bauer-group/IP-Chrome-HeaderAuth.git - cd IP-Chrome-HeaderAuth - ``` - -2. **Install dependencies:** - ```bash - npm install - ``` - -### Build Instructions - -The build process bundles the extension for Chrome. Key scripts and their purpose: - -- `npm run build`: Builds the project, updates the version, copies assets, and zips the final package. -- `npm run clean`: Removes all build artifacts (`dist`, `dist-zip`). -- `npm run build-icons`: Builds icon assets for the extension. -- Intermediate scripts: - - `_version-update`: Updates the extension version. - - `_copy`: Copies necessary files and assets to the distribution folder. - - `_zip`: Zips the build output with the current version. - -**To build the extension:** -```bash -npm run build -``` - -**To clean build artifacts:** -```bash -npm run clean -``` - -**To build icons separately:** -```bash -npm run build-icons -``` - -### Manual Installation in Chrome - -1. Build the extension (`npm run build`). -2. Open Chrome and navigate to `chrome://extensions/`. -3. Enable "Developer mode". -4. Click "Load unpacked" and select the `dist` folder in your project directory. - -### Usage - -- Configure the extension via the options page (`options.html` in the extension popup). -- The extension automatically attaches authentication headers to outgoing requests based on your configuration. - -## Contributing - -1. Fork the repository. -2. Create your feature branch (`git checkout -b feature/AmazingFeature`). -3. Commit your changes (`git commit -m 'Add some feature'`). -4. Push to the branch (`git push origin feature/AmazingFeature`). -5. Open a pull request. - -## License - -Distributed under the MIT License. See `LICENSE` for more information. - -## Contact - -For questions or support, please open an issue on GitHub. - diff --git a/README.md b/README.md new file mode 100644 index 0000000..36d920c --- /dev/null +++ b/README.md @@ -0,0 +1,99 @@ +# BAUER GROUP – Header Authenticator + +A Chrome/Brave/Edge (Manifest V3) extension that transparently protects internal +services behind a header-based authentication barrier. It injects a configurable +secret header (a GUID, default `X-BAUERGROUP-Auth`) into outgoing requests to the +domains you configure; a reverse proxy / WAF in front of the service validates it. + +> Greenfield successor to the original `BAUERGROUP.Extension.Chrome.HeaderAuth`. +> Built on a modern stack and a flexible rule model. The legacy repository is archived. + +## Features + +- **Rule/profile model** – any number of rules, each with its own domains, header + name and secret. Different service groups can use different GUIDs. +- **Professional UI** – a compact status popup and a full options page (CRUD table, + validated add/edit dialog, masked secret with reveal/generate/copy, domain chips, + JSON import/export). German + English, switchable in-app. +- **Cross-device sync** – configuration syncs via the browser account + (`chrome.storage.sync`); secrets can optionally be kept device-local per rule. +- **Enterprise rollout** – central provisioning via `chrome.storage.managed` + (GPO/MDM). Managed rules are read-only with an organization badge. +- **Least privilege** – only the canonical `*.app.bauer-group.com` is granted by + default; user-added domains request host access at runtime. + +**Extension ID (pinned):** `jncjhkagdjiiohjfmbpmlemdchbkjaib` + +## Tech stack + +WXT (Vite-based) · React 19 · Tailwind 4 · shadcn-pattern components (Radix) · +Zod 4 · TypeScript (strict+) · ESLint flat + Prettier · Vitest · commitlint + simple-git-hooks. + +## Development + +```bash +npm install # also runs `wxt prepare` (generates .wxt types) +npm run dev # Chrome dev with HMR +npm run dev:firefox # Firefox dev with HMR + +npm run typecheck +npm run lint +npm test +``` + +## Build & package + +```bash +npm run build # unpacked build -> .output/chrome-mv3/ (for "Load unpacked") +npm run zip # distributable zip -> .output/*-chrome.zip +npm run zip:firefox # .output/*-firefox.zip +npm run sign:crx # signed .crx + updates.xml (needs .keys/key.pem locally) +``` + +Three first-class artifacts are produced: + +| Artifact | Command | Purpose | +| ----------------------------- | ----------------------------------- | --------------------------------------- | +| Unpacked (`dist`) | `wxt build` → `.output/chrome-mv3/` | Developer install via "Load unpacked" | +| Distributable ZIP | `wxt zip` → `.output/*.zip` | Web Store upload / manual sharing | +| Signed `.crx` + `updates.xml` | `npm run sign:crx` | Self-hosted force-install + auto-update | + +## Install (developer / manual) + +1. `npm install && npm run build` +2. Open `chrome://extensions` (or `brave://`, `edge://`), enable **Developer mode**. +3. **Load unpacked** → select `.output/chrome-mv3`. + +## Distribution to workstations + +The extension reaches managed workstations via **enterprise force-install policy**, +through two parallel channels (same extension ID, one channel per machine): + +- **Self-hosted** – signed `.crx` + `updates.xml` published as GitHub Release assets; + `update_url` points at `…/releases/latest/download/updates.xml`. +- **Chrome Web Store (unlisted)** – `update_url` is the Web Store update service. + +See [`docs/deployment/`](docs/deployment/) for ready-to-use GPO `.reg`, macOS +configuration profile, and managed-config examples. + +## Signing key + +The signing key pair gives the extension its stable ID. The **private** key is +**never committed** (`.keys/key.pem`, gitignored). In CI it is provided as the +`EXTENSION_PRIVATE_KEY` secret. The matching public key is pinned as `key` in +`wxt.config.ts`. Losing the private key changes the ID and breaks force-install +policies — store it in the team password manager. + +## Security notes + +- **Shipped code has 0 known vulnerabilities.** The dev-only audit findings trace to + `wxt → web-ext-run` (the Firefox dev runner) and never ship. +- **Secrets in sync** are stored unencrypted in the browser account cloud (the + cross-device-sync trade-off). For governed secrets, push them via managed policy, + or disable "Sync secret across devices" on a rule to keep it device-local. +- **WSS is best-effort.** Header modification on WebSocket upgrades initiated from a + service worker is unreliable (Chromium limitation); page-initiated WebSockets work. + +## License + +MIT diff --git a/buildIcons.ts b/buildIcons.ts deleted file mode 100644 index ef976ed..0000000 --- a/buildIcons.ts +++ /dev/null @@ -1,14 +0,0 @@ -import * as sharp from 'sharp'; - -const sizes = [16, 19, 24, 32, 48, 64, 128]; -const sourceIcon = 'resources/IconSourceFile_Transparent.png'; -const outputDir = 'resources/icons/'; - -sizes.forEach(size => { - sharp(sourceIcon) - .resize(size, size) - .toFile(`${outputDir}${size}x${size}.png`, (err: Error) => { - if (err) throw err; - console.log(`Icon ${size}x${size} created`); - }); -}); diff --git a/commitlint.config.js b/commitlint.config.js new file mode 100644 index 0000000..094fc83 --- /dev/null +++ b/commitlint.config.js @@ -0,0 +1,11 @@ +/** + * Conventional Commits enforcement (BAUER GROUP commit standard). + * @see https://www.conventionalcommits.org + */ +export default { + extends: ['@commitlint/config-conventional'], + rules: { + 'subject-case': [0], + 'body-max-line-length': [0], + }, +}; diff --git a/docs/deployment/README.md b/docs/deployment/README.md new file mode 100644 index 0000000..8b8d421 --- /dev/null +++ b/docs/deployment/README.md @@ -0,0 +1,72 @@ +# Enterprise deployment + +Force-install the extension on managed workstations and (optionally) push the +protection rules centrally. Chrome, Brave and Edge are all Chromium-based and honor +the same policy keys. + +| Fact | Value | +| ---------------------- | ------------------------------------------------------------------------------------------ | +| Extension ID | `jncjhkagdjiiohjfmbpmlemdchbkjaib` | +| Self-hosted update URL | `https://github.com/bauer-group/IP-Chrome-HeaderAuth/releases/latest/download/updates.xml` | +| Web Store update URL | `https://clients2.google.com/service/update2/crx` | + +> The release assets must be **anonymously downloadable** — the browser fetches +> `updates.xml`/`.crx` with no authentication, so a private repository returns 404 on +> every managed device and the force-install silently never updates. Verify with +> `curl -sSIL -o /dev/null -w '%{http_code}\n' ` from a shell with no +> GitHub token in the environment; anything other than `200` means this channel is +> dead. If the repository ever returns to private, host the two files on internal +> HTTPS / MinIO and repoint `update_url` there. + +## 1. Force-install (the extension itself) + +Use **one** channel per machine — set `update_url` to either the self-hosted +`updates.xml` or the Web Store service, not both. + +### Windows (Group Policy / registry) + +Force-install value format: `";"`. See +[`force-install-windows.reg`](force-install-windows.reg) for a ready file (Chrome, +Brave, Edge). Key paths: + +- Chrome: `HKLM\SOFTWARE\Policies\Google\Chrome\ExtensionInstallForcelist` +- Brave: `HKLM\SOFTWARE\Policies\BraveSoftware\Brave\ExtensionInstallForcelist` +- Edge: `HKLM\SOFTWARE\Policies\Microsoft\Edge\ExtensionInstallForcelist` + +With the official ADMX templates you can set the same under +_Computer Configuration → Administrative Templates → Google/Chrome → Extensions → +Configure the list of force-installed apps and extensions_. + +### macOS (configuration profile / MDM) + +Deploy [`macos-chrome-forcelist.mobileconfig`](macos-chrome-forcelist.mobileconfig) +via your MDM (Jamf, Intune, etc.). It sets `ExtensionInstallForcelist` for +`com.google.Chrome` (duplicate the payload for `com.brave.Browser` / +`com.microsoft.Edge` as needed). + +## 2. Push the configuration (managed storage) + +Provisioning rules centrally makes protection truly transparent — users get the +correct domains + secrets automatically and cannot edit them. The policy shape is +defined by [`../../src/public/managed-schema.json`](../../src/public/managed-schema.json); +an example payload is in [`managed-config.example.json`](managed-config.example.json). + +- **Google Admin console / Microsoft Intune / Jamf** – paste the JSON as the + managed configuration for the extension ID. This is the recommended path for the + rule array. +- **Windows registry** – Chromium reads managed storage from + `HKLM\SOFTWARE\Policies\Google\Chrome\3rdparty\extensions\jncjhkagdjiiohjfmbpmlemdchbkjaib\policy`. + Simple values (`masterEnabled`) map directly; arrays/objects (`rules`) use numbered + subkeys — the Admin console / Intune JSON path is far less error-prone for those. + +Effective configuration = **managed rules (read-only) ∪ user rules**. A managed +`masterEnabled` overrides the in-app master switch. + +## 3. Verify on a test machine + +1. Apply the force-install policy, restart the browser. +2. `chrome://extensions` shows the extension installed and **managed** (not removable). +3. If a managed config was pushed, the options page lists the managed rule(s) with the + _"Managed by your organization"_ badge. +4. On a protected domain, DevTools → Network shows the `X-BAUERGROUP-Auth` request + header. `chrome://policy` lists the applied policies. diff --git a/docs/deployment/force-install-windows.reg b/docs/deployment/force-install-windows.reg new file mode 100644 index 0000000..4ccfe5e --- /dev/null +++ b/docs/deployment/force-install-windows.reg @@ -0,0 +1,25 @@ +Windows Registry Editor Version 5.00 + +; BAUER GROUP Header Authenticator - force-install via enterprise policy. +; The repository must be public: Chrome fetches updates.xml and the .crx anonymously. +; Use ONE update channel per machine: +; self-hosted -> .../releases/latest/download/updates.xml (shown below) +; Web Store -> https://clients2.google.com/service/update2/crx + +; --- Google Chrome --- +[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome\ExtensionInstallForcelist] +"1"="jncjhkagdjiiohjfmbpmlemdchbkjaib;https://github.com/bauer-group/IP-Chrome-HeaderAuth/releases/latest/download/updates.xml" + +; --- Brave --- +[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\BraveSoftware\Brave\ExtensionInstallForcelist] +"1"="jncjhkagdjiiohjfmbpmlemdchbkjaib;https://github.com/bauer-group/IP-Chrome-HeaderAuth/releases/latest/download/updates.xml" + +; --- Microsoft Edge --- +[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Edge\ExtensionInstallForcelist] +"1"="jncjhkagdjiiohjfmbpmlemdchbkjaib;https://github.com/bauer-group/IP-Chrome-HeaderAuth/releases/latest/download/updates.xml" + +; --- Optional: push the global master switch via managed storage (Chrome) --- +; Full rule arrays are best pushed via the Admin console / Intune JSON +; (see managed-config.example.json); simple values map directly here. +[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome\3rdparty\extensions\jncjhkagdjiiohjfmbpmlemdchbkjaib\policy] +"masterEnabled"=dword:00000001 diff --git a/docs/deployment/macos-chrome-forcelist.mobileconfig b/docs/deployment/macos-chrome-forcelist.mobileconfig new file mode 100644 index 0000000..ad54940 --- /dev/null +++ b/docs/deployment/macos-chrome-forcelist.mobileconfig @@ -0,0 +1,41 @@ + + + + + + PayloadContent + + + PayloadType + com.google.Chrome + PayloadIdentifier + com.bauer-group.headerauth.chrome.forcelist + PayloadUUID + 1A2B3C4D-0001-0000-0000-000000000001 + PayloadVersion + 1 + PayloadEnabled + + ExtensionInstallForcelist + + jncjhkagdjiiohjfmbpmlemdchbkjaib;https://github.com/bauer-group/IP-Chrome-HeaderAuth/releases/latest/download/updates.xml + + + + PayloadDisplayName + BAUER GROUP Header Authenticator - Force Install (Chrome) + PayloadIdentifier + com.bauer-group.headerauth.forcelist + PayloadType + Configuration + PayloadUUID + 1A2B3C4D-0000-0000-0000-000000000000 + PayloadVersion + 1 + + diff --git a/docs/deployment/managed-config.example.json b/docs/deployment/managed-config.example.json new file mode 100644 index 0000000..a1c16b9 --- /dev/null +++ b/docs/deployment/managed-config.example.json @@ -0,0 +1,12 @@ +{ + "masterEnabled": true, + "rules": [ + { + "label": "BAUER GROUP Apps", + "domainPatterns": ["app.bauer-group.com"], + "headerName": "X-BAUERGROUP-Auth", + "secretValue": "REPLACE-WITH-YOUR-GUID-0000-000000000000", + "enabled": true + } + ] +} diff --git a/docs/superpowers/plans/2026-08-17-cicd-und-remote-migration.md b/docs/superpowers/plans/2026-08-17-cicd-und-remote-migration.md new file mode 100644 index 0000000..43245f8 --- /dev/null +++ b/docs/superpowers/plans/2026-08-17-cicd-und-remote-migration.md @@ -0,0 +1,2186 @@ +# CI/CD Professionalization & Remote Migration — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Bring the v2 WXT extension repo to the BAUER GROUP CI/CD standard, move it to Node 24+, and land it on `bauer-group/IP-Chrome-HeaderAuth` (which holds v1 under an unrelated history) through a reviewable pull request. + +**Architecture:** Consume `bauer-group/automation-templates` reusable workflows rather than reimplementing them. A pre-merge gate (`ci.yml`) is reused verbatim by the release pipeline via `workflow_call`, so the two can never drift. Version numbers come from Conventional Commits via semantic-release; the one-time `2.0.0` baseline is seeded by an explicit tag before the merge. + +**Tech Stack:** WXT 0.21 · React 19 · TypeScript 6 · Vitest 4 · npm · GitHub Actions · semantic-release 24 + +**Spec:** [`docs/superpowers/specs/2026-08-17-cicd-und-remote-migration-design.md`](../specs/2026-08-17-cicd-und-remote-migration-design.md) + +## Global Constraints + +- **Node floor:** `engines.node` = `">=24"`, `.nvmrc` = `24`. Never a literal Node version inside a workflow — always `node-version-file: '.nvmrc'`. +- **TypeScript is pinned at `^6.0.3`.** Do not upgrade to 7.x under any circumstance: `typescript-eslint@8.67` declares peer `typescript: ">=4.8.4 <6.1.0"`, and TS 7 breaks `npm run lint`. +- **Action pins:** `actions/checkout@v7`, `actions/setup-node@v6`, `softprops/action-gh-release@v3`, `actions/upload-artifact@v7`. Internal `bauer-group/automation-templates` references pin `@main`. +- **Every job calling `nodejs-build.yml` (directly or via `ci.yml`) MUST declare `permissions: {contents: read, pull-requests: write}`.** GitHub validates called-workflow permissions at run creation, before any `if:`. Missing it produces `startup_failure` with no log. +- **Commits:** Conventional Commits, English, **past tense** subject (`added`, `fixed`, `updated`), body mandatory for non-trivial changes. Never `Co-Authored-By:` or any AI attribution. +- **Workflow style:** `name:` starts with an emoji; step names too (`📥 Checkout`, `📦 Setup Node.js`, `📚 Install dependencies`, `🎨 Lint`, `🏗️ Build`, `🧪 Test`). Every file opens with a boxed `# ===` header stating WHAT / WHY / WHEN. Explicit `permissions:` and `timeout-minutes:` everywhere. +- **Never run `git push --tags` from the v1 clone** at `C:/Projects/Internal-Projects/BAUERGROUP.Extension.Chrome.HeaderAuth` — it holds `v0.0.5`, `v0.0.16`, `v0.0.17`, which were deleted upstream and would be resurrected. + +--- + +## File Structure + +| Path | Responsibility | +| ---------------------------------------------- | ---------------------------------------------------------------------------------------------------- | +| `.nvmrc` | Single source of the Node version for CI and local dev | +| `LICENSE` | MIT text — `package.json` already declares MIT with no file present | +| `.github/CODEOWNERS` | Review ownership | +| `.github/dependabot.yml` | Update cadence + the commit prefixes that decide whether an update ships | +| `.github/config/release/semantic-release.json` | Release plugin chain (org convention: never `.releaserc` at root) | +| `.github/config/commitlint.config.js` | Path `modules-pr-validation.yml` reads; the root `commitlint.config.js` stays for the local git hook | +| `.github/workflows/ci.yml` | The gate. `workflow_call`-able so `release.yml` reuses it | +| `.github/workflows/pr-validation.yml` | Pre-merge gates that only make sense on a PR: commitlint, secret scan, license, dependency review | +| `.github/workflows/release.yml` | gate → semantic-release → asset publish → CWS (disabled) | +| `.github/workflows/codeql.yml` | SAST | +| `.github/workflows/ai-issue-summary.yml` | AI triage on new issues/PRs | +| `.github/workflows/dependabot-maintenance.yml` | Auto-merge for Dependabot | +| `vitest.config.ts` | Test env, include glob, coverage provider + threshold | +| `src/lib/*.test.ts` | New unit tests for the six untested modules | +| `scripts/sign-crx.mjs` | CRX signing — fail-loud under CI, correct repo fallback | + +--- + +## Task 1: Node 24 runtime floor + +**Files:** + +- Create: `.nvmrc` +- Create: `LICENSE` +- Modify: `package.json` (`version`, `engines.node`) + +**Interfaces:** + +- Produces: `.nvmrc` containing `24` — every later workflow reads it via `node-version-file`. + +- [ ] **Step 1: Create `.nvmrc`** + +``` +24 +``` + +(single line, no `v` prefix — matches all 19 non-external `.nvmrc` files in the estate) + +- [ ] **Step 2: Create `LICENSE`** + +``` +MIT License + +Copyright (c) BAUER GROUP + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +The yearless copyright line is the form the org's own repos use — do not add a year. + +- [ ] **Step 3: Update `package.json`** + +Change two fields: + +```json + "version": "2.0.0", + "engines": { + "node": ">=24" + }, +``` + +`">=24"` and not `^24`: Node 26 becomes LTS on 2026-10-28 and must be admitted without a manifest change. `version` moves to `2.0.0` to match the baseline tag from Task 11. + +- [ ] **Step 4: Verify the local toolchain still resolves** + +Run: `npm ls --depth=0` +Expected: no `EBADENGINE` warnings. (The local machine runs Node 25.8.1, which satisfies `>=24`.) + +- [ ] **Step 5: Commit** + +```bash +git add .nvmrc LICENSE package.json +git commit -F - <<'EOF' +build(runtime): raised the Node floor to 24 and added the MIT LICENSE + +engines.node said ">=20", which was never satisfiable by this +dependency set: lint-staged@17 requires >=22.22.1 and +@commitlint/cli@21 requires >=22.12.0. Node 20 also reached EOL on +2026-04-30. + +* engines.node ">=20" -> ">=24" (Active LTS until 2028-04-30). + ">=24" rather than "^24" so Node 26 is admitted when it reaches + LTS on 2026-10-28 without another manifest edit. +* .nvmrc pins 24 as the single source for CI and local dev — the + org convention (19 of 19 non-external .nvmrc files in the estate). +* LICENSE added; package.json and the README already claimed MIT + with no file present. +* version 1.0.0 -> 2.0.0, matching the release baseline tag. +EOF +``` + +--- + +## Task 2: Dependency refresh + +**Files:** + +- Modify: `package.json` +- Modify: `package-lock.json` (generated) + +**Interfaces:** + +- Consumes: the Node 24 floor from Task 1 (WXT 0.21 requires Node ≥ 22). +- Produces: `vite` as an explicit devDependency — WXT 0.21 declares it a required peer. + +- [ ] **Step 1: Remove the unused dependency** + +Confirm it is genuinely unused first: + +Run: `git grep -n "react-dropdown-menu" -- src/` +Expected: no output. + +Then: + +```bash +npm uninstall @radix-ui/react-dropdown-menu +``` + +- [ ] **Step 2: Upgrade WXT and add the vite peer** + +```bash +npm install --save-dev wxt@^0.21.4 vite@^8 +``` + +WXT 0.21 promoted `vite` from a bundled dependency to a required peer (`^6.3.4 || ^7 || ^8.0.0-0`). Vite 8 already resolves transitively, so this pins what is effectively there. Everything else 0.21 removed — `webextension-polyfill`, `url:` imports, `useAppConfig`, `globalName`, isolated-element — is unused in this repo, so no migration work follows. + +- [ ] **Step 3: Update everything else within caret ranges** + +```bash +npm update +``` + +This moves 26 packages, all within their declared ranges — no manifest edit. Notable: `lucide-react` 1.20 → 1.31 (11 minors). + +- [ ] **Step 4: Verify TypeScript did NOT move** + +Run: `node -p "require('./package.json').devDependencies.typescript"` +Expected: `^6.0.3` + +If it shows `^7`, revert it. See Global Constraints — TS 7 breaks `npm run lint`. + +- [ ] **Step 5: Verify the whole toolchain still works** + +Run each and confirm exit code 0: + +```bash +npm run typecheck +npm run lint +npm run format:check +npm test +npm run build +npm run build:firefox +npm run build:edge +``` + +Expected: all pass. The three builds each produce a directory under `.output/`. + +- [ ] **Step 6: Icon smoke-check** + +`lucide-react` spanned 11 minors. Open `.output/chrome-mv3/` and confirm the popup and options pages still reference icons. Cheapest check: + +Run: `git grep -n "from 'lucide-react'" -- src/ | wc -l` +Then: `npm run build 2>&1 | grep -i "error\|warn" || echo "clean"` +Expected: `clean`, and the import count unchanged from before the update. + +- [ ] **Step 7: Commit** + +```bash +git add package.json package-lock.json +git commit -F - <<'EOF' +build(deps): updated WXT to 0.21 and refreshed the dependency set + +* wxt ^0.20.26 -> ^0.21.4. 0.21 promoted vite from a bundled + dependency to a required peer, so vite is now an explicit + devDependency. Everything 0.21 removed (webextension-polyfill, + url: imports, useAppConfig, globalName, isolated-element) is + unused here, so nothing needed migrating. +* npm update moved 26 packages within their caret ranges. The + widest jump is lucide-react 1.20 -> 1.31; icon imports and the + three browser builds were verified after. +* Removed @radix-ui/react-dropdown-menu — declared but imported + nowhere in src/. + +typescript deliberately stays at ^6.0.3. npm flags 7.x as latest, +but typescript-eslint@8.67 declares peer ">=4.8.4 <6.1.0" and +closed TS7 support as not planned; upgrading breaks npm run lint. +A dependabot ignore entry guards this. +EOF +``` + +--- + +## Task 3: Test infrastructure + the pure module + +**Files:** + +- Modify: `vitest.config.ts` +- Modify: `package.json` (add `test:coverage` script, add `@vitest/coverage-v8` + `jsdom`) +- Create: `src/lib/rule-status.test.ts` + +**Interfaces:** + +- Produces: `npm run test:coverage` — the command `ci.yml` invokes in Task 8. +- Produces: a working `fakeBrowser` setup via `WxtVitest()` that Task 4 relies on. + +`src/lib/rule-status.ts` is the one untested module that is genuinely pure — it imports only a type. It goes first, before the browser-fake machinery, to prove the harness works. + +- [ ] **Step 1: Add the test dependencies** + +```bash +npm install --save-dev @vitest/coverage-v8 jsdom +``` + +No `@webext-core/fake-browser` is needed as a direct dependency: WXT re-exports it from `wxt/testing`, and it is already installed transitively. + +- [ ] **Step 2: Replace `vitest.config.ts`** + +```ts +import { defineConfig } from 'vitest/config'; +import { WxtVitest } from 'wxt/testing/vitest-plugin'; + +// WxtVitest() wires up WXT's aliases and auto-mocks `wxt/browser` with +// @webext-core/fake-browser, so modules that import the browser global are +// testable without hand-written mocks. +// +// The include glob covers .tsx as well as .ts: it previously matched only +// `*.test.ts`, so a component test would have been collected by nothing and +// silently never run. +export default defineConfig({ + plugins: [WxtVitest()], + test: { + environment: 'jsdom', + include: ['src/**/*.test.{ts,tsx}'], + restoreMocks: true, + coverage: { + provider: 'v8', + include: ['src/lib/**'], + // UI hooks and the i18n provider are exercised through components, not + // unit tests; they would drag the lib threshold down without signal. + exclude: ['src/lib/hooks/**', 'src/lib/i18n/**'], + reporter: ['text', 'lcov'], + thresholds: { lines: 80, functions: 80, branches: 80, statements: 80 }, + }, + }, +}); +``` + +- [ ] **Step 3: Add the coverage script to `package.json`** + +Insert after the existing `"test:watch"` entry: + +```json + "test:coverage": "vitest run --coverage", +``` + +- [ ] **Step 4: Write the failing test** — `src/lib/rule-status.test.ts` + +```ts +import { describe, it, expect } from 'vitest'; +import { ruleStatus } from './rule-status'; +import type { EffectiveRule } from './schema/config'; + +function makeRule(overrides: Partial = {}): EffectiveRule { + return { + id: 'r1', + enabled: true, + label: 'Rule', + domainPatterns: ['app.bauer-group.com'], + headerName: 'X-BAUERGROUP-Auth', + secretValue: '11111111-1111-1111-1111-111111111111', + syncSecret: true, + source: 'user', + ...overrides, + }; +} + +describe('ruleStatus', () => { + it('reports active when the rule is enabled and access is granted', () => { + expect(ruleStatus(makeRule(), true)).toEqual({ key: 'active', managed: false }); + }); + + it('reports needs-access when enabled but access is missing', () => { + expect(ruleStatus(makeRule(), false)).toEqual({ key: 'needs-access', managed: false }); + }); + + it('reports disabled regardless of grant state', () => { + const rule = makeRule({ enabled: false }); + expect(ruleStatus(rule, true)).toEqual({ key: 'disabled', managed: false }); + expect(ruleStatus(rule, false)).toEqual({ key: 'disabled', managed: false }); + }); + + it('flags managed rules through every status', () => { + expect(ruleStatus(makeRule({ source: 'managed' }), true).managed).toBe(true); + expect(ruleStatus(makeRule({ source: 'managed' }), false).managed).toBe(true); + expect(ruleStatus(makeRule({ source: 'managed', enabled: false }), true).managed).toBe(true); + }); +}); +``` + +- [ ] **Step 5: Run the tests** + +Run: `npm test` +Expected: PASS — 5 existing test files plus this one. `rule-status.ts` is already implemented, so these tests pass immediately; their value is locking the behaviour and proving the new jsdom + WxtVitest harness collects and runs tests correctly. + +- [ ] **Step 6: Run coverage and record the baseline** + +Run: `npm run test:coverage` +Expected: **the threshold FAILS.** Record the reported percentages — the six untested modules are the gap Task 4 closes. Do not lower the threshold to make this pass. + +- [ ] **Step 7: Commit** + +```bash +git add vitest.config.ts package.json package-lock.json src/lib/rule-status.test.ts +git commit -F - <<'EOF' +test(config): added coverage gating and fixed the test include glob + +The include glob was `src/**/*.test.ts`, which does not match .tsx. +A component test would have been collected by nothing and silently +never run — a trap that only surfaces the first time someone writes +one and believes the green result. + +* include -> src/**/*.test.{ts,tsx}, environment -> jsdom. +* WxtVitest() plugin wires WXT's aliases and auto-mocks wxt/browser + with @webext-core/fake-browser, so the modules touching browser + globals become testable without hand-written mocks. +* v8 coverage over src/lib with an 80% threshold, excluding hooks/ + and i18n/ which are exercised through components rather than unit + tests. +* Tests for rule-status.ts, the one untested module that is purely + functional — it proves the harness before the browser fakes land. + +The threshold fails at this commit by design; the following commit +covers the six browser-touching modules that make it pass. +EOF +``` + +--- + +## Task 4: Tests for the browser-touching modules + +**Files:** + +- Create: `src/lib/effective.test.ts` +- Create: `src/lib/storage/managed.test.ts` +- Create: `src/lib/storage/index.test.ts` +- Create: `src/lib/dnr/apply-rules.test.ts` +- Create: `src/lib/permissions/index.test.ts` + +**Interfaces:** + +- Consumes: `WxtVitest()` + `fakeBrowser` from Task 3. +- Produces: coverage above the 80 % threshold, which Task 8's `ci.yml` enforces. + +`fakeBrowser.reset()` in a `beforeEach` is mandatory — the fake keeps in-memory state between tests otherwise. + +- [ ] **Step 1: Write `src/lib/storage/managed.test.ts`** + +```ts +import { describe, it, expect, beforeEach } from 'vitest'; +import { fakeBrowser } from 'wxt/testing'; +import { loadManagedConfig, managedRulesToEffective, ManagedConfigSchema } from './managed'; + +const SECRET = '11111111-1111-1111-1111-111111111111'; + +describe('loadManagedConfig', () => { + beforeEach(() => fakeBrowser.reset()); + + it('returns null when no policy is provisioned', async () => { + await expect(loadManagedConfig()).resolves.toBeNull(); + }); + + it('parses a provisioned policy', async () => { + await fakeBrowser.storage.managed.set({ + masterEnabled: true, + rules: [{ label: 'Corp', domainPatterns: ['*.bauer-group.com'], secretValue: SECRET }], + }); + const config = await loadManagedConfig(); + expect(config?.masterEnabled).toBe(true); + expect(config?.rules).toHaveLength(1); + expect(config?.rules[0].headerName).toBe('X-BAUERGROUP-Auth'); + }); + + it('returns null for a policy that fails validation', async () => { + await fakeBrowser.storage.managed.set({ rules: [{ label: '', domainPatterns: [] }] }); + await expect(loadManagedConfig()).resolves.toBeNull(); + }); +}); + +describe('managedRulesToEffective', () => { + it('synthesises ids and tags every rule as managed', () => { + const managed = ManagedConfigSchema.parse({ + rules: [ + { label: 'A', domainPatterns: ['a.example.com'], secretValue: SECRET }, + { label: 'B', domainPatterns: ['b.example.com'], secretValue: SECRET, enabled: false }, + ], + }); + const effective = managedRulesToEffective(managed); + expect(effective.map((r) => r.id)).toEqual(['managed-0', 'managed-1']); + expect(effective.every((r) => r.source === 'managed')).toBe(true); + expect(effective.every((r) => r.syncSecret)).toBe(true); + expect(effective[1].enabled).toBe(false); + }); + + it('returns an empty list when the policy has no rules', () => { + expect(managedRulesToEffective(ManagedConfigSchema.parse({}))).toEqual([]); + }); +}); +``` + +- [ ] **Step 2: Write `src/lib/effective.test.ts`** + +```ts +import { describe, it, expect } from 'vitest'; +import { computeEffectiveConfig } from './effective'; +import { ManagedConfigSchema } from './storage/managed'; +import type { Config } from './schema/config'; + +const SECRET = '11111111-1111-1111-1111-111111111111'; + +function userConfig(overrides: Partial = {}): Config { + return { + schemaVersion: 1, + masterEnabled: true, + uiLocale: 'de', + rules: [ + { + id: 'u1', + enabled: true, + label: 'User rule', + domainPatterns: ['user.example.com'], + headerName: 'X-BAUERGROUP-Auth', + secretValue: SECRET, + syncSecret: true, + }, + ], + ...overrides, + }; +} + +describe('computeEffectiveConfig', () => { + it('tags user rules as user-sourced when no policy exists', () => { + const effective = computeEffectiveConfig(userConfig(), null); + expect(effective.rules).toHaveLength(1); + expect(effective.rules[0].source).toBe('user'); + expect(effective.masterEnabled).toBe(true); + }); + + it('places managed rules before user rules', () => { + const managed = ManagedConfigSchema.parse({ + rules: [{ label: 'Corp', domainPatterns: ['corp.example.com'], secretValue: SECRET }], + }); + const effective = computeEffectiveConfig(userConfig(), managed); + expect(effective.rules.map((r) => r.source)).toEqual(['managed', 'user']); + }); + + it('lets a managed masterEnabled override the user switch', () => { + const managed = ManagedConfigSchema.parse({ masterEnabled: false, rules: [] }); + expect(computeEffectiveConfig(userConfig(), managed).masterEnabled).toBe(false); + }); + + it('falls back to the user switch when the policy omits masterEnabled', () => { + const managed = ManagedConfigSchema.parse({ rules: [] }); + const user = userConfig({ masterEnabled: false }); + expect(computeEffectiveConfig(user, managed).masterEnabled).toBe(false); + }); +}); +``` + +- [ ] **Step 3: Write `src/lib/storage/index.test.ts`** + +```ts +import { describe, it, expect, beforeEach } from 'vitest'; +import { fakeBrowser } from 'wxt/testing'; +import { loadConfig, saveConfig, onConfigChanged } from './index'; +import type { Config } from '../schema/config'; + +const SECRET = '11111111-1111-1111-1111-111111111111'; +const ZERO_GUID = '00000000-0000-0000-0000-000000000000'; + +function makeConfig(syncSecret: boolean): Config { + return { + schemaVersion: 1, + masterEnabled: true, + uiLocale: 'de', + rules: [ + { + id: 'r1', + enabled: true, + label: 'Rule', + domainPatterns: ['app.bauer-group.com'], + headerName: 'X-BAUERGROUP-Auth', + secretValue: SECRET, + syncSecret, + }, + ], + }; +} + +describe('loadConfig / saveConfig', () => { + beforeEach(() => fakeBrowser.reset()); + + it('returns defaults when storage is empty', async () => { + const config = await loadConfig(); + expect(config.rules).toEqual([]); + expect(config.masterEnabled).toBe(true); + }); + + it('round-trips a synced-secret rule through sync storage', async () => { + await saveConfig(makeConfig(true)); + const loaded = await loadConfig(); + expect(loaded.rules[0].secretValue).toBe(SECRET); + expect(loaded.rules[0].syncSecret).toBe(true); + }); + + it('keeps a device-local secret out of sync storage', async () => { + await saveConfig(makeConfig(false)); + + const sync = (await fakeBrowser.storage.sync.get(null)) as Record; + expect(JSON.stringify(sync)).not.toContain(SECRET); + expect(JSON.stringify(sync)).toContain(ZERO_GUID); + + const local = (await fakeBrowser.storage.local.get(null)) as Record; + expect(local['secret:r1']).toBe(SECRET); + + const loaded = await loadConfig(); + expect(loaded.rules[0].secretValue).toBe(SECRET); + }); + + it('substitutes the null GUID when the device-local secret is missing', async () => { + await saveConfig(makeConfig(false)); + await fakeBrowser.storage.local.remove('secret:r1'); + const loaded = await loadConfig(); + expect(loaded.rules[0].secretValue).toBe(ZERO_GUID); + }); + + it('drops the device-local copy when a rule switches back to synced', async () => { + await saveConfig(makeConfig(false)); + await saveConfig(makeConfig(true)); + const local = (await fakeBrowser.storage.local.get(null)) as Record; + expect(local['secret:r1']).toBeUndefined(); + }); +}); + +describe('onConfigChanged', () => { + beforeEach(() => fakeBrowser.reset()); + + it('fires on sync changes and stops after unsubscribe', async () => { + let calls = 0; + const unsubscribe = onConfigChanged(() => { + calls += 1; + }); + + await fakeBrowser.storage.sync.set({ anything: 1 }); + expect(calls).toBeGreaterThan(0); + + const afterSubscribe = calls; + unsubscribe(); + await fakeBrowser.storage.sync.set({ anything: 2 }); + expect(calls).toBe(afterSubscribe); + }); +}); +``` + +- [ ] **Step 4: Write `src/lib/dnr/apply-rules.test.ts`** + +```ts +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { fakeBrowser } from 'wxt/testing'; +import { browser } from 'wxt/browser'; +import { applyRules, DnrLimitError, MAX_UNSAFE_DYNAMIC_RULES } from './apply-rules'; +import type { ModifyHeaderRule } from './build-rules'; + +function makeRule(id: number): ModifyHeaderRule { + return { + id, + priority: 1, + action: { + type: 'modifyHeaders', + requestHeaders: [{ header: 'X-BAUERGROUP-Auth', operation: 'set', value: 'v' }], + }, + condition: { requestDomains: ['app.bauer-group.com'] }, + } as unknown as ModifyHeaderRule; +} + +describe('applyRules', () => { + beforeEach(() => fakeBrowser.reset()); + + it('removes every live rule id before adding the new set', async () => { + const getDynamicRules = vi + .spyOn(browser.declarativeNetRequest, 'getDynamicRules') + .mockResolvedValue([{ id: 7 }, { id: 9 }] as never); + const updateDynamicRules = vi + .spyOn(browser.declarativeNetRequest, 'updateDynamicRules') + .mockResolvedValue(undefined as never); + + const rules = [makeRule(1)]; + await applyRules(rules); + + expect(getDynamicRules).toHaveBeenCalledOnce(); + expect(updateDynamicRules).toHaveBeenCalledWith({ + removeRuleIds: [7, 9], + addRules: rules, + }); + }); + + it('still clears live rules when the new set is empty', async () => { + vi.spyOn(browser.declarativeNetRequest, 'getDynamicRules').mockResolvedValue([ + { id: 3 }, + ] as never); + const updateDynamicRules = vi + .spyOn(browser.declarativeNetRequest, 'updateDynamicRules') + .mockResolvedValue(undefined as never); + + await applyRules([]); + + expect(updateDynamicRules).toHaveBeenCalledWith({ removeRuleIds: [3], addRules: [] }); + }); + + it('throws DnrLimitError above the binding ceiling and touches nothing', async () => { + const updateDynamicRules = vi + .spyOn(browser.declarativeNetRequest, 'updateDynamicRules') + .mockResolvedValue(undefined as never); + + const tooMany = Array.from({ length: MAX_UNSAFE_DYNAMIC_RULES + 1 }, (_, i) => makeRule(i + 1)); + + await expect(applyRules(tooMany)).rejects.toBeInstanceOf(DnrLimitError); + await expect(applyRules(tooMany)).rejects.toThrow(String(MAX_UNSAFE_DYNAMIC_RULES)); + expect(updateDynamicRules).not.toHaveBeenCalled(); + }); +}); +``` + +- [ ] **Step 5: Write `src/lib/permissions/index.test.ts`** + +```ts +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { fakeBrowser } from 'wxt/testing'; +import { browser } from 'wxt/browser'; +import { + hasOriginsForPatterns, + requestOriginsForPatterns, + removeOriginsForPatterns, +} from './index'; +import { patternsToOrigins } from './patterns'; + +describe('hasOriginsForPatterns', () => { + beforeEach(() => fakeBrowser.reset()); + + it('short-circuits to true when no pattern yields an origin', async () => { + const contains = vi.spyOn(browser.permissions, 'contains'); + await expect(hasOriginsForPatterns([])).resolves.toBe(true); + expect(contains).not.toHaveBeenCalled(); + }); + + it('delegates to permissions.contains with the derived origins', async () => { + const contains = vi.spyOn(browser.permissions, 'contains').mockResolvedValue(true as never); + await expect(hasOriginsForPatterns(['app.bauer-group.com'])).resolves.toBe(true); + expect(contains).toHaveBeenCalledWith({ origins: patternsToOrigins(['app.bauer-group.com']) }); + }); + + it('reports false when the binding rejects', async () => { + vi.spyOn(browser.permissions, 'contains').mockRejectedValue(new Error('nope') as never); + await expect(hasOriginsForPatterns(['app.bauer-group.com'])).resolves.toBe(false); + }); +}); + +describe('requestOriginsForPatterns', () => { + beforeEach(() => fakeBrowser.reset()); + + it('reports false when the user denies the prompt', async () => { + vi.spyOn(browser.permissions, 'request').mockResolvedValue(false as never); + await expect(requestOriginsForPatterns(['app.bauer-group.com'])).resolves.toBe(false); + }); + + it('reports false when the binding rejects', async () => { + vi.spyOn(browser.permissions, 'request').mockRejectedValue(new Error('nope') as never); + await expect(requestOriginsForPatterns(['app.bauer-group.com'])).resolves.toBe(false); + }); +}); + +describe('removeOriginsForPatterns', () => { + beforeEach(() => fakeBrowser.reset()); + + it('keeps origins that are still used by another rule', async () => { + const remove = vi.spyOn(browser.permissions, 'remove').mockResolvedValue(true as never); + await expect( + removeOriginsForPatterns(['app.bauer-group.com'], ['app.bauer-group.com']), + ).resolves.toBe(true); + expect(remove).not.toHaveBeenCalled(); + }); + + it('revokes origins that no remaining rule needs', async () => { + const remove = vi.spyOn(browser.permissions, 'remove').mockResolvedValue(true as never); + await expect(removeOriginsForPatterns(['gone.example.com'], [])).resolves.toBe(true); + expect(remove).toHaveBeenCalledWith({ origins: patternsToOrigins(['gone.example.com']) }); + }); +}); +``` + +- [ ] **Step 6: Run the tests** + +Run: `npm test` +Expected: PASS. + +If `applyRules` assertions fail on the exact `updateDynamicRules` argument shape, read `src/lib/dnr/build-rules.ts` and align `makeRule()` with the real `ModifyHeaderRule` type rather than loosening the assertion. If a `permissions` test fails because `patternsToOrigins` returns something unexpected, read `src/lib/permissions/patterns.ts` and its existing `patterns.test.ts` — the derived origins are asserted through the real function precisely so this test cannot drift from it. + +- [ ] **Step 7: Run coverage — the threshold must now pass** + +Run: `npm run test:coverage` +Expected: PASS, all four metrics ≥ 80 % over `src/lib/**`. + +If a metric is short, add cases to the module that is lagging. Do **not** lower the threshold and do **not** widen the `exclude` list. + +- [ ] **Step 8: Commit** + +```bash +git add src/lib/effective.test.ts src/lib/storage/managed.test.ts src/lib/storage/index.test.ts src/lib/dnr/apply-rules.test.ts src/lib/permissions/index.test.ts +git commit -F - <<'EOF' +test(lib): covered the six modules touching browser APIs + +Every module that imported the browser global was untested, which is +also the set where a regression is least likely to be caught by hand: +storage sharding, managed-policy parsing, DNR rule replacement and +host-permission handling all fail silently rather than loudly. + +* storage/index: the device-local secret path is asserted from both + ends — the synced copy must contain the null GUID and never the + real secret, and switching a rule back to synced must drop the + local copy. +* dnr/apply-rules: the live rule list is the removal source of + truth, so the test asserts removeRuleIds comes from + getDynamicRules and not from the incoming set. The limit case + asserts updateDynamicRules is never reached. +* permissions: all three entry points swallow binding rejections by + design; tests pin that they return false rather than throwing, + and that removeOriginsForPatterns spares origins still in use. +* managed: an invalid policy must yield null, not a partial config. + +Origins are asserted through the real patternsToOrigins rather than +literals so these tests cannot drift from the pattern logic. + +Coverage over src/lib now clears the 80% threshold introduced in the +previous commit. +EOF +``` + +--- + +## Task 5: Code fixes + +**Files:** + +- Modify: `scripts/sign-crx.mjs:4-5,30-34,42` +- Modify: `docs/deployment/README.md` +- Modify: `docs/deployment/force-install-windows.reg` +- Modify: `docs/deployment/macos-chrome-forcelist.mobileconfig` + +**Interfaces:** + +- Produces: `sign-crx.mjs` exits non-zero under CI when the signing key is absent — the release pipeline in Task 8 depends on this to avoid green-but-empty releases. + +- [ ] **Step 1: Make `sign-crx.mjs` fail loudly under CI** + +Replace the header comment (lines 4–5): + +```js +// Key source (in order): EXTENSION_PRIVATE_KEY env (CI secret) → .keys/key.pem (local). +// Locally, a missing key SKIPS signing so `npm run build` works without secrets. +// Under CI it is a hard failure: a silently unsigned release ships a GitHub +// Release with no .crx and no updates.xml, and every force-installed client +// keeps the old version with no error anywhere. +``` + +Replace the missing-key branch (lines 30–34): + +```js +const keyPath = resolveKeyPath(); +if (!keyPath) { + const message = '[sign:crx] No signing key (EXTENSION_PRIVATE_KEY / .keys/key.pem).'; + if (process.env.CI) { + console.error(`${message} Refusing to publish an unsigned release.`); + process.exit(1); + } + console.warn(`${message} Skipping (local build).`); + process.exit(0); +} +``` + +- [ ] **Step 2: Fix the stale repo fallback (line 42)** + +```js +const repo = process.env.GITHUB_REPOSITORY ?? 'bauer-group/IP-Chrome-HeaderAuth'; +``` + +The old default was `bauer-group/BAUERGROUP.Extension.Chrome.HeaderAuth`, which is neither repo's name — it was the local directory name. + +- [ ] **Step 3: Verify both branches** + +```bash +CI=1 npm run sign:crx; echo "CI exit: $?" +``` + +Expected: exit `1` with the "Refusing to publish" message — **unless** `.keys/key.pem` exists locally, in which case it signs. To test the failure path with the key present: + +```bash +CI=1 EXTENSION_PRIVATE_KEY= node -e "process.env.CI='1';" ; mv .keys/key.pem .keys/key.pem.bak 2>/dev/null; CI=1 npm run sign:crx; echo "CI exit: $?"; mv .keys/key.pem.bak .keys/key.pem 2>/dev/null +``` + +Expected: exit `1`. + +Then the local path: + +```bash +mv .keys/key.pem .keys/key.pem.bak 2>/dev/null; npm run sign:crx; echo "local exit: $?"; mv .keys/key.pem.bak .keys/key.pem 2>/dev/null +``` + +Expected: exit `0` with "Skipping (local build)". + +- [ ] **Step 4: Replace the `/` placeholders** + +Run: `git grep -n "/" -- docs/` + +Replace every occurrence with `bauer-group/IP-Chrome-HeaderAuth` in: + +- `docs/deployment/README.md` +- `docs/deployment/force-install-windows.reg` +- `docs/deployment/macos-chrome-forcelist.mobileconfig` + +Then verify none remain: + +Run: `git grep -n "\|" -- docs/ || echo "clean"` +Expected: `clean` + +- [ ] **Step 5: Commit** + +```bash +git add scripts/sign-crx.mjs docs/deployment/ +git commit -F - <<'EOF' +fix(release): failed loudly on a missing signing key under CI + +sign-crx.mjs exited 0 when no key was found, so a release running +without the EXTENSION_PRIVATE_KEY secret produced a green pipeline +and a GitHub Release with no .crx and no updates.xml. Force-installed +clients would keep the old version and report nothing — the worst +shape a failure can take. + +* Under CI a missing key is now exit 1. The soft skip stays for + local builds, where working without secrets is the point. +* Repo fallback for the updates.xml codebase URL corrected from + bauer-group/BAUERGROUP.Extension.Chrome.HeaderAuth (the local + directory name, never a repo) to bauer-group/IP-Chrome-HeaderAuth. +* Replaced the / placeholders across the deployment + docs, the .reg and the .mobileconfig — an admin copying those + would have deployed a policy pointing at nothing. +EOF +``` + +--- + +## Task 6: Repo hygiene files + +**Files:** + +- Create: `.github/CODEOWNERS` +- Create: `.github/dependabot.yml` +- Create: `.github/config/release/semantic-release.json` +- Create: `.github/config/commitlint.config.js` + +**Interfaces:** + +- Produces: `.github/config/release/semantic-release.json` — read by `modules-semantic-release.yml` in Task 8. +- Produces: `.github/config/commitlint.config.js` — read by `modules-pr-validation.yml` in Task 8. + +Deliberately not created: `SECURITY.md`, `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, `PULL_REQUEST_TEMPLATE.md`, `ISSUE_TEMPLATE/`. These are inherited from `bauer-group/.github`. + +- [ ] **Step 1: Create `.github/CODEOWNERS`** + +``` +# Every change is reviewed by the core team. +* @bauer-group/core +``` + +- [ ] **Step 2: Create `.github/dependabot.yml`** + +```yaml +# ============================================================================= +# Dependabot Configuration +# ============================================================================= +# Watches: +# 1. GitHub Actions versions +# 2. npm dependencies (runtime + build tooling) +# +# COMMIT PREFIXES DECIDE WHETHER AN UPDATE SHIPS +# release.yml only publishes assets when semantic-release actually cuts a +# release (`if: needs.release.outputs.release-created == 'true'`). A +# `chore(...)` update therefore lands on main but never reaches a published +# .crx — it rides along with whatever releasing commit happens to come next, +# which for a security patch can be weeks. +# +# So npm uses `fix(deps)` and cuts its own patch: in a bundled extension +# nearly everything except pure linting tooling ends up inside the shipped +# artifact, including the build chain (wxt, vite, tailwind). GitHub Actions +# stays on `chore(ci)` — it never enters the bundle. +# +# Grouping bounds the cost: one PR per ecosystem per week, so at most one +# patch release a week comes from dependency traffic. +# ============================================================================= + +version: 2 +updates: + # --------------------------------------------------------------------------- + # GitHub Actions + # --------------------------------------------------------------------------- + - package-ecosystem: 'github-actions' + directory: '/' + schedule: + interval: 'weekly' + day: 'sunday' + time: '06:30' + timezone: 'Etc/UTC' + labels: + - 'dependencies' + - 'github-actions' + - 'dependabot' + commit-message: + prefix: 'chore(ci)' + groups: + github-actions: + patterns: + - '*' + + # --------------------------------------------------------------------------- + # npm + # --------------------------------------------------------------------------- + - package-ecosystem: 'npm' + directory: '/' + schedule: + interval: 'weekly' + day: 'sunday' + time: '06:30' + timezone: 'Etc/UTC' + labels: + - 'dependencies' + - 'npm' + - 'dependabot' + commit-message: + prefix: 'fix(deps)' + groups: + npm: + patterns: + - '*' + ignore: + # TypeScript 7 breaks the lint step: typescript-eslint@8 declares peer + # `typescript: ">=4.8.4 <6.1.0"`, TS 7 shipped without a stable + # programmatic API, and typescript-eslint closed TS7 support as + # "not planned". Revisit only when typescript-eslint ships TS7 support. + - dependency-name: 'typescript' + versions: ['>=7'] +``` + +- [ ] **Step 3: Create `.github/config/release/semantic-release.json`** + +```json +{ + "branches": ["main"], + "plugins": [ + "@semantic-release/commit-analyzer", + "@semantic-release/release-notes-generator", + [ + "@semantic-release/changelog", + { + "changelogFile": "CHANGELOG.md" + } + ], + [ + "@semantic-release/npm", + { + "npmPublish": false + } + ], + [ + "@semantic-release/git", + { + "assets": ["CHANGELOG.md", "package.json", "package-lock.json"], + "message": "chore(release): ${nextRelease.version}\n\n${nextRelease.notes}" + } + ], + "@semantic-release/github" + ] +} +``` + +`npmPublish: false` with `@semantic-release/npm` still present is deliberate: it bumps `package.json` without publishing to a registry. WXT derives the extension's manifest version from `package.json`, so that bump _is_ the version-stamping mechanism — no `@semantic-release/exec` needed. + +- [ ] **Step 4: Create `.github/config/commitlint.config.js`** + +```js +/** + * Conventional Commits enforcement, read by + * bauer-group/automation-templates/.github/workflows/modules-pr-validation.yml. + * + * Kept in sync with the root commitlint.config.js, which serves the local + * simple-git-hooks commit-msg hook. Two files because the two consumers look in + * different places; without this one the CI commitlint step degrades silently + * (the module runs it with continue-on-error). + */ +export default { + extends: ['@commitlint/config-conventional'], + rules: { + 'subject-case': [0], + 'body-max-line-length': [0], + }, +}; +``` + +- [ ] **Step 5: Validate the YAML and JSON parse** + +```bash +node -e "JSON.parse(require('fs').readFileSync('.github/config/release/semantic-release.json','utf8')); console.log('semantic-release.json OK')" +node --input-type=module -e "import('./.github/config/commitlint.config.js').then(m=>console.log('commitlint OK', Object.keys(m.default)))" +npx --yes js-yaml .github/dependabot.yml > /dev/null && echo "dependabot.yml OK" +``` + +Expected: three OK lines. + +- [ ] **Step 6: Commit** + +```bash +git add .github/CODEOWNERS .github/dependabot.yml .github/config/ +git commit -F - <<'EOF' +ci(config): added CODEOWNERS, Dependabot and release configuration + +Brings the repo onto the org conventions so the reusable workflows +find what they expect. + +* dependabot.yml: npm uses fix(deps), GitHub Actions uses chore(ci). + The prefix is load-bearing — chore(...) never cuts a release, so a + runtime dependency patch carrying it would sit on main until an + unrelated releasing commit came along. In a bundled extension the + build chain ships inside the artifact too, so npm gets fix(deps). + Grouping bounds this to one PR per ecosystem per week. +* dependabot.yml ignores typescript >=7 — typescript-eslint@8 + declares peer <6.1.0 and closed TS7 support as not planned, so the + PR would break the lint step. Without the ignore it arrives every + week looking harmless. +* config/release/semantic-release.json: org convention keeps this + out of the repo root; the composite action merges it at runtime. + npmPublish:false bumps package.json without publishing — and since + WXT derives the manifest version from package.json, that bump is + the version-stamping mechanism. +* config/commitlint.config.js mirrors the root config, which serves + the local git hook. modules-pr-validation.yml reads this path and + degrades silently without it. + +SECURITY.md, CONTRIBUTING.md, CODE_OF_CONDUCT.md and the issue/PR +templates are deliberately absent — they are inherited from +bauer-group/.github. +EOF +``` + +--- + +## Task 7: CI gate and PR validation workflows + +**Files:** + +- Create: `.github/workflows/ci.yml` +- Create: `.github/workflows/pr-validation.yml` + +**Interfaces:** + +- Produces: `ci.yml` with a `workflow_call` trigger — `release.yml` (Task 8) calls it as `uses: ./.github/workflows/ci.yml`. + +- [ ] **Step 1: Create `.github/workflows/ci.yml`** + +```yaml +# ============================================================================= +# CI Gate +# ============================================================================= +# WHAT: Lint, format, typecheck, test with coverage, audit, and build all three +# browser targets. +# WHY: Until now the quality gates lived inside release.yml and therefore ran +# AFTER code had landed on main. This runs them before a merge is +# possible, and release.yml reuses this exact file via workflow_call so +# the pre-merge gate and the release gate cannot drift apart. +# WHEN: Every PR, every push to main, on demand, and as the release gate. +# ============================================================================= + +name: 🔍 CI + +on: + pull_request: + branches: [main] + types: [opened, synchronize, reopened, ready_for_review] + push: + branches: [main] + workflow_dispatch: + workflow_call: + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + gate: + name: 🧪 Quality Gate + # nodejs-build.yml calls modules-code-quality.yml, which declares + # `pull-requests: write`. GitHub validates a called workflow's permissions + # when the run is CREATED — before any job `if:` — so this is required even + # though Sonar is off. Omitting the block does not help: a partial block + # sets every unnamed scope to none, and the repo default is already + # pull-requests: none. Without it: startup_failure, no job, no log. + permissions: + contents: read + pull-requests: write + uses: bauer-group/automation-templates/.github/workflows/nodejs-build.yml@main + with: + node-version-file: '.nvmrc' + package-manager: 'npm' + frozen-lockfile: true + + run-lint: true + lint-command: 'npm run lint' + run-format-check: true + format-command: 'npm run format:check' + run-typecheck: true + typecheck-command: 'npm run typecheck' + + run-tests: true + test-command: 'npm run test:coverage' + test-coverage: true + + run-audit: true + audit-level: 'high' + + # WXT builds one browser per invocation; three sequential builds are a few + # seconds each and keep this on the reusable workflow rather than forcing + # a hand-rolled matrix. + build-command: 'npm run build && npm run build:firefox && npm run build:edge' + + # The reusable's default artifact glob is dist|build|lib|coverage. WXT + # emits .output/, so without this nothing would be uploaded. + upload-artifacts: true + artifact-name: 'extension-builds' + artifact-path: | + .output/chrome-mv3 + .output/firefox-mv2 + .output/edge-mv3 + artifact-retention-days: 14 + # Node 24 is the floor (.nvmrc); 26 becomes LTS on 2026-10-28 and is + # tested now so the promotion is a non-event. + enable-matrix: true + matrix-node: '["24", "26"]' + secrets: inherit +``` + +- [ ] **Step 2: Create `.github/workflows/pr-validation.yml`** + +```yaml +# ============================================================================= +# Pull Request Gates +# ============================================================================= +# WHAT: Conventional-commit validation, secret scanning, license compliance, +# and dependency review. +# WHY: These only make sense with a PR context (they diff head against base), +# which is why they live here rather than in ci.yml — ci.yml also runs on +# push and as the release gate, where there is no PR to diff against. +# WHEN: Every pull request targeting main. +# ============================================================================= + +name: 🔍 PR Gates + +on: + pull_request: + branches: [main] + types: [opened, synchronize, reopened, ready_for_review] + +concurrency: + group: pr-gates-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + validate: + name: ✅ Commits, Secrets & Licenses + permissions: + contents: read + pull-requests: write + security-events: write + checks: write + uses: bauer-group/automation-templates/.github/workflows/modules-pr-validation.yml@main + with: + enable-commit-lint: true + enable-security-scan: true + enable-license-check: true + fail-on-security-issues: true + # Reported, not blocking: this repo ships MIT and pulls a broad React / + # Radix tree whose transitive licence metadata is noisy. A hard fail here + # would block PRs on someone else's package.json typo. + fail-on-license-issues: false + secrets: inherit + + dependency-review: + name: 🛡️ Dependency Review + permissions: + contents: read + pull-requests: write + uses: bauer-group/automation-templates/.github/workflows/modules-dependency-review.yml@main + with: + fail-on-severity: 'high' + comment-summary-in-pr: 'on-failure' + # Runtime scope only. Dev-only advisories do not reach the shipped .crx, + # and blocking a PR on one trains people to bypass the gate. + fail-on-scopes: 'runtime' +``` + +- [ ] **Step 3: Validate the YAML parses** + +```bash +for f in .github/workflows/ci.yml .github/workflows/pr-validation.yml; do + npx --yes js-yaml "$f" > /dev/null && echo "$f OK" +done +``` + +Expected: two OK lines. + +- [ ] **Step 4: Verify against the reusable workflows' real inputs** + +Every `with:` key above must exist in the called workflow. Confirm: + +```bash +TPL="C:/Projects/Production+Development/Automation-Templates/.github/workflows" +grep -E "^ [a-z-]+:" "$TPL/nodejs-build.yml" | head -60 +grep -E "^ [a-z-]+:" "$TPL/modules-pr-validation.yml" +grep -E "^ [a-z-]+:" "$TPL/modules-dependency-review.yml" +``` + +Expected: every key used above appears in the corresponding list. A typo here surfaces only at run time as `Invalid input`. + +- [ ] **Step 5: Commit** + +```bash +git add .github/workflows/ci.yml .github/workflows/pr-validation.yml +git commit -F - <<'EOF' +ci(gate): added a pre-merge CI gate and PR validation + +Quality gates previously ran inside release.yml, which triggers on +push to main — so they verified code that had already landed. There +was no pre-merge verification at all. + +* ci.yml runs lint, format, typecheck, tests with coverage, a high + audit and all three browser builds through nodejs-build.yml. It + declares workflow_call so release.yml reuses this exact file + rather than a copied `with:` block — the pre-merge gate and the + release gate are now provably the same thing. +* Node matrix [24, 26]: 24 is the floor from .nvmrc, 26 becomes LTS + on 2026-10-28 and is tested now so the promotion is a non-event. +* artifact-path is set explicitly. The reusable defaults to + dist|build|lib|coverage and WXT emits .output/, so the default + would have uploaded nothing while still reporting success. +* The gate job declares pull-requests: write. nodejs-build.yml calls + modules-code-quality.yml, whose permissions GitHub validates at + run creation — before any job if: — so it is required even with + Sonar off, and a missing block yields startup_failure with no log. +* pr-validation.yml keeps the PR-only gates separate: they diff head + against base, which ci.yml cannot rely on since it also runs on + push and as the release gate. +EOF +``` + +--- + +## Task 8: Release, CodeQL, AI summary, Dependabot automerge + +**Files:** + +- Modify: `.github/workflows/release.yml` (full replacement) +- Create: `.github/workflows/codeql.yml` +- Create: `.github/workflows/ai-issue-summary.yml` +- Create: `.github/workflows/dependabot-maintenance.yml` + +**Interfaces:** + +- Consumes: `ci.yml` (Task 7) via `uses: ./.github/workflows/ci.yml`; `.github/config/release/semantic-release.json` (Task 6); the fail-loud `sign:crx` (Task 5). + +- [ ] **Step 1: Replace `.github/workflows/release.yml`** + +```yaml +# ============================================================================= +# Release +# ============================================================================= +# WHAT: Gate → semantic release → publish signed assets → (optional) Chrome +# Web Store. +# WHY: The previous version called `npm version patch` unconditionally, so a +# feat: never produced a MINOR and a BREAKING CHANGE: never produced a +# MAJOR — the repo enforced Conventional Commits through commitlint and +# then discarded the information they carry. Its publish job also +# re-checked-out the pre-bump commit, so it could package a version +# other than the one released. +# WHEN: Push to main, or manual dispatch. +# +# NO concurrency group: semantic-release hard-resets to origin, and cancelling +# it mid-flight can leave a tag pushed with no release attached. +# ============================================================================= + +name: 🚀 Release + +on: + push: + branches: [main] + paths-ignore: + - '*.md' + - 'docs/**' + workflow_dispatch: + inputs: + force-release: + description: 'force create release' + type: boolean + default: false + +permissions: + contents: read + +jobs: + gate: + name: 🔍 Gate + # Same file the PRs run — see ci.yml. The pull-requests: write grant is the + # permission ceiling required by nodejs-build.yml further down the chain. + permissions: + contents: read + pull-requests: write + uses: ./.github/workflows/ci.yml + secrets: inherit + + release: + name: 📦 Semantic Release + needs: [gate] + if: needs.gate.result == 'success' + permissions: + contents: write + issues: write + pull-requests: write + uses: bauer-group/automation-templates/.github/workflows/modules-semantic-release.yml@main + with: + target-branch: 'main' + node-version: '24' + force-release: ${{ inputs.force-release || false }} + secrets: inherit + + publish-assets: + name: 📤 Publish Release Assets + needs: [release] + if: needs.release.outputs.release-created == 'true' + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: write + steps: + - name: 📥 Checkout release tag + uses: actions/checkout@v7 + with: + # The released tag, NOT the pushed commit. semantic-release commits + # the version bump itself, so checking out github.sha here would + # build the pre-bump tree and ship a .crx whose manifest version does + # not match the release it is attached to. + ref: ${{ needs.release.outputs.tag-name }} + + - name: 📦 Setup Node.js + uses: actions/setup-node@v6 + with: + node-version-file: '.nvmrc' + cache: npm + + - name: 📚 Install dependencies + run: npm ci + + - name: 🏗️ Build & zip (Chrome, Firefox, Edge) + run: | + npm run build + npm run zip + npm run zip:firefox + npm run zip:edge + + - name: 🔏 Sign CRX + generate updates.xml + env: + EXTENSION_PRIVATE_KEY: ${{ secrets.EXTENSION_PRIVATE_KEY }} + GITHUB_REPOSITORY: ${{ github.repository }} + # Hard-fails when the secret is absent (CI=true is set by the runner) — + # a green release with no .crx leaves every force-installed client + # silently pinned to the old version. + run: npm run sign:crx + + - name: 📎 Attach assets to the release + uses: softprops/action-gh-release@v3 + with: + tag_name: ${{ needs.release.outputs.tag-name }} + files: | + .output/*-chrome.zip + .output/*-firefox.zip + .output/*-edge.zip + .output/header-authenticator.crx + .output/updates.xml + + publish-web-store: + name: 📤 Publish to Chrome Web Store + needs: [release, publish-assets] + # Wired but off. Set the repository variable PUBLISH_WEB_STORE=true and add + # the CWS_* secrets once a listing exists. v1 was never published from this + # repo, and v2 pins a new extension ID, so there is nothing to update yet. + if: needs.release.outputs.release-created == 'true' && vars.PUBLISH_WEB_STORE == 'true' + # Best-effort: a Web Store hiccup stays a visible red job but must not + # invalidate a release whose GitHub assets already published correctly. + continue-on-error: true + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + steps: + - name: 📥 Checkout release tag + uses: actions/checkout@v7 + with: + ref: ${{ needs.release.outputs.tag-name }} + + - name: 📦 Setup Node.js + uses: actions/setup-node@v6 + with: + node-version-file: '.nvmrc' + cache: npm + + - name: 📚 Install dependencies + run: npm ci + + - name: 🏗️ Build & zip (Chrome) + run: | + npm run build + npm run zip + + - name: 📤 Upload to Chrome Web Store + env: + CWS_CLIENT_ID: ${{ secrets.CWS_CLIENT_ID }} + CWS_CLIENT_SECRET: ${{ secrets.CWS_CLIENT_SECRET }} + CWS_REFRESH_TOKEN: ${{ secrets.CWS_REFRESH_TOKEN }} + CWS_EXTENSION_ID: ${{ secrets.CWS_EXTENSION_ID }} + run: | + ZIP="$(ls .output/*-chrome.zip | head -1)" + npx --yes chrome-webstore-upload-cli@3 upload \ + --source "$ZIP" \ + --extension-id "$CWS_EXTENSION_ID" \ + --client-id "$CWS_CLIENT_ID" \ + --client-secret "$CWS_CLIENT_SECRET" \ + --refresh-token "$CWS_REFRESH_TOKEN" +``` + +- [ ] **Step 2: Create `.github/workflows/codeql.yml`** + +```yaml +# ============================================================================= +# CodeQL Analysis +# ============================================================================= +# WHAT: Static application security testing over the TypeScript/React source. +# WHY: This extension injects a secret header into outbound requests and holds +# GUID secrets in extension storage — taint-tracking findings here are +# directly security-relevant, not box-ticking. +# WHEN: Push to main, every PR, and weekly (advisories land after the code +# stops changing, so a schedule catches what push triggers cannot). +# ============================================================================= + +name: 🛡️ CodeQL + +on: + push: + branches: [main] + pull_request: + branches: [main] + schedule: + # Mondays 04:17 UTC — off the hour to avoid the scheduler's peak queue. + - cron: '17 4 * * 1' + +permissions: + contents: read + +jobs: + analyze: + name: 🔎 Analyze + permissions: + contents: read + security-events: write + actions: read + packages: read + uses: bauer-group/automation-templates/.github/workflows/modules-codeql.yml@main + with: + languages: 'javascript-typescript' + queries: 'security-extended' + # Findings surface in the Security tab rather than blocking merges. A + # SAST false positive blocking every PR is how teams learn to bypass the + # gate entirely. + fail-on-findings: false +``` + +- [ ] **Step 3: Create `.github/workflows/ai-issue-summary.yml`** + +```yaml +name: 🤖 Issue AI Summary + +on: + issues: + types: [opened] + + pull_request_target: + types: [opened] + +permissions: + issues: write + pull-requests: write + contents: read + models: read + +jobs: + summarize-new-issue: + name: 🧠 Generate AI Summary + if: github.event_name == 'issues' || github.event_name == 'pull_request_target' + uses: bauer-group/automation-templates/.github/workflows/modules-ai-issue-summary.yml@main + with: + summary-type: 'technical' + add-labels: true + add-priority: true + translate: '' + comment-template: | + ## AI Analysis + + {summary} + + --- + *This summary was automatically generated by AI to help with triage and may not be 100% accurate.* + secrets: inherit +``` + +- [ ] **Step 4: Create `.github/workflows/dependabot-maintenance.yml`** + +```yaml +# ============================================================================= +# Dependabot Maintenance +# ============================================================================= +# WHAT: Approves Dependabot PRs and enables GitHub's native auto-merge. +# WHY: Auto-merge waits for the required status checks, so the CI gate still +# decides — this only removes the human click from an update that has +# already proven itself green. +# WHEN: Every Dependabot pull request. +# +# REQUIRES: repository setting `allow_auto_merge = true` and branch protection +# with required checks on main. Without required checks, auto-merge merges +# immediately and this becomes a rubber stamp. +# ============================================================================= + +name: 🤖 Dependabot Maintenance + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + +permissions: + contents: read + +jobs: + automerge: + name: 🔀 Auto-merge Dependabot PRs + if: github.actor == 'dependabot[bot]' + permissions: + contents: write + pull-requests: write + uses: bauer-group/automation-templates/.github/workflows/docker-maintenance-dependabot.yml@main + secrets: inherit +``` + +- [ ] **Step 5: Validate all four parse** + +```bash +for f in .github/workflows/*.yml; do + npx --yes js-yaml "$f" > /dev/null && echo "$f OK" +done +``` + +Expected: six OK lines (ci, pr-validation, release, codeql, ai-issue-summary, dependabot-maintenance). + +- [ ] **Step 6: Verify inputs against the reusables** + +```bash +TPL="C:/Projects/Production+Development/Automation-Templates/.github/workflows" +grep -E "^ [a-z-]+:" "$TPL/modules-semantic-release.yml" +grep -E "^ [a-z-]+:" "$TPL/modules-codeql.yml" +grep -nE "outputs:|release-created|tag-name|version:" "$TPL/modules-semantic-release.yml" | head -20 +``` + +Expected: `target-branch`, `node-version`, `force-release` exist; outputs `release-created`, `version`, `tag-name` exist. Confirm `docker-maintenance-dependabot.yml` has an `on: workflow_call` block — if it does **not**, it is a template to copy rather than call, and this workflow must inline its jobs instead. + +- [ ] **Step 7: Commit** + +```bash +git add .github/workflows/ +git commit -F - <<'EOF' +ci(release): replaced hand-rolled versioning with semantic-release + +The old pipeline ran `npm version patch` unconditionally. commitlint +enforced Conventional Commits on the way in and the release step then +threw that information away: a feat: never produced a MINOR, a +BREAKING CHANGE: never produced a MAJOR. Every release since the repo +began has been a patch regardless of what changed. + +* release.yml now gates on ci.yml (the same file PRs run), then hands + versioning to modules-semantic-release.yml. Nothing bumps or tags + by hand any more. +* publish-assets checks out the RELEASED TAG rather than github.sha. + semantic-release commits the version bump itself, so building from + the pushed commit produced a .crx whose manifest version did not + match the release it was attached to. +* No concurrency group on this workflow: semantic-release hard-resets + to origin, and cancelling it mid-flight can leave a tag pushed with + no release attached. +* publish-web-store stays wired but gated on PUBLISH_WEB_STORE, with + continue-on-error so a Web Store hiccup cannot invalidate a release + whose GitHub assets already published. +* codeql.yml: security-extended over javascript-typescript, weekly in + addition to push/PR — advisories land after code stops changing. + Findings report to the Security tab rather than blocking merges; a + SAST false positive that blocks every PR just teaches people to + bypass the gate. +* ai-issue-summary.yml and dependabot-maintenance.yml adopt the org + workflows for triage and update automation. +EOF +``` + +--- + +## Task 9: Full local verification + +**Files:** none — this task only runs things. + +**Interfaces:** + +- Consumes: everything from Tasks 1–8. +- Produces: the green baseline that makes the PR in Task 11 meaningful. + +Nothing has been pushed yet. This is the last chance to find a problem without it being visible on the remote. + +- [ ] **Step 1: Clean install from the lockfile** + +```bash +rm -rf node_modules +npm ci +``` + +Expected: completes with no `EBADENGINE` warnings. + +- [ ] **Step 2: Run every gate the CI will run** + +```bash +npm run lint +npm run format:check +npm run typecheck +npm run test:coverage +npm audit --audit-level=high +npm run build +npm run build:firefox +npm run build:edge +npm run zip +npm run zip:firefox +npm run zip:edge +``` + +Expected: all exit 0. Coverage clears 80 % on all four metrics. + +If `npm audit --audit-level=high` reports something, fix it now — the CI gate runs the same check and will block the PR. + +- [ ] **Step 3: Confirm the build outputs match `artifact-path`** + +```bash +ls -d .output/*/ +``` + +Expected: directories matching the `artifact-path` globs in `ci.yml` — `chrome-mv3`, `firefox-mv2`, `edge-mv3`. **If the Firefox or Edge directory is named differently, correct `ci.yml` now**, otherwise the artifact upload silently uploads nothing. + +- [ ] **Step 4: Confirm the working tree is clean** + +Run: `git status --short` +Expected: empty. Build outputs are gitignored. + +- [ ] **Step 5: Review the commit series** + +Run: `git log --oneline main` +Expected: the three original commits plus the spec commit plus one commit per task above. Every subject in past tense, every non-trivial one with a body. + +--- + +## Task 10: Secret scan across both histories + +**Files:** none — produces a report for the human. + +**Interfaces:** + +- Produces: the go/no-go input for the visibility flip in Task 13. + +The repo is about to become public and v1's 27 commits stay reachable through the migration. GitHub's push protection only ever inspected new pushes, never the existing history. + +- [ ] **Step 1: Scan the v2 history** + +```bash +cd "C:/Projects/Internal-Projects/BAUERGROUP.Extension.Chrome.HeaderAuth.v2" +npx --yes gitleaks@latest detect --source . --log-opts="--all" --redact --report-format json --report-path /tmp/gitleaks-v2.json --exit-code 0 +``` + +- [ ] **Step 2: Scan the v1 history** + +```bash +cd "C:/Projects/Internal-Projects/BAUERGROUP.Extension.Chrome.HeaderAuth" +npx --yes gitleaks@latest detect --source . --log-opts="--all" --redact --report-format json --report-path /tmp/gitleaks-v1.json --exit-code 0 +``` + +Read-only. Do not fetch, push, or modify anything in the v1 clone. + +- [ ] **Step 3: Summarise both reports** + +```bash +node -e " +for (const [name, path] of [['v2','/tmp/gitleaks-v2.json'],['v1','/tmp/gitleaks-v1.json']]) { + let findings = []; + try { findings = JSON.parse(require('fs').readFileSync(path,'utf8')) || []; } catch {} + console.log(name + ': ' + findings.length + ' finding(s)'); + for (const f of findings) console.log(' ' + f.RuleID + ' ' + f.File + ':' + f.StartLine + ' commit ' + String(f.Commit).slice(0,8)); +} +" +``` + +- [ ] **Step 4: Sanity-check the known non-secret** + +`wxt.config.ts` contains `EXTENSION_KEY`, which is a **public** key and is meant to be committed. If gitleaks flags it, that is a false positive — note it as such in the report rather than acting on it. The private key lives in `.keys/key.pem`, which is gitignored; confirm it was never committed: + +```bash +cd "C:/Projects/Internal-Projects/BAUERGROUP.Extension.Chrome.HeaderAuth.v2" +git log --all --diff-filter=A --name-only --format="%H" -- "*.pem" ".keys/*" | grep -v '^$' || echo "no key material ever committed (v2)" +cd "C:/Projects/Internal-Projects/BAUERGROUP.Extension.Chrome.HeaderAuth" +git log --all --diff-filter=A --name-only --format="%H" -- "*.pem" "*key*" | grep -v '^$' || echo "no key material ever committed (v1)" +``` + +- [ ] **Step 5: Present the report to the human and STOP** + +Report: finding counts per history, each finding's rule/file/commit, and which are false positives with the reason. + +**If there is any real finding, stop here and escalate.** A history rewrite is a separate decision with its own trade-offs — it is not a step in this plan. Do not proceed to Task 13 without an explicit go. + +--- + +## Task 11: Remote preparation and migration + +**Files:** none in the working tree — this task changes the remote and creates the migration commit. + +**Interfaces:** + +- Consumes: the green local state from Task 9. +- Produces: an open PR against `main`, and the branch tip SHA that Task 12 tags. + +- [ ] **Step 1: Close the three stale Dependabot PRs** + +```bash +for n in 1 2 3; do + gh pr close "$n" -R bauer-group/IP-Chrome-HeaderAuth --delete-branch \ + --comment "Superseded by the v2 rewrite: package.json is replaced wholesale and this dependency no longer exists in the tree. Closing rather than merging — the advisory is not remediated, the code carrying it is removed." +done +``` + +Verify: `gh pr list -R bauer-group/IP-Chrome-HeaderAuth --state open` +Expected: empty. + +- [ ] **Step 2: Rename `master` to `main`** + +```bash +gh api -X POST repos/bauer-group/IP-Chrome-HeaderAuth/branches/master/rename -f new_name=main +``` + +GitHub retargets any open PRs and installs redirects for `master` links. Doing this before the PR means it targets `main` from the start, and `modules-semantic-release.yml`'s `target-branch: 'main'` default applies unmodified. + +Verify: + +```bash +gh repo view bauer-group/IP-Chrome-HeaderAuth --json defaultBranchRef --jq .defaultBranchRef.name +``` + +Expected: `main` + +- [ ] **Step 3: Attach the remote and fetch** + +```bash +cd "C:/Projects/Internal-Projects/BAUERGROUP.Extension.Chrome.HeaderAuth.v2" +git remote add origin https://github.com/bauer-group/IP-Chrome-HeaderAuth.git +git fetch origin +``` + +Verify the two histories really are disjoint (this is what makes the tree-replace necessary): + +```bash +git merge-base HEAD origin/main || echo "no common ancestor — as expected" +``` + +Expected: `no common ancestor — as expected` + +- [ ] **Step 4: Record the local commit SHAs before they are collapsed** + +```bash +git log --oneline main | tail -n +1 +``` + +Copy the SHAs and subjects — the migration commit body names them so the collapsed history stays traceable. + +- [ ] **Step 5: Create the branch and replace the tree** + +```bash +git checkout -b feat/v2-wxt-rewrite origin/main +git rm -rq . +git checkout main -- . +git add -A +``` + +Verify the staged tree is exactly v2's: + +```bash +git diff --cached --stat | tail -1 +git status --short | grep -c '^D ' || true +``` + +Expected: deletions covering v1's files and additions covering v2's. Then confirm the tree matches `main` exactly: + +```bash +git write-tree +git rev-parse main^{tree} +``` + +Expected: **the two hashes are identical.** If they differ, the tree replacement is incomplete — stop and investigate before committing. + +- [ ] **Step 6: Create the migration commit** + +Substitute the real SHAs from Step 4 into the body. + +```bash +git commit -F - <<'EOF' +feat!: replaced the v1 extension with the v2 WXT rewrite + +v2 is a ground-up rewrite. It shares the product identity, the +X-BAUERGROUP-Auth header contract and the MIT licence with v1, and +nothing else: v1 was a hand-rolled tsc build with a manual manifest, +v2 is WXT + React 19 + Tailwind 4 + Zod with declarativeNetRequest +rule generation, enterprise policy support via chrome.storage.managed +and a signed self-hosted CRX channel. + +The two histories share no ancestor, so this commit replaces the tree +wholesale rather than merging. v1 history stays intact and reachable +below this commit; git bisect crosses the boundary cleanly. + +Collapsed from three commits in the standalone v2 repository: + d5ea84d feat(extension): scaffolded WXT extension foundation + 5503606 feat(ui): added professional popup and options UI + 2d55715 ci(release): added dual-channel release pipeline and docs +plus the CI/CD, Node 24 and test work done for this migration. + +BREAKING CHANGE: the extension ID changed from a per-install random +ID to the pinned jncjhkagdjiiohjfmbpmlemdchbkjaib. v1 installations +do not upgrade in place — managed deployments must update their +ExtensionInstallForcelist entry, and manual installs must be +reinstalled. Stored v1 configuration is not migrated. +EOF +``` + +- [ ] **Step 7: Push and open the PR** + +```bash +git push -u origin feat/v2-wxt-rewrite +gh pr create -R bauer-group/IP-Chrome-HeaderAuth \ + --base main --head feat/v2-wxt-rewrite \ + --title "feat!: v2 WXT rewrite with professional CI/CD" \ + --body "$(cat <<'PRBODY' +Replaces the v1 `tsc`/MV3 extension with the v2 WXT rewrite and brings the repo onto the BAUER GROUP CI/CD standard. + +## What changed + +**Extension** — ground-up rewrite: WXT + React 19 + Tailwind 4 + Radix + Zod, `declarativeNetRequest` rule generation, enterprise policy via `chrome.storage.managed`, sharded sync storage with device-local secrets, signed self-hosted CRX channel. + +**CI/CD** — six workflows consuming `bauer-group/automation-templates`: +- `ci.yml` — the gate: lint, format, typecheck, tests with an 80% coverage threshold, `npm audit --audit-level=high`, and all three browser builds on a Node `[24, 26]` matrix. `release.yml` reuses this exact file via `workflow_call`, so the pre-merge gate and the release gate cannot drift. +- `pr-validation.yml` — commitlint, secret scan, license check, dependency review. +- `release.yml` — semantic-release replaces the previous unconditional `npm version patch`, which meant a `feat:` never cut a MINOR and a `BREAKING CHANGE:` never cut a MAJOR. +- `codeql.yml`, `ai-issue-summary.yml`, `dependabot-maintenance.yml`. + +**Runtime** — Node floor raised to 24 (`.nvmrc`). The previous `engines.node: ">=20"` was never satisfiable by this dependency set: `lint-staged@17` requires `>=22.22.1`. Node 20 reached EOL on 2026-04-30. + +**Tests** — coverage over `src/lib` gated at 80%. The six modules touching browser APIs were entirely untested. + +## Review notes + +The diff is large because the tree is replaced wholesale — the two histories share no ancestor. v1 history remains intact and reachable below the migration commit. + +`.nvmrc`, `LICENSE`, `.github/` and `docs/superpowers/` are the parts worth reading closely; `src/` is the v2 tree as it stood in the standalone repository plus the test additions. + +Design and plan: `docs/superpowers/specs/2026-08-17-cicd-und-remote-migration-design.md` and `docs/superpowers/plans/2026-08-17-cicd-und-remote-migration.md`. + +## Not in this PR + +- The repository visibility flip (needed for the self-hosted CRX channel to work — Chrome fetches release assets anonymously). +- Chrome Web Store publishing: wired but gated behind `PUBLISH_WEB_STORE`. +PRBODY +)" +``` + +- [ ] **Step 8: Record the branch tip SHA** + +```bash +git rev-parse feat/v2-wxt-rewrite +``` + +Task 12 tags this exact SHA. Write it down. + +--- + +## Task 12: Validate, tag the baseline, merge, release + +**Files:** none — this task drives CI and the release. + +**Interfaces:** + +- Consumes: the branch tip SHA from Task 11. +- Produces: `v2.0.0` on `main` with assets attached. + +- [ ] **Step 1: Wait for CI and read the result** + +```bash +gh pr checks -R bauer-group/IP-Chrome-HeaderAuth feat/v2-wxt-rewrite --watch +``` + +Expected: every check green. + +This is the first real execution of the new pipeline. A failure here is the plan working as intended — fix it on the branch, push, and re-run. Common first-run failures and their causes: + +- `startup_failure` with no log → a job is missing `pull-requests: write` (see Global Constraints). +- `Invalid input` → a `with:` key does not exist on the called workflow; re-check Task 7 Step 4. +- Empty artifact upload → `artifact-path` does not match the real `.output/` directory names; re-check Task 9 Step 3. + +- [ ] **Step 2: Record the exact check names** + +```bash +gh pr checks -R bauer-group/IP-Chrome-HeaderAuth feat/v2-wxt-rewrite --json name,state --jq '.[] | .name' +``` + +Task 13 needs these verbatim for branch protection. + +- [ ] **Step 3: Push the `v2.0.0` baseline tag** + +```bash +SHA="$(git rev-parse feat/v2-wxt-rewrite)" +git tag -a v2.0.0 "$SHA" -m "v2.0.0 — WXT rewrite baseline + +Sets the semantic-release baseline. v1's tags (v0.0.19, v0.0.20, +v0.0.24) remain reachable, so without this tag the first automated +release would compute 1.0.0 from v0.0.24 rather than the 2.0.0 the +product is called." +git push origin v2.0.0 +``` + +Verify: `git ls-remote --tags origin | grep v2.0.0` +Expected: one line, matching `$SHA`. + +- [ ] **Step 4: Merge with a MERGE COMMIT** + +```bash +gh pr merge -R bauer-group/IP-Chrome-HeaderAuth feat/v2-wxt-rewrite --merge --delete-branch +``` + +**`--merge`, never `--squash` and never `--rebase`.** Both rewrite the commit SHA, which would leave `v2.0.0` pointing at a commit that never reaches `main`; semantic-release would then baseline off `v0.0.24` and cut `1.0.0` over the top. + +Verify the tag is reachable from `main`: + +```bash +git fetch origin +git merge-base --is-ancestor v2.0.0 origin/main && echo "v2.0.0 is on main" +``` + +Expected: `v2.0.0 is on main` + +- [ ] **Step 5: Confirm the first release run is a no-op** + +```bash +gh run list -R bauer-group/IP-Chrome-HeaderAuth --workflow="🚀 Release" --limit 1 +gh run watch -R bauer-group/IP-Chrome-HeaderAuth "$(gh run list -R bauer-group/IP-Chrome-HeaderAuth --workflow='🚀 Release' --limit 1 --json databaseId --jq '.[0].databaseId')" +``` + +Expected: the gate passes, the release job reports **no new version** ("There are no relevant changes, so no new version is released"), and `publish-assets` is skipped because `release-created` is not `'true'`. + +This is correct: the highest reachable tag is `v2.0.0`, and the only commit since it is the merge commit, which carries no conventional type. + +- [ ] **Step 6: Create the v2.0.0 GitHub Release with assets** + +semantic-release deliberately did not create this one — the baseline tag predates it. Build the assets locally and publish once: + +```bash +cd "C:/Projects/Internal-Projects/BAUERGROUP.Extension.Chrome.HeaderAuth.v2" +git checkout main && git pull origin main +npm ci +npm run build && npm run zip && npm run zip:firefox && npm run zip:edge +GITHUB_REPOSITORY=bauer-group/IP-Chrome-HeaderAuth npm run sign:crx + +gh release create v2.0.0 -R bauer-group/IP-Chrome-HeaderAuth \ + --title "v2.0.0 — WXT rewrite" \ + --notes "First release of the v2 extension: a ground-up WXT + React 19 rewrite of the v1 \`tsc\` extension. + +Shares the product identity, the \`X-BAUERGROUP-Auth\` header contract and the MIT licence with v1, and nothing else. + +**Breaking:** the extension ID is now the pinned \`jncjhkagdjiiohjfmbpmlemdchbkjaib\`. v1 installations do not upgrade in place — managed deployments must update their \`ExtensionInstallForcelist\` entry and stored v1 configuration is not migrated. + +Every subsequent release is cut automatically by semantic-release from Conventional Commits." \ + .output/*-chrome.zip \ + .output/*-firefox.zip \ + .output/*-edge.zip \ + .output/header-authenticator.crx \ + .output/updates.xml +``` + +Verify: `gh release view v2.0.0 -R bauer-group/IP-Chrome-HeaderAuth --json assets --jq '.assets[].name'` +Expected: five assets. + +--- + +## Task 13: Repository settings and the visibility flip + +**Files:** none — repository configuration only. + +**Interfaces:** + +- Consumes: the check names from Task 12 Step 2, and the human's go from Task 10 Step 5. + +- [ ] **Step 1: Enable auto-merge and branch cleanup** + +```bash +gh api -X PATCH repos/bauer-group/IP-Chrome-HeaderAuth \ + -F allow_auto_merge=true \ + -F delete_branch_on_merge=true +``` + +`allow_auto_merge` is what makes `dependabot-maintenance.yml` functional; without it the workflow approves PRs that then sit unmerged. + +- [ ] **Step 2: Set the signing key secret** + +```bash +cd "C:/Projects/Internal-Projects/BAUERGROUP.Extension.Chrome.HeaderAuth.v2" +gh secret set EXTENSION_PRIVATE_KEY -R bauer-group/IP-Chrome-HeaderAuth < .keys/key.pem +``` + +The redirect never prints the key material. Verify it registered without reading it back: + +```bash +gh secret list -R bauer-group/IP-Chrome-HeaderAuth +``` + +Expected: `EXTENSION_PRIVATE_KEY` listed. + +**Tell the human:** this key exists only on this workstation and in the secret store. It is the sole determinant of the extension ID; losing it invalidates every force-install policy. A copy belongs in the team password manager — that is a human task the pipeline cannot do. + +- [ ] **Step 3: Protect `main` with the real check names** + +Use the names recorded in Task 12 Step 2. Substitute them for the placeholders below — do not guess them. + +```bash +gh api -X PUT repos/bauer-group/IP-Chrome-HeaderAuth/branches/main/protection \ + --input - <<'JSON' +{ + "required_status_checks": { + "strict": true, + "contexts": ["", ""] + }, + "enforce_admins": false, + "required_pull_request_reviews": null, + "restrictions": null, + "allow_force_pushes": false, + "allow_deletions": false +} +JSON +``` + +`required_pull_request_reviews: null` and `enforce_admins: false` are deliberate: this is a solo-developer repo, so a review requirement would only block its own author, and semantic-release pushes the release commit directly to `main`. The status checks are the gate that matters. + +Verify: `gh api repos/bauer-group/IP-Chrome-HeaderAuth/branches/main/protection --jq '.required_status_checks.contexts'` + +- [ ] **Step 4: Flip visibility to public — ONLY after the human approves Task 10's report** + +Do not run this without an explicit go on the scan report. Once indexed, always indexed. + +```bash +gh api -X PATCH repos/bauer-group/IP-Chrome-HeaderAuth -f visibility=public +``` + +Verify: `gh repo view bauer-group/IP-Chrome-HeaderAuth --json visibility --jq .visibility` +Expected: `PUBLIC` + +- [ ] **Step 5: Verify the self-hosted CRX channel actually works** + +This is the whole point of the flip — confirm it rather than assuming it. Fetch anonymously, with no token: + +```bash +env -u GITHUB_TOKEN -u GH_TOKEN curl -sSIL -o /dev/null -w '%{http_code}\n' \ + https://github.com/bauer-group/IP-Chrome-HeaderAuth/releases/latest/download/header-authenticator.crx +env -u GITHUB_TOKEN -u GH_TOKEN curl -sSIL -o /dev/null -w '%{http_code}\n' \ + https://github.com/bauer-group/IP-Chrome-HeaderAuth/releases/latest/download/updates.xml +``` + +Expected: `200` for both. A `404` means the flip did not take effect or the assets are missing from the `latest` release. + +- [ ] **Step 6: Confirm the update manifest points at the right place** + +```bash +env -u GITHUB_TOKEN -u GH_TOKEN curl -sSL \ + https://github.com/bauer-group/IP-Chrome-HeaderAuth/releases/latest/download/updates.xml +``` + +Expected: an `` with the extension ID `jncjhkagdjiiohjfmbpmlemdchbkjaib`. + +If the `codebase` shows the old `BAUERGROUP.Extension.Chrome.HeaderAuth` fallback, `GITHUB_REPOSITORY` was not set when the assets were signed in Task 12 Step 6 — rebuild and re-upload. + +- [ ] **Step 7: Final report to the human** + +State plainly: which checks are required on `main`, that the CRX and `updates.xml` return 200 anonymously with the version they report, that `EXTENSION_PRIVATE_KEY` is set, and the outstanding human task (key backup in the password manager). If anything in Steps 5–6 failed, say so with the actual output rather than reporting completion. + +--- + +## Self-Review + +**Spec coverage.** §4.1 workflow set → Tasks 7–8. §4.2 permission ceiling → Global Constraints + Task 7 Step 1 comment. §4.3 version mechanics → Task 12 Steps 3–6. §4.4 CRX defect → Task 5 + Task 13 Steps 5–6. §5 file table → Tasks 1, 6, 7, 8. §5.1 action pins → Global Constraints. §5.2 dependabot prefixes → Task 6 Step 2. §6 runtime/deps → Tasks 1–2. §7 testing → Tasks 3–4. §8 migration sequence → Tasks 10–13. §9 risks → Task 10 Step 5 (escalation) and Task 13 Step 2 (key backup). + +**Deviation from the spec, recorded deliberately:** §6 lists `@webext-core/fake-browser` among the added dependencies. Task 3 does not add it — `wxt/testing` re-exports it and WXT already depends on it, so a direct dependency would be redundant. Fewer declared dependencies, identical capability. + +**Placeholder scan.** Two intentional placeholders remain, both because the value cannot exist until earlier steps run: the branch-tip SHA (Task 11 Step 8 → Task 12 Step 3) and the status-check names (Task 12 Step 2 → Task 13 Step 3). Both name the step that produces them and instruct against guessing. + +**Type consistency.** `EffectiveRule`, `Config`, `ManagedConfig`, `ModifyHeaderRule` match `src/lib/schema/config.ts`, `src/lib/storage/managed.ts` and `src/lib/dnr/build-rules.ts`. Function names used in tests — `computeEffectiveConfig`, `ruleStatus`, `loadManagedConfig`, `managedRulesToEffective`, `ManagedConfigSchema`, `loadConfig`, `saveConfig`, `onConfigChanged`, `applyRules`, `DnrLimitError`, `MAX_UNSAFE_DYNAMIC_RULES`, `hasOriginsForPatterns`, `requestOriginsForPatterns`, `removeOriginsForPatterns`, `patternsToOrigins` — all verified against the actual sources. `needs.release.outputs.tag-name` / `release-created` / `version` verified against `modules-semantic-release.yml`. diff --git a/docs/superpowers/specs/2026-08-17-cicd-und-remote-migration-design.md b/docs/superpowers/specs/2026-08-17-cicd-und-remote-migration-design.md new file mode 100644 index 0000000..0791a64 --- /dev/null +++ b/docs/superpowers/specs/2026-08-17-cicd-und-remote-migration-design.md @@ -0,0 +1,254 @@ +# CI/CD Professionalization & Remote Migration — Design + +**Date:** 2026-08-17 +**Repo:** `BAUERGROUP.Extension.Chrome.HeaderAuth.v2` → `bauer-group/IP-Chrome-HeaderAuth` +**Status:** Approved + +--- + +## 1. Problem + +Three problems, one workstream. + +**1.1 — The v2 repo has no remote and no real pipeline.** It carries a single +`.github/workflows/release.yml` that runs _after_ code lands on `main`, so nothing +is verified before a merge. That workflow calls `npm version patch` unconditionally: +the repo enforces Conventional Commits through commitlint, then discards the SemVer +information those commits carry. A `feat:` never yields a MINOR, a `BREAKING CHANGE:` +never yields a MAJOR. Its `publish-web-store` job re-checks-out the pre-bump commit +and rebuilds, so it can package a different version than the one released. + +**1.2 — The target remote holds v1, with a completely unrelated history.** +`bauer-group/IP-Chrome-HeaderAuth` is private, its default branch is `master`, and +it holds 27 commits from a hand-rolled `tsc` MV3 extension. `git merge-base` between +the two trees returns nothing — zero shared objects. GitHub cannot render a pull +request between branches with no common ancestor, so the preferred branch-and-PR +route requires manufacturing one first. + +**1.3 — The declared runtime is not merely stale, it was never satisfiable.** +`engines.node: ">=20"` sits in `package.json` while `lint-staged@17` demands +`node >=22.22.1` and `@commitlint/cli@21` demands `>=22.12.0`. Node 20 reached EOL +on 2026-04-30. + +## 2. Goals & non-goals + +**Goals.** Bring the v2 repo to the CI/CD standard of the best repos in the estate, +consuming `bauer-group/automation-templates` rather than reimplementing it. Land v2 +on the existing remote through a reviewable pull request without destroying v1 +history. Move the runtime to Node 24+ and refresh the dependency set. Raise the test +floor high enough that a coverage gate can actually be enforced. + +**Non-goals.** No feature work on the extension itself. No Chrome Web Store listing +is created (the publish job ships wired but disabled). No history rewrite of v1 — +if the secret scan finds something, that becomes its own decision, not a silent fix. + +## 3. Decisions + +| # | Decision | Rationale | +| --- | ------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| D1 | **Tree-replacement commit + PR** (over `-s ours` merge or archive-and-promote) | Manufactures the common ancestor GitHub needs, keeps v1 history linear and reachable, `git bisect` works across the boundary, and it is plain git with no exotic strategy flags. Cost: v2's three local commit messages collapse into one, mitigated by naming their SHAs in the commit body. | +| D2 | **First v2 release is `2.0.0`**, set by an explicit annotated tag | The product is called v2; the SemVer major should say so. Requires the baseline tag because v1's tags stay reachable under D1. | +| D3 | **Rename `master` → `main` before opening the PR** | GitHub's rename API retargets open PRs and installs redirects. Doing it first means the PR targets `main` from the start, `modules-semantic-release.yml`'s `target-branch: 'main'` default applies unmodified, and branch protection is configured once. | +| D4 | **Repo becomes public**, after a secret scan of both histories | The self-hosted CRX channel is inoperative on a private repo (see §4.4). MIT-licensed already. | +| D5 | **Close the 3 open Dependabot PRs** with a reason | They patch v1 code that the migration deletes. Merging them first would add three merge commits for code removed moments later. The CVEs are not fixed — the code is gone; the closing comment says so. | +| D6 | **Chrome Web Store job stays wired but disabled** | v1 was never published from this repo (verified across all 27 commits: no `key.pem`, no CWS credentials, no upload step). v2 pins a new extension ID, so it could not update a pre-existing listing in place anyway. | +| D7 | **Reusable-first: `nodejs-build.yml@main` for the gate** | It covers `node-version-file`, `enable-matrix`, lint/format/typecheck/test/coverage/audit/build and artifact upload. The extension-specific parts fit its existing inputs: three browsers in one `build-command`, `.output/` in `artifact-path`. No hand-rolling justification exists here. | +| D8 | **TypeScript stays on `^6.0.3`** | `typescript-eslint@8.67` declares peer `typescript: ">=4.8.4 <6.1.0"`. TS 7 shipped without a stable programmatic API and typescript-eslint closed TS7 support as _not planned_. Upgrading breaks `npm run lint`. Recorded here **and** as a `dependabot.yml` ignore entry, so the eventual PR is not helpfully merged. | + +## 4. Architecture + +### 4.1 Workflow set + +``` +pull_request ──┬─► ci.yml 🔍 nodejs-build.yml@main + │ matrix node [24, 26] + │ lint · format:check · typecheck + │ test + coverage (80% on src/lib) + │ audit (high) · build chrome+firefox+edge + │ + └─► pr-validation.yml 🔍 modules-pr-validation.yml@main + modules-dependency-review.yml@main + +push main ─────┬─► ci.yml (regression net) + │ + └─► release.yml 🚀 + ├─ gate uses: ./.github/workflows/ci.yml + ├─ release needs gate · modules-semantic-release.yml@main + ├─ publish-assets if release-created · checkout ref: v${version} + └─ publish-web-store if vars.PUBLISH_WEB_STORE · continue-on-error + +issues/PR ─────► ai-issue-summary.yml 🤖 +schedule/push ─► codeql.yml 🛡️ modules-codeql.yml@main +dependabot PR ─► dependabot-maintenance.yml 🤖 +``` + +`ci.yml` declares `workflow_call` so `release.yml` reuses it rather than duplicating +the `with:` block. This keeps the pre-merge gate and the release gate provably +identical — they are the same file. + +### 4.2 The permission ceiling + +`nodejs-build.yml` internally calls `modules-code-quality.yml`, which declares +`pull-requests: write`. GitHub validates the permissions of every called workflow +when the run is created — **before** any job `if:` is evaluated — so this applies +even with `enable-sonar: false`. A caller that cannot satisfy it fails with +`startup_failure` and produces no log. + +Omitting the `permissions:` block does not help: a partial block sets every unnamed +scope to `none`, and the repo default is already `pull-requests: none`. Every job +that calls `nodejs-build.yml`, directly or through `ci.yml`, must declare: + +```yaml +permissions: + contents: read + pull-requests: write +``` + +### 4.3 Version mechanics — the 2.0.0 baseline + +Under D1 the v1 tags (`v0.0.19`, `v0.0.20`, `v0.0.24`) stay reachable from the +release branch, and semantic-release baselines off the highest reachable tag. Left +alone, the migration commit — a `feat!:` — would compute `1.0.0` +(`semver.inc('0.0.24', 'major')`; semantic-release applies no preMajor downgrade, +that is `standard-version` behaviour). D2 asks for `2.0.0`, so the baseline is set +explicitly: + +``` +1. push branch, open PR → ci.yml validates the full pipeline BEFORE the merge +2. git tag -a v2.0.0 → seeds the baseline + git push origin v2.0.0 +3. gh pr merge --merge → MERGE COMMIT, never squash + (squash rewrites the SHA; the tag would not be in main) +4. release.yml fires on main → highest reachable tag = v2.0.0 + commits since = the merge commit only, no conventional type + → "no relevant changes" → no release, clean no-op +5. gh release create v2.0.0 → one-time, manual, with zips + crx + updates.xml +6. next feat:/fix: → 2.1.0 / 2.0.1 / 3.0.0, fully automatic +``` + +Step 5 is the only manual version action, and it is appropriate: it is a one-time +event. From step 6 onward nothing touches a version number by hand. + +**Squash merging is not an option here.** It produces a new commit SHA, leaving the +`v2.0.0` tag pointing at a commit that never reaches `main`; semantic-release would +then fall back to `v0.0.24` and cut `1.0.0` over the top. + +### 4.4 The CRX distribution defect + +`scripts/sign-crx.mjs` writes `updates.xml` with a `codebase` of +`https://github.com/${GITHUB_REPOSITORY}/releases/latest/download/header-authenticator.crx`. +Chrome's updater fetches that URL **anonymously**. Release assets on a private repo +require a token, so the URL returns 404 on every managed device — the entire +`docs/deployment/` force-install story (GPO `ExtensionInstallForcelist`, the `.reg`, +the `.mobileconfig`) is inoperative today. D4 resolves this. + +Two further defects in the same file: the repo fallback is +`bauer-group/BAUERGROUP.Extension.Chrome.HeaderAuth`, which is neither repo's name, +and a missing key causes `process.exit(0)` — a green release with silently absent +assets. The soft path stays for local development; under `CI` it must fail loudly. + +## 5. Files + +| Path | Status | Content | +| ---------------------------------------------- | --------- | --------------------------------------------- | +| `.nvmrc` | new | `24` | +| `LICENSE` | new | MIT, `Copyright (c) BAUER GROUP` | +| `CHANGELOG.md` | generated | by semantic-release | +| `.github/CODEOWNERS` | new | `* @bauer-group/core` | +| `.github/dependabot.yml` | new | npm + github-actions, weekly sunday 06:30 UTC | +| `.github/config/release/semantic-release.json` | new | Node variant, `npmPublish: false` | +| `.github/config/commitlint.config.js` | new | path read by `modules-pr-validation.yml` | +| `.github/workflows/ci.yml` | new | gate | +| `.github/workflows/pr-validation.yml` | new | commit + supply-chain gates | +| `.github/workflows/release.yml` | replaced | release pipeline | +| `.github/workflows/codeql.yml` | new | SAST | +| `.github/workflows/ai-issue-summary.yml` | new | AI triage | +| `.github/workflows/dependabot-maintenance.yml` | new | auto-merge | + +Deliberately **not** created — inherited from `bauer-group/.github`: +`SECURITY.md`, `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, `PULL_REQUEST_TEMPLATE.md`, +`ISSUE_TEMPLATE/`. + +### 5.1 Action pins + +`actions/checkout@v7`, `actions/setup-node@v6`, `softprops/action-gh-release@v3`, +`actions/upload-artifact@v7`. Internal `bauer-group/automation-templates` references +pin `@main` per org rule. Node comes from `node-version-file: '.nvmrc'` — never a +literal in a workflow. + +### 5.2 Dependabot commit prefixes + +`github-actions` uses `chore(ci)`; npm uses `fix(deps)`. The prefix is load-bearing: +`chore(...)` never cuts a release, so a runtime dependency fix carrying that prefix +lands on `main` and then waits for an unrelated releasing commit — for a security +patch, potentially weeks. In a bundled extension nearly everything except pure +linting tooling ends up in the shipped artifact, so npm updates take `fix(deps)`. +A single grouped weekly PR bounds this to at most one patch release per week. + +## 6. Runtime & dependencies + +`engines.node` becomes `">=24"` and `.nvmrc` contains `24` — matching all 19 +non-external `.nvmrc` files found in the estate. `">=24"` rather than `^24` so +Node 26 is admitted when it reaches LTS on 2026-10-28 without a manifest change. + +The CI matrix covers `[24, 26]`. The release job stays **single**, pinned via +`.nvmrc`: a publishing pipeline that fans out produces duplicate tags and releases. + +| Change | Detail | +| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `wxt` `^0.20.26` → `^0.21.4` | 0.21 makes `vite` a required peer → add `vite` to devDependencies. Everything 0.21 removed (`webextension-polyfill`, `url:` imports, `useAppConfig`, `globalName`, isolated-element) is unused here. | +| `npm update` | 26 packages behind, all within-caret. `lucide-react` 1.20 → 1.31 spans 11 minors and gets an icon smoke-check. | +| `typescript` | **held** at `^6.0.3` — see D8. | +| `@radix-ui/react-dropdown-menu` | removed; declared but imported nowhere. | +| added | `@vitest/coverage-v8`, `jsdom`, `@webext-core/fake-browser` | + +## 7. Testing + +`vitest.config.ts` today sets `include: ['src/**/*.test.ts']`, which does not match +`.tsx` — component tests would silently not run. It becomes +`src/**/*.test.{ts,tsx}` with `environment: 'jsdom'` and a v8 coverage provider at +an **80 % threshold on `src/lib/**`\*\*. + +Six modules touching the `browser` global are currently untested and must be covered +before that threshold can be enforced: `src/lib/effective.ts`, +`src/lib/storage/index.ts`, `src/lib/storage/managed.ts`, `src/lib/dnr/apply-rules.ts`, +`src/lib/rule-status.ts`, `src/lib/permissions/index.ts`. They use +`@webext-core/fake-browser` — WXT's own testing path — rather than hand-written mocks. + +The gate's `coverage-threshold` is switched on only after these land. Enabling it +first would produce a red gate on day one. + +## 8. Migration sequence + +| # | Phase | Action | Reversible | +| --- | ----------- | ----------------------------------------------------------------------------------------------------------------- | ------------------- | +| 1 | Local | All changes from §5–7 on the v2 repo's `main`, atomic commits, green locally (lint · typecheck · test · build ×3) | yes | +| 2 | Scan | Gitleaks across **both** histories (27 v1 + n v2 commits) → report to the human | yes | +| 3 | Remote prep | Close Dependabot PRs #1–3 + delete branches · rename `master` → `main` | yes | +| 4 | Migration | `remote add` · branch from `origin/main` · tree-replace · commit · push · open PR | yes (delete branch) | +| 5 | Validation | Wait for CI green on the PR — the first real test of the new pipeline | yes | +| 6 | Merge | tag `v2.0.0` → `gh pr merge --merge` → `gh release create v2.0.0` with assets | `git revert` | +| 7 | Settings | `allow_auto_merge` · `delete_branch_on_merge` · branch protection · `EXTENSION_PRIVATE_KEY` secret | yes | +| 8 | Public | After human approval of the scan report: visibility flip · verify the CRX URL | **no** | + +Step 8 is the only practically irreversible action and requires explicit approval: +once indexed, always indexed. + +Branch protection required-checks are configured **after** the first CI run, because +the job names are not known until then. + +## 9. Risks + +**The secret scan may find something in v1 history.** That escalates to the human +rather than resolving silently — a history rewrite is its own decision with its own +trade-offs, not a step in this plan. + +**`.keys/key.pem` exists only on one workstation.** It is the sole determinant of +the extension ID `jncjhkagdjiiohjfmbpmlemdchbkjaib`; losing it invalidates every +force-install policy. It reaches CI via `gh secret set EXTENSION_PRIVATE_KEY < +.keys/key.pem`, which never prints the material. A second copy in the team password +manager is a human task, tracked here because the pipeline cannot do it. + +**`-s ours` was rejected** partly because a later merge of `master` into `main` would +be a silent no-op. D1 avoids the flag entirely, so this does not apply — noted so a +future reader does not reintroduce it. diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..2880a02 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,41 @@ +import js from '@eslint/js'; +import globals from 'globals'; +import tseslint from 'typescript-eslint'; +import reactHooks from 'eslint-plugin-react-hooks'; +import reactRefresh from 'eslint-plugin-react-refresh'; + +export default tseslint.config( + { + ignores: ['.wxt/**', '.output/**', 'node_modules/**', 'dist/**', 'coverage/**', '.keys/**'], + }, + js.configs.recommended, + ...tseslint.configs.recommended, + { + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2023, + globals: { ...globals.browser, ...globals.webextensions }, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/exhaustive-deps': 'warn', + 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }], + '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], + }, + }, + { + files: ['scripts/**/*.mjs', '*.config.{js,ts}'], + languageOptions: { + globals: { ...globals.node }, + }, + }, + { + // Design-system files intentionally co-export components + variant helpers. + files: ['src/components/ui/**', 'src/lib/i18n/index.tsx'], + rules: { 'react-refresh/only-export-components': 'off' }, + }, +); diff --git a/package-lock.json b/package-lock.json index 0ed3077..136a3db 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,1741 +1,7862 @@ { "name": "bauergroup-header-authenticator", - "version": "0.0.5", + "version": "2.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "bauergroup-header-authenticator", - "version": "0.0.5", + "version": "2.0.0", + "hasInstallScript": true, + "license": "MIT", "dependencies": { - "sharp": "^0.32.6" + "@radix-ui/react-alert-dialog": "^1.1.17", + "@radix-ui/react-dialog": "^1.1.17", + "@radix-ui/react-label": "^2.1.10", + "@radix-ui/react-slot": "^1.3.0", + "@radix-ui/react-switch": "^1.3.1", + "@radix-ui/react-tooltip": "^1.2.10", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^1.20.0", + "react": "^19.2.7", + "react-dom": "^19.2.7", + "sonner": "^2.0.7", + "tailwind-merge": "^3.6.0", + "zod": "^4.4.3" }, "devDependencies": { - "@types/archiver": "^6.0.1", - "@types/chrome": "^0.0.287", - "@types/node": "^22.10.0", - "archiver": "^6.0.1", - "bestzip": "^2.2.1", - "mkdirp": "^3.0.1", - "ncp": "^2.0.0", - "rimraf": "^5.0.5", - "typescript": "^5.7.3" - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + "@commitlint/cli": "^21.0.2", + "@commitlint/config-conventional": "^21.0.2", + "@eslint/js": "^10.0.1", + "@tailwindcss/vite": "^4.3.1", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "@vitest/coverage-v8": "^4.1.10", + "@wxt-dev/auto-icons": "^1.1.1", + "@wxt-dev/module-react": "^1.2.2", + "crx3": "^2.0.0", + "eslint": "^10.5.0", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.3", + "globals": "^17.6.0", + "jsdom": "^29.1.1", + "lint-staged": "^17.0.7", + "prettier": "^3.8.4", + "simple-git-hooks": "^2.13.1", + "tailwindcss": "^4.3.1", + "typescript": "^6.0.3", + "typescript-eslint": "^8.61.1", + "vite": "^8.2.1", + "vitest": "^4.1.9", + "wxt": "^0.21.4" }, "engines": { - "node": ">=12" + "node": ">=24" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "node_modules/@1natsu/wait-element": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@1natsu/wait-element/-/wait-element-4.2.0.tgz", + "integrity": "sha512-Om0Q+WE9mNrpY4AwMTvkFiYHv8VM7TML3PvOqXy+w6kAjLjKhGYHYX+305+a6J8RVpds9s7IF2Z5aOPYwULFNw==", "dev": true, "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "dependencies": { + "defu": "^6.1.4", + "many-keys-map": "^3.0.0" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "node_modules/@aklinker1/rollup-plugin-visualizer": { + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/@aklinker1/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.12.0.tgz", + "integrity": "sha512-X24LvEGw6UFmy0lpGJDmXsMyBD58XmX1bbwsaMLhNoM+UMQfQ3b2RtC+nz4b/NoRK5r6QJSKJHBNVeUdwqybaQ==", "dev": true, "license": "MIT", + "dependencies": { + "open": "^8.4.0", + "picomatch": "^2.3.1", + "source-map": "^0.7.4", + "yargs": "^17.5.1" + }, + "bin": { + "rollup-plugin-visualizer": "dist/bin/cli.js" + }, "engines": { - "node": ">=12" + "node": ">=14" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "peerDependencies": { + "rollup": "2.x || 3.x || 4.x" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } } }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "node_modules/@aklinker1/rollup-plugin-visualizer/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "node_modules/@aklinker1/rollup-plugin-visualizer/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=12" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "node_modules/@aklinker1/rollup-plugin-visualizer/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "ansi-regex": "^6.0.1" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" }, "engines": { "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "node_modules/@aklinker1/rollup-plugin-visualizer/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, "engines": { - "node": ">=12" + "node": ">=8.6" }, "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "node_modules/@aklinker1/rollup-plugin-visualizer/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", - "optional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, "engines": { - "node": ">=14" + "node": ">=8" } }, - "node_modules/@types/archiver": { + "node_modules/@aklinker1/rollup-plugin-visualizer/node_modules/strip-ansi": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-6.0.1.tgz", - "integrity": "sha512-F2+JkmDYvtQrtb2YldwL0apRB1/WB6ub+1zVF/bKp3TOygUMFqfOLuw5Fj62Q+DPwJUFz1eocMxJMu7yVpplZA==", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, + "license": "MIT", "dependencies": { - "@types/readdir-glob": "*" + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@types/chrome": { - "version": "0.0.287", - "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.287.tgz", - "integrity": "sha512-wWhBNPNXZHwycHKNYnexUcpSbrihVZu++0rdp6GEk5ZgAglenLx+RwdEouh6FrHS0XQiOxSd62yaujM1OoQlZQ==", + "node_modules/@aklinker1/rollup-plugin-visualizer/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", "dependencies": { - "@types/filesystem": "*", - "@types/har-format": "*" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/@types/filesystem": { - "version": "0.0.35", - "resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.35.tgz", - "integrity": "sha512-1eKvCaIBdrD2mmMgy5dwh564rVvfEhZTWVQQGRNn0Nt4ZEnJ0C8oSUCzvMKRA4lGde5oEVo+q2MrTTbV/GHDCQ==", + "node_modules/@aklinker1/rollup-plugin-visualizer/node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", "dev": true, + "license": "MIT", "dependencies": { - "@types/filewriter": "*" + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" } }, - "node_modules/@types/filewriter": { - "version": "0.0.32", - "resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.32.tgz", - "integrity": "sha512-Kpi2GXQyYJdjL8mFclL1eDgihn1SIzorMZjD94kdPZh9E4VxGOeyjPxi5LpsM4Zku7P0reqegZTt2GxhmA9VBg==", - "dev": true + "node_modules/@aklinker1/rollup-plugin-visualizer/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } }, - "node_modules/@types/har-format": { - "version": "1.2.15", - "resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.15.tgz", - "integrity": "sha512-RpQH4rXLuvTXKR0zqHq3go0RVXYv/YVqv4TnPH95VbwUxZdQlK1EtcMvQvMpDngHbt13Csh9Z4qT9AbkiQH5BA==", - "dev": true + "node_modules/@aklinker1/zero-zip": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@aklinker1/zero-zip/-/zero-zip-1.0.1.tgz", + "integrity": "sha512-07a596Bd1QO0bi3tIyt2xruq6vKk7hCUt9enHBwggvipB0iiycoJ9/CqzN6UFawaKbOi6NBfMlUxUXzIOZ7Dsg==", + "dev": true, + "license": "MIT" }, - "node_modules/@types/node": { - "version": "22.19.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.7.tgz", - "integrity": "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==", + "node_modules/@asamuzakjp/css-color": { + "version": "5.1.11", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz", + "integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~6.21.0" + "@asamuzakjp/generational-cache": "^1.0.1", + "@csstools/css-calc": "^3.2.0", + "@csstools/css-color-parser": "^4.1.0", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, - "node_modules/@types/readdir-glob": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@types/readdir-glob/-/readdir-glob-1.1.4.tgz", - "integrity": "sha512-uEJsErL2wFCTcbbmJpIuD8OWYNabgv1oaYP2bOkzZXKtk3c6LCYQEKngIqBj2VR2NMv9DOAXSkxSYOWtHxh2gQ==", + "node_modules/@asamuzakjp/dom-selector": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz", + "integrity": "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==", "dev": true, + "license": "MIT", "dependencies": { - "@types/node": "*" + "@asamuzakjp/generational-cache": "^1.0.1", + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/@asamuzakjp/generational-cache": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz", + "integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", "dev": true, + "license": "MIT" + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=6.9.0" } }, - "node_modules/archiver": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-6.0.1.tgz", - "integrity": "sha512-CXGy4poOLBKptiZH//VlWdFuUC1RESbdZjGjILwBuZ73P7WkAUN0htfSfBq/7k6FRFlpu7bg4JOkj1vU9G6jcQ==", + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "dev": true, - "dependencies": { - "archiver-utils": "^4.0.1", - "async": "^3.2.4", - "buffer-crc32": "^0.2.1", - "readable-stream": "^3.6.0", - "readdir-glob": "^1.1.2", - "tar-stream": "^3.0.0", - "zip-stream": "^5.0.1" - }, + "license": "MIT", "engines": { - "node": ">= 12.0.0" + "node": ">=6.9.0" } }, - "node_modules/archiver-utils": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-4.0.1.tgz", - "integrity": "sha512-Q4Q99idbvzmgCTEAAhi32BkOyq8iVI5EwdO0PmBDSGIzzjYNdcFn7Q7k3OzbLy4kLUPXfJtG6fO2RjftXbobBg==", + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, + "license": "MIT", "dependencies": { - "glob": "^8.0.0", - "graceful-fs": "^4.2.0", - "lazystream": "^1.0.0", - "lodash": "^4.17.15", - "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" }, "engines": { - "node": ">= 12.0.0" + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" } }, - "node_modules/archiver-utils/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/archiver-utils/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", "dev": true, + "license": "MIT", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=6.9.0" } }, - "node_modules/archiver-utils/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "dev": true, + "license": "MIT", "dependencies": { - "brace-expansion": "^2.0.1" + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" }, "engines": { - "node": ">=10" + "node": ">=6.9.0" } }, - "node_modules/async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", - "dev": true + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } }, - "node_modules/b4a": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", - "integrity": "sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==" + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/bare-events": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", - "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", - "license": "Apache-2.0", - "peerDependencies": { - "bare-abort-controller": "*" + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, - "peerDependenciesMeta": { - "bare-abort-controller": { - "optional": true - } + "engines": { + "node": ">=6.9.0" } }, - "node_modules/bare-fs": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.3.tgz", - "integrity": "sha512-9+kwVx8QYvt3hPWnmb19tPnh38c6Nihz8Lx3t0g9+4GoIf3/fTgYwM4Z6NxgI+B9elLQA7mLE9PpqcWtOMRDiQ==", - "license": "Apache-2.0", - "optional": true, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", "dependencies": { - "bare-events": "^2.5.4", - "bare-path": "^3.0.0", - "bare-stream": "^2.6.4", - "bare-url": "^2.2.2", - "fast-fifo": "^1.3.2" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { - "bare": ">=1.16.0" + "node": ">=6.9.0" }, "peerDependencies": { - "bare-buffer": "*" - }, - "peerDependenciesMeta": { - "bare-buffer": { - "optional": true - } + "@babel/core": "^7.0.0" } }, - "node_modules/bare-os": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz", - "integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==", - "license": "Apache-2.0", - "optional": true, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", "engines": { - "bare": ">=1.14.0" + "node": ">=6.9.0" } }, - "node_modules/bare-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", - "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "bare-os": "^3.0.1" + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" } }, - "node_modules/bare-stream": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz", - "integrity": "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "streamx": "^2.21.0" - }, - "peerDependencies": { - "bare-buffer": "*", - "bare-events": "*" - }, - "peerDependenciesMeta": { - "bare-buffer": { - "optional": true - }, - "bare-events": { - "optional": true - } + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" } }, - "node_modules/bare-url": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz", - "integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==", - "license": "Apache-2.0", - "optional": true, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", "dependencies": { - "bare-path": "^3.0.0" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/bestzip": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/bestzip/-/bestzip-2.2.1.tgz", - "integrity": "sha512-XdAb87RXqOqF7C6UgQG9IqpEHJvS6IOUo0bXWEAebjSSdhDjsbcqFKdHpn5Q7QHz2pGr3Zmw4wgG3LlzdyDz7w==", + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", "dev": true, + "license": "MIT", "dependencies": { - "archiver": "^5.3.0", - "async": "^3.2.0", - "glob": "^7.1.6", - "which": "^2.0.2", - "yargs": "^16.2.0" + "@babel/types": "^7.29.8" }, "bin": { - "bestzip": "bin/cli.js" + "parser": "bin/babel-parser.js" }, "engines": { - "node": ">=10" + "node": ">=6.0.0" } }, - "node_modules/bestzip/node_modules/archiver": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", - "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, + "license": "MIT", "dependencies": { - "archiver-utils": "^2.1.0", - "async": "^3.2.4", - "buffer-crc32": "^0.2.1", - "readable-stream": "^3.6.0", - "readdir-glob": "^1.1.2", - "tar-stream": "^2.2.0", - "zip-stream": "^4.1.0" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { - "node": ">= 10" + "node": ">=6.9.0" } }, - "node_modules/bestzip/node_modules/archiver-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", - "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", "dev": true, + "license": "MIT", "dependencies": { - "glob": "^7.1.4", - "graceful-fs": "^4.2.0", - "lazystream": "^1.0.0", - "lodash.defaults": "^4.2.0", - "lodash.difference": "^4.5.0", - "lodash.flatten": "^4.4.0", - "lodash.isplainobject": "^4.0.6", - "lodash.union": "^4.6.0", - "normalize-path": "^3.0.0", - "readable-stream": "^2.0.0" + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" }, "engines": { - "node": ">= 6" + "node": ">=6.9.0" } }, - "node_modules/bestzip/node_modules/archiver-utils/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", "dev": true, + "license": "MIT", "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/bestzip/node_modules/compress-commons": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", - "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", "dev": true, - "dependencies": { - "buffer-crc32": "^0.2.13", - "crc32-stream": "^4.0.2", - "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" - }, + "license": "MIT", "engines": { - "node": ">= 10" + "node": ">=18" } }, - "node_modules/bestzip/node_modules/crc32-stream": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", - "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", "dev": true, + "license": "MIT", "dependencies": { - "crc-32": "^1.2.0", - "readable-stream": "^3.4.0" + "css-tree": "^3.0.0" }, - "engines": { - "node": ">= 10" + "bin": { + "specificity": "bin/cli.js" } }, - "node_modules/bestzip/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/bestzip/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/bestzip/node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "node_modules/@commitlint/cli": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-21.2.2.tgz", + "integrity": "sha512-a+6hQxIxnpdvSvS2apvttPNbEliYsVC3PqFYDiiB2kjbwIsQsj1urvQ4Tkf70pKYozPalKAuRQmm/GHwndduqA==", "dev": true, + "license": "MIT", "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" + "@commitlint/config-conventional": "^21.2.2", + "@commitlint/format": "^21.2.2", + "@commitlint/lint": "^21.2.2", + "@commitlint/load": "^21.2.2", + "@commitlint/read": "^21.2.1", + "@commitlint/types": "^21.2.0", + "tinyexec": "^1.0.0", + "yargs": "^18.0.0" + }, + "bin": { + "commitlint": "cli.js" }, "engines": { - "node": ">=6" + "node": ">=22.12.0" } }, - "node_modules/bestzip/node_modules/zip-stream": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", - "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", + "node_modules/@commitlint/config-conventional": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-21.2.2.tgz", + "integrity": "sha512-NxA37SZviusFUEYOQZ5hNnZ1h7O/KiemPkxjOlpzKJNnWxThiwc6/SaZhaPa8fyLvfRBAywhQhJJk8XESHWlpQ==", "dev": true, + "license": "MIT", "dependencies": { - "archiver-utils": "^3.0.4", - "compress-commons": "^4.1.2", - "readable-stream": "^3.6.0" + "@commitlint/types": "^21.2.0", + "conventional-changelog-conventionalcommits": "^10.0.0" }, "engines": { - "node": ">= 10" + "node": ">=22.12.0" } }, - "node_modules/bestzip/node_modules/zip-stream/node_modules/archiver-utils": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", - "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "node_modules/@commitlint/config-validator": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-21.2.0.tgz", + "integrity": "sha512-t7AzNHAKeIdo/3NRGwzpufKHsKkPHmFs/56N2Fnsh0/r0rGtnQzTxk6vnFgjaGr4hdSQKNB50/KAhR9Yk4LJKA==", "dev": true, + "license": "MIT", "dependencies": { - "glob": "^7.2.3", - "graceful-fs": "^4.2.0", - "lazystream": "^1.0.0", - "lodash.defaults": "^4.2.0", - "lodash.difference": "^4.5.0", - "lodash.flatten": "^4.4.0", - "lodash.isplainobject": "^4.0.6", - "lodash.union": "^4.6.0", - "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" + "@commitlint/types": "^21.2.0", + "ajv": "^8.11.0" }, "engines": { - "node": ">= 10" - } - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" + "node": ">=22.12.0" } }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "node_modules/@commitlint/ensure": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-21.2.0.tgz", + "integrity": "sha512-76IF9vDNS13lAzEEik9eKwzt8f9hYhWiwVXZ2AnyLCz5/f511FsEQ3pw1X3/zSQpdRLQU7i5qDMVKyXi1GWjSg==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" + "@commitlint/types": "^21.2.0", + "es-toolkit": "^1.46.0" + }, + "engines": { + "node": ">=22.12.0" } }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "node_modules/@commitlint/execute-rule": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-21.0.1.tgz", + "integrity": "sha512-RifH+FmImozKBE6mozhF4K3r2RRKP7SMi/Q/zLCmExtp5e05lhHOUYqGBlFBAGNHaZxU/WYw1XuugYK9jQzqnA==", "dev": true, + "license": "MIT", "engines": { - "node": "*" + "node": ">=22.12.0" } }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "node_modules/@commitlint/format": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-21.2.2.tgz", + "integrity": "sha512-v6fvxZSc/AvVMROlr3H34+1766bZSYApRUSCAMjWamStPjKMvZ8GdvVA5YW/VQNgbFTmcMz6OYmSTJEvIjPrfA==", "dev": true, + "license": "MIT", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/color": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", - "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", - "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" + "@commitlint/types": "^21.2.0", + "picocolors": "^1.1.1" }, "engines": { - "node": ">=12.5.0" + "node": ">=22.12.0" } }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@commitlint/is-ignored": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-21.2.2.tgz", + "integrity": "sha512-9UoKNgfFE3LU7FrzierCvk3CdDfMDeVGC86qZiT/n0TIjfq/dmZ9MHuXd45OTNRa26ZanmJRxEtmiXk/lEJihg==", + "dev": true, + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "@commitlint/types": "^21.2.0", + "semver": "^7.6.0" }, "engines": { - "node": ">=7.0.0" + "node": ">=22.12.0" } }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "node_modules/@commitlint/lint": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-21.2.2.tgz", + "integrity": "sha512-Fy8JxEBzdmsYWFude/61GxXu5O+wEymwiRK2z9GL9R8mCsXphCoGxAFc5iHn5mjlfcSrhiiONE+ksf4KOjnaPg==", + "dev": true, + "license": "MIT", "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" + "@commitlint/is-ignored": "^21.2.2", + "@commitlint/parse": "^21.2.2", + "@commitlint/rules": "^21.2.2", + "@commitlint/types": "^21.2.0" + }, + "engines": { + "node": ">=22.12.0" } }, - "node_modules/compress-commons": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-5.0.1.tgz", - "integrity": "sha512-MPh//1cERdLtqwO3pOFLeXtpuai0Y2WCd5AhtKxznqM7WtaMYaOEMSgn45d9D10sIHSfIKE603HlOp8OPGrvag==", + "node_modules/@commitlint/load": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-21.2.2.tgz", + "integrity": "sha512-0Tt6wDPX167cjKC5D4zhm0+20wJJG+TN/TKovMOspfSe78rOnKX+MNzlVNiu6HyQPZChPJ8QBH31MVt6Bb8fCg==", "dev": true, + "license": "MIT", "dependencies": { - "crc-32": "^1.2.0", - "crc32-stream": "^5.0.0", - "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" + "@commitlint/config-validator": "^21.2.0", + "@commitlint/execute-rule": "^21.0.1", + "@commitlint/resolve-extends": "^21.2.2", + "@commitlint/types": "^21.2.0", + "cosmiconfig": "^9.0.1", + "cosmiconfig-typescript-loader": "^6.1.0", + "es-toolkit": "^1.46.0", + "is-plain-obj": "^4.1.0", + "picocolors": "^1.1.1" }, "engines": { - "node": ">= 12.0.0" + "node": ">=22.12.0" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true - }, - "node_modules/crc-32": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", - "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "node_modules/@commitlint/message": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-21.2.0.tgz", + "integrity": "sha512-YxGoiXD/HXNXLJPrQwE5poXa+XH0CBEm+mdvbHQP0g6MV/dmJyUFCzPNzZbxL93GvZ70TmtTK0Z0/IBpAqHv8g==", "dev": true, - "bin": { - "crc32": "bin/crc32.njs" - }, + "license": "MIT", "engines": { - "node": ">=0.8" + "node": ">=22.12.0" } }, - "node_modules/crc32-stream": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-5.0.0.tgz", - "integrity": "sha512-B0EPa1UK+qnpBZpG+7FgPCu0J2ETLpXq09o9BkLkEAhdB6Z61Qo4pJ3JYu0c+Qi+/SAL7QThqnzS06pmSSyZaw==", + "node_modules/@commitlint/parse": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-21.2.2.tgz", + "integrity": "sha512-MEkobPfvRp+z06Wro8HMG1BDGHzZmj82A1LH1nWeG3ipHpg/x4m6v3wEDvMBIKjRFUnfR3nBeFs3MVCr7UdAmg==", "dev": true, + "license": "MIT", "dependencies": { - "crc-32": "^1.2.0", - "readable-stream": "^3.4.0" + "@commitlint/types": "^21.2.0", + "conventional-changelog-angular": "^9.0.0", + "conventional-commits-parser": "^7.0.0" }, "engines": { - "node": ">= 12.0.0" + "node": ">=22.12.0" } }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "node_modules/@commitlint/read": { + "version": "21.2.1", + "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-21.2.1.tgz", + "integrity": "sha512-hUW7EJQnNTL0vPOmVMNK4CrnrNBN0nN+JJHReFkdHO5y4iyHeEmTBwuC15OCqUTjxWo7idnH1LftfpWVIaPWIA==", "dev": true, "license": "MIT", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "@commitlint/top-level": "^21.2.0", + "@commitlint/types": "^21.2.0", + "@conventional-changelog/git-client": "^3.0.0", + "tinyexec": "^1.0.0" }, "engines": { - "node": ">= 8" + "node": ">=22.12.0" } }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "node_modules/@commitlint/resolve-extends": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-21.2.2.tgz", + "integrity": "sha512-RPkJ/IFi7sMUUVbZLqwWFtWw/zRDcfFsmrPSiTMrt5wb7AdxOr86EGQFvmGzef5QKV5IPBWWCujqVTw1RWX44A==", + "dev": true, + "license": "MIT", "dependencies": { - "mimic-response": "^3.1.0" + "@commitlint/config-validator": "^21.2.0", + "@commitlint/types": "^21.2.0", + "es-toolkit": "^1.46.0", + "global-directory": "^5.0.0", + "resolve-from": "^5.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=22.12.0" } }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "node_modules/@commitlint/rules": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-21.2.2.tgz", + "integrity": "sha512-eplQzyYkBjYB1HyyRj8hkcK11Y9DU9nuBz7uOKEd6NpE9NGDytLFCAnlRE+OoiK/5sHEJsaz2RGhuWBvYzIbNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/ensure": "^21.2.0", + "@commitlint/message": "^21.2.0", + "@commitlint/to-lines": "^21.0.1", + "@commitlint/types": "^21.2.0" + }, "engines": { - "node": ">=4.0.0" + "node": ">=22.12.0" } }, - "node_modules/detect-libc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", - "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "node_modules/@commitlint/to-lines": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-21.0.1.tgz", + "integrity": "sha512-bd1BFII7p1EQZre9Kaj+kKaMFP3cFCdt21K7DItVux9XP5WjLgJ0/Uy1pJJh9aPwVJ6SKg62PxqlZaHI8hQAXw==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=22.12.0" } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "node_modules/@commitlint/top-level": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-21.2.0.tgz", + "integrity": "sha512-Y5gmQ+KxzqCrBFJfLvFEPvvwD3LDiNZoTT2yeFBm96M8qhmqSzQc5DvX3rheAaAMjyIvMXOCLS/mWfdpONsjyQ==", "dev": true, - "license": "MIT" - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "license": "MIT", "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, + "escalade": "^3.2.0" + }, "engines": { - "node": ">=6" + "node": ">=22.12.0" } }, - "node_modules/events-universal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", - "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", - "license": "Apache-2.0", + "node_modules/@commitlint/types": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-21.2.0.tgz", + "integrity": "sha512-7zVFCDB2reMvJH5dmbKnOQPjZEvjdJTH8jc0U/PIPU1r3/+vf5pD1HlfitV2MWsWXrvu7u39iY1lyLUPOaN0Gw==", + "dev": true, + "license": "MIT", "dependencies": { - "bare-events": "^2.7.0" - } - }, - "node_modules/expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "conventional-commits-parser": "^7.0.0", + "picocolors": "^1.1.1" + }, "engines": { - "node": ">=6" + "node": ">=22.12.0" } }, - "node_modules/fast-fifo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", - "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==" - }, - "node_modules/foreground-child": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", - "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "node_modules/@conventional-changelog/git-client": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@conventional-changelog/git-client/-/git-client-3.1.2.tgz", + "integrity": "sha512-jZqwnJwf7nboIlAcw/mkOjVa6DexCcUOgT2oOQgkoi3z9vR8tGFkcMy2BFcYwjhL9sYcDDXkRQDayiDieCoW7A==", "dev": true, + "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" + "@simple-libs/child-process-utils": "^2.0.0", + "@simple-libs/stream-utils": "^2.0.0", + "semver": "^7.5.2" }, "engines": { - "node": ">=14" + "node": ">=22" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "peerDependencies": { + "conventional-commits-filter": "^6.0.1", + "conventional-commits-parser": "^7.1.2" + }, + "peerDependenciesMeta": { + "conventional-commits-filter": { + "optional": true + }, + "conventional-commits-parser": { + "optional": true + } } }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "node_modules/@conventional-changelog/template": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@conventional-changelog/template/-/template-1.3.0.tgz", + "integrity": "sha512-GsCw/qu92GI0EX6s7fxUi/SG1lmFjG9XZivxxEDZXqztuQKCn5o5wKdz4v005zci0Md7EZzgAwmQLoIEDZoaow==", "dev": true, + "license": "MIT", "engines": { - "node": "6.* || 8.* || >= 10.*" + "node": ">=22" } }, - "node_modules/github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "node_modules/@csstools/color-helpers": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.1.tgz", + "integrity": "sha512-gLNsunvwf3mCi5u5o46/Z/JcJMnhbHSaZ69rkgPzNM3J4s8hWwpPUQB6/tt0EDFyCiWzxANlx+2LJwpYj4zS1w==", "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=20.19.0" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "node_modules/@csstools/css-calc": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.3.0.tgz", + "integrity": "sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==", + "dev": true, "funding": [ { "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" + "url": "https://github.com/sponsors/csstools" }, { - "type": "consulting", - "url": "https://feross.org/support" + "type": "opencollective", + "url": "https://opencollective.com/csstools" } - ] + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "node_modules/@csstools/css-color-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.2.0.tgz", + "integrity": "sha512-5+5LEmFuY1AjXdYhmgjTJogtQnP1evJ1zrBZGUNZ0thkpwnnmKxcHdAMn/OtFjAb25zA+jKDVYVRl+5G7rjv1A==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", "dependencies": { - "once": "^1.3.0", - "wrappy": "1" + "@csstools/color-helpers": "^6.1.1", + "@csstools/css-calc": "^3.3.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" - }, - "node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" } }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.8.tgz", + "integrity": "sha512-CpMLjAvwQg3BL5S0IeqsZNMH7EQrEWi0kLKOC13ZBF0ZwERiLWlibNPJr8G1kdU3Ms/r2KiNrF81pUh2HwAHdg==", "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } } }, - "node_modules/lazystream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", - "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", "dev": true, - "dependencies": { - "readable-stream": "^2.0.5" - }, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", "engines": { - "node": ">= 0.6.3" + "node": ">=20.19.0" } }, - "node_modules/lazystream/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "node_modules/@emnapi/runtime": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", "dev": true, + "license": "MIT", + "optional": true, "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "tslib": "^2.4.0" } }, - "node_modules/lazystream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/lazystream/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", "dev": true, + "license": "MIT", "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", - "dev": true - }, - "node_modules/lodash.difference": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", - "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", - "dev": true - }, - "node_modules/lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", - "dev": true - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "dev": true - }, - "node_modules/lodash.union": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", - "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", - "dev": true - }, - "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "eslint-visitor-keys": "^3.4.3" + }, "engines": { - "node": ">=10" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, + "license": "Apache-2.0", "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://opencollective.com/eslint" } }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, - "license": "ISC", + "license": "MIT", "engines": { - "node": ">=16 || 14 >=14.17" + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/mkdirp": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", - "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", "dev": true, - "bin": { - "mkdirp": "dist/cjs/src/bin.js" + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" - }, - "node_modules/napi-build-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", - "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" - }, - "node_modules/ncp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", - "integrity": "sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA==", + "node_modules/@eslint/config-helpers": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz", + "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==", "dev": true, - "bin": { - "ncp": "bin/ncp" - } - }, - "node_modules/node-abi": { - "version": "3.51.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.51.0.tgz", - "integrity": "sha512-SQkEP4hmNWjlniS5zdnfIXTk1x7Ome85RDzHlTbBtzE97Gfwz/Ipw4v/Ryk20DWIy3yCNVLVlGKApCnmvYoJbA==", + "license": "Apache-2.0", "dependencies": { - "semver": "^7.3.5" + "@eslint/core": "^1.2.1" }, "engines": { - "node": ">=10" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/node-addon-api": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", - "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "Apache-2.0", "dependencies": { - "wrappy": "1" + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } } }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=8" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "node_modules/@eslint/plugin-kit": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "Apache-2.0", "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" }, "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/prebuild-install": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", - "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", - "dependencies": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^1.0.1", - "node-abi": "^3.3.0", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" + "node_modules/@exodus/bytes": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz", + "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, - "bin": { - "prebuild-install": "bin.js" + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" }, - "engines": { - "node": ">=10" + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } } }, - "node_modules/prebuild-install/node_modules/tar-fs": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", - "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "node_modules/@floating-ui/core": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz", + "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", "license": "MIT", "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/prebuild-install/node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" + "@floating-ui/utils": "^0.2.12" } }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "node_modules/@floating-ui/dom": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz", + "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==", + "license": "MIT", "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "@floating-ui/core": "^1.8.0", + "@floating-ui/utils": "^0.2.12" } }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "node_modules/@floating-ui/react-dom": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.9.tgz", + "integrity": "sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==", + "license": "MIT", "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" + "@floating-ui/dom": "^1.8.0" }, - "bin": { - "rc": "cli.js" + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" } }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } + "node_modules/@floating-ui/utils": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz", + "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", + "license": "MIT" }, - "node_modules/readdir-glob": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", - "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "minimatch": "^5.1.0" + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" } }, - "node_modules/readdir-glob/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "balanced-match": "^1.0.0" + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" } }, - "node_modules/readdir-glob/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, + "license": "Apache-2.0", "engines": { - "node": ">=10" + "node": ">=18.18.0" } }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=0.10.0" + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/rimraf": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.5.tgz", - "integrity": "sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==", + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, - "dependencies": { - "glob": "^10.3.7" - }, - "bin": { - "rimraf": "dist/esm/bin.mjs" - }, + "license": "Apache-2.0", "engines": { - "node": ">=14" + "node": ">=18.18" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/rimraf/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", "dev": true, "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" + "engines": { + "node": ">=18" } }, - "node_modules/rimraf/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.3.tgz", + "integrity": "sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.3.2" } }, - "node_modules/rimraf/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "node_modules/@img/sharp-darwin-x64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.3.tgz", + "integrity": "sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w==", + "cpu": [ + "x64" + ], "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=20.9.0" }, "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "url": "https://opencollective.com/libvips" }, - "engines": { - "node": ">=10" + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.3.2" } }, - "node_modules/semver/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "node_modules/@img/sharp-freebsd-wasm32": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.3.tgz", + "integrity": "sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], "dependencies": { - "yallist": "^4.0.0" + "@img/sharp-wasm32": "0.35.3" }, "engines": { - "node": ">=10" + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/sharp": { - "version": "0.32.6", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.6.tgz", - "integrity": "sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==", - "hasInstallScript": true, - "dependencies": { - "color": "^4.2.3", - "detect-libc": "^2.0.2", - "node-addon-api": "^6.1.0", - "prebuild-install": "^7.1.1", - "semver": "^7.5.4", - "simple-get": "^4.0.1", - "tar-fs": "^3.0.4", - "tunnel-agent": "^0.6.0" - }, - "engines": { - "node": ">=14.15.0" - }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.2.tgz", + "integrity": "sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], "funding": { "url": "https://opencollective.com/libvips" } }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.2.tgz", + "integrity": "sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.2.tgz", + "integrity": "sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ==", + "cpu": [ + "arm" + ], "dev": true, - "engines": { - "node": ">=8" + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.2.tgz", + "integrity": "sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA==", + "cpu": [ + "arm64" + ], "dev": true, - "engines": { - "node": ">=14" - }, + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://opencollective.com/libvips" } }, - "node_modules/simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.2.tgz", + "integrity": "sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.2.tgz", + "integrity": "sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.2.tgz", + "integrity": "sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.2.tgz", + "integrity": "sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.2.tgz", + "integrity": "sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.2.tgz", + "integrity": "sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.3.tgz", + "integrity": "sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.3.2" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.3.tgz", + "integrity": "sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.3.2" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.3.tgz", + "integrity": "sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.3.2" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.3.tgz", + "integrity": "sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.3.2" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.3.tgz", + "integrity": "sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.3.2" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.3.tgz", + "integrity": "sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.3.2" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.3.tgz", + "integrity": "sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.3.2" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.3.tgz", + "integrity": "sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.3.2" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.3.tgz", + "integrity": "sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==", + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.11.1" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-webcontainers-wasm32": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.3.tgz", + "integrity": "sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/sharp-wasm32": "0.35.3" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.3.tgz", + "integrity": "sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.3.tgz", + "integrity": "sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.3.tgz", + "integrity": "sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.144.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.144.0.tgz", + "integrity": "sha512-nuhZIOLuI6TFQ32I/WnUx+SCPY7SdSKwgnFHydAuoS1+Z4BRcaP+RRJmGzl9lw+0OFF7UmaESf7KQRXaNLHypg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.7.tgz", + "integrity": "sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-alert-dialog": { + "version": "1.1.23", + "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.23.tgz", + "integrity": "sha512-VAYOiQRqj3GPpYJE0I9J+X8Ip05cyVlNdKOFeiGS2Ou1HHGfpl0BxOyZm6nmVDyU+W+NF3/XLzmjHmVGydhwgA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dialog": "1.1.23", + "@radix-ui/react-primitive": "2.1.10" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.15.tgz", + "integrity": "sha512-v4zggRcjadnI+ClKDuijlQEW4tw3NoaeHc/PwpKnLoLLKNUG4InLegkstooLcRIUWCs+8L22dGURCVuFfOKfnA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.10" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.5.tgz", + "integrity": "sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.2.2.tgz", + "integrity": "sha512-RHCUGwKHDr0hDGg4X7ma4JG4/+12qxw8rkh5QKdDldlCvtja6nUx1Ef/8HVrJze81lEsgLQlqjzjGNHantgnQA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.23", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.23.tgz", + "integrity": "sha512-Ksw4WeROkO4rC9k/onilX/Ao2Cr1ku1unMNH+XSCcP4jSXYu7HDsg9n4ojMjVb22XpYjAQ9qfrFlVbru1vXDUA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.19.tgz", + "integrity": "sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-effect-event": "0.0.5" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.6.tgz", + "integrity": "sha512-RNOJjfZMTyBM6xYmV3IVGXkPjIhcBAuv48POevAXwrGJhkWZ9p1rFoIS1JFooPuT193AZmRsCPhpoVJxx6OPoQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.16.tgz", + "integrity": "sha512-wmRZ2WWLvmt6KHy2rNPOdPUjwq5xOHY02+m+udwJTn0aNIox/rkskAvJTyTLGhPK6KgrUjlJUJpgmx/+wFiFIQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.4.tgz", + "integrity": "sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.15.tgz", + "integrity": "sha512-o/rdYEwZTTo5tjknnPeyQFU45kUC4i/XyeDPP+HGyi6XqpOP6Zf5Ya5vh/Yfe9Id5JiuWnnAx2XqIeD3UYZt0g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.10" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.3.7.tgz", + "integrity": "sha512-UsJrrd7w4wuKKTdvd/DNERVlwSlUcyXzjhyDwBk+3aPOsCjOY6ZSbxuw8E6lZTjjfP8Cpd0J8VVkrYUWyGYXyg==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-use-rect": "1.1.4", + "@radix-ui/react-use-size": "1.1.4", + "@radix-ui/rect": "1.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.17.tgz", + "integrity": "sha512-vKQLcWypUnwZVvfV7UkGahH2g6ySe8M8R+zYBwPrv5byZ9QAW6cQVvNKo7GgmD+p8aYb6D9JBuvy8/WhOno2wQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.10.tgz", + "integrity": "sha512-3wyzCQ6+ubRA+D4uv9m95JYLXxmOHp05qjrkjeA7uKHHtjpPggQzc6DAb0URl7j67oR0K2foO4ip27TiX037Bw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.10.tgz", + "integrity": "sha512-MucOnzh6hR5mid6VpkbglRAMYMjKLqRnGBbjXkzjK52fuQDd1qbkx78a5P40mkcnVXJdEVxm26E9OPAiUq7nBg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.3.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.3.tgz", + "integrity": "sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.5" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.3.7.tgz", + "integrity": "sha512-48tB/4dn2UVLBCYhTu9AuR63IHl73l/qLbLgxd86noTUor4/K4LFDAcYjK+isP5313qxaFpjPVogE7+Y0/V3Kw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-size": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.16.tgz", + "integrity": "sha512-6EamKFRRnlpdadndbZ6LMwycfwkwPte1B42hs6QA0gYhjaOKqW4PZ4pjaW9UrlDX5eVt/OjncE7BFTPL5nmZhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-visually-hidden": "1.2.11" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.4.tgz", + "integrity": "sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.6.tgz", + "integrity": "sha512-uEQJGT97ZA/TgP/Hydw47lHu+/vQj6z/0jA+WeTbK1o9Rx45GImjpD0tc3W5ad3D6XTSR6e1yEO0FvGq6WQfVQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-use-effect-event": "0.0.5", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.5.tgz", + "integrity": "sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.4.tgz", + "integrity": "sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.4.tgz", + "integrity": "sha512-cSOCh6JlkmfjLyNcLiu2nB4v+nm+dkZ+Q5KHWk/soo4U7ZLiEQFKHK9/YmtBHjfCEaU43IBKQOc4/uJmCaiCTQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.4.tgz", + "integrity": "sha512-D3anSY15EJoxrihpsXI6SMrmmonnQtR2ni7arO+Lfdg3O95b9hNXxONk8jA5C8ANdF/h5HMAxejgs8PWJ6rlhw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.11.tgz", + "integrity": "sha512-NFS86RYYZb4/exihaESBGOpMJFz8MGLAfu3mOBSGByVnVPC9JPASfYubxd/8KbkQK0sYAv8lVQDEQukDX/qXvQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.10" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.3.tgz", + "integrity": "sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw==", + "license": "MIT" + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.4.tgz", + "integrity": "sha512-jHC2cnyKz5xU2fhECtFl8OZ83cYNt13GZQD+0uMJ/X3o+ijmd56okHhTUwxVSHPx1IRVIJEZ1/1pPzeLCU6XKA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-Dc5mPD8F5F/FS8i01syd7FTF6yB2fVthH/TRkjwJkzUK6EpoxHtqvZQP5Zwq80/5z19TWYHIg1KOHboCgVx/aQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.4.tgz", + "integrity": "sha512-fpDm4oBo6SqLvWUYCmFhdde3U9KH2fRNNMeAnAPAIwxRL345xutL0EtEUcuoxsoazdJGv/MuDBQHlCDrtbvqOg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.4.tgz", + "integrity": "sha512-rSJoreDE/HoIzoaib6MTp5jQtCTdMHKIvItAKT/ImS6Y6Ww76oUaeMyp4Vc/fAgd/ehji068IxetHXAnqUwN9A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.4.tgz", + "integrity": "sha512-/jm8OGHgn7oGaJu3i/qZI9spUGcJ+y/lk43ttQ/iO1tOd9NissG6o97bighBCiL+BKRngmcDuR6ikfwYdJmVuQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.4.tgz", + "integrity": "sha512-tIP06BeD9EqvECBrPZ+sqdPlYrT+aYaAiu1wYziVx5elRK/ftm33JxVDy2bXGbr6J0CrtirCkR87/X5a2euEng==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.4.tgz", + "integrity": "sha512-Ql1Q0EQqVThvn9VAVlwNzsUvbSFtCMGjLpRRi4pk5i7NZZ4n5ISiLMjHYtus4VQ2PvkSw24zyaCVsiS+sXPj1w==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.4.tgz", + "integrity": "sha512-GjbjXD4XXfN19D0LZNbmiCBUoDiRACsYHr0yaIbbn8aFsXjHZifcYqu/W5Er5X2X990WjHXFrxarn5chzItorQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.4.tgz", + "integrity": "sha512-p5WR0NOwaRmJ/B1b6IjEFLLivwEsf3PrdBIhRbhTCQisbo2SvHHpG4ELB/+FgQNnB88LTOF86upmJmbvZdQ2lw==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.4.tgz", + "integrity": "sha512-4/GyVjmhR+Tc6HLJvwc1sOhPqAZtySiSMesOZyX6JQ5XBxoTDEMKQzvo07NIK6nTon/SivlZqvhzvuVBNQhObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.4.tgz", + "integrity": "sha512-l9eeLsCNvPpmSXUej0etw/J1eqV0Jj1D5G/xG6YTijmE6dkv6E2QezgWbTfQk63v952DPqrjOCoiqxq7Bw0YUQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.4.tgz", + "integrity": "sha512-e0F355MSTMm3+UOqtV3L24gFUp2N5m1f8L/7d56deik6va+AXdrt9F8LbzGpeWGWRbZEDq4m8NVnJDeBtf9DZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.4.tgz", + "integrity": "sha512-AWLi0uBRYh6QlE7OKhiz+phZC0qwtij2QZmhmOdsLdFn64m7oMpooE9ICE3lhm9xMb4SpDo2WbHcxX1iFLFtqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.4.tgz", + "integrity": "sha512-UwSDJOg3dqCAejWdxclJjCsh3Qq4vLYMDxmyHqo1btz3stK2VqgwNd3mm5tuIwzSlGIQ/1H9Hr+Zn09mrezNqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@simple-libs/child-process-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@simple-libs/child-process-utils/-/child-process-utils-2.0.0.tgz", + "integrity": "sha512-dvNoRKLijXnD0XoJAz94pbNuB5GQgDr55UhpSPhffDkTT0Cmcqh9jSCOtwfT2d4H6MI9E7c4SgtMuJXZ6F3c6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@simple-libs/stream-utils": "^2.0.0" + }, + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://ko-fi.com/dangreen" + } + }, + "node_modules/@simple-libs/stream-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@simple-libs/stream-utils/-/stream-utils-2.0.0.tgz", + "integrity": "sha512-fCTuZK4QBa+39Oz9l4OGfJfz+GpwCp3AqO7Zch3to99xHPgstVsRFpeQ8LNd2o1Gv8raL2mCFwiaHh7bFSp5DQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://ko-fi.com/dangreen" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz", + "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.24.1", + "jiti": "^2.7.0", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.3" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz", + "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-x64": "4.3.3", + "@tailwindcss/oxide-freebsd-x64": "4.3.3", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-x64-musl": "4.3.3", + "@tailwindcss/oxide-wasm32-wasi": "4.3.3", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz", + "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz", + "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz", + "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz", + "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz", + "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz", + "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz", + "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz", + "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz", + "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz", + "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.11.1", + "@emnapi/runtime": "^1.11.1", + "@emnapi/wasi-threads": "^1.2.2", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", + "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz", + "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.3.tgz", + "integrity": "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.3.3", + "@tailwindcss/oxide": "4.3.3", + "tailwindcss": "4.3.3" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } + }, + "node_modules/@topcli/prompts": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@topcli/prompts/-/prompts-4.0.0.tgz", + "integrity": "sha512-kkKYPb4k/6kRdnESt77B5vJrrYnYHIYczwv0P1c+tfN/IWEx3KZeKEHoUq7sImIyBEXWdA2uakIroD9pmRJziQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/filesystem": { + "version": "0.0.36", + "resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.36.tgz", + "integrity": "sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/filewriter": "*" + } + }, + "node_modules/@types/filewriter": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.33.tgz", + "integrity": "sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/har-format": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.16.tgz", + "integrity": "sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "26.2.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.2.0.tgz", + "integrity": "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~8.3.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.67.0.tgz", + "integrity": "sha512-Un7Heoyj65NREbKAyIrFxeM143NZpExWmy1Nep4DLeQOeLlTeumPjoNKnBrU5D5moWXbPJgRa5Uwcdu0faVNGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.67.0", + "@typescript-eslint/type-utils": "8.67.0", + "@typescript-eslint/utils": "8.67.0", + "@typescript-eslint/visitor-keys": "8.67.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.67.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.67.0.tgz", + "integrity": "sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.67.0", + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/typescript-estree": "8.67.0", + "@typescript-eslint/visitor-keys": "8.67.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.67.0.tgz", + "integrity": "sha512-cvE8c7ulYeXN9fYuszhCeCsbzyVEXuhrRCybnBre7TUmqb5nRmBfQAwCj0O3WJFDeyAZt4VYv51vMCC9LHSdYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.67.0", + "@typescript-eslint/types": "^8.67.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.67.0.tgz", + "integrity": "sha512-EgvsleTwS4E+WzzSvem8fAUubLwatMNF1B5hHSLQxcvs7q2dtRhGyujHwLJSYlG41niJ7GP24Aha2+0mb1b2kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/visitor-keys": "8.67.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.67.0.tgz", + "integrity": "sha512-vV+LUSv5njUWsknE71fqKTlXUva+R76SaeORd6Zojcunk/6DvKFXONU3BrAs2H49mbygUXt6gbYunzwqNwlhdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.67.0.tgz", + "integrity": "sha512-aVWDXbRmdXO9siTfX4ditQI1T9+zVcNazT48EJCD0v40/9RIFoUgZ05CmGEq9H2gixRpjUn/iplwvlcvutJW/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/typescript-estree": "8.67.0", + "@typescript-eslint/utils": "8.67.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.67.0.tgz", + "integrity": "sha512-sBtgslww8nsMYUjhdPBiSyUqSzT8uR6g93A2QXnQC8+cGdjz0CyaOdqHDRJb1AtORbZCNUJBBeFA/tNR2uQmww==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.67.0.tgz", + "integrity": "sha512-EKQBCE9yNlRJYm7jdTW5AhDacDUmSwQb0FAJAmK2EKYrNXIsa2vxcSZx6PvJ/dEdI6lS+Y9W+EXckLj0iPFGcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.67.0", + "@typescript-eslint/tsconfig-utils": "8.67.0", + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/visitor-keys": "8.67.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.67.0.tgz", + "integrity": "sha512-U9D1FdwEWBwok3hxxSdhclMb0twvt9QnjIQ0VfQ1AiX2epnpSgv2ubVDsayOFyY8K6FX+AQ7E0FKWVG3iKsj1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.67.0", + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/typescript-estree": "8.67.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.67.0.tgz", + "integrity": "sha512-fkv8dHRDqfGtTHuJeebdrQ7cX6Ad4WAS00rgHh9UGvMycF1mjBfsxry1XsLIFhWZ6Judlh6UdzK+TYlbpCXgnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.67.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.5.tgz", + "integrity": "sha512-BOVzne/NL162sMdResB25mUv+vWMF5NoAjNf09TeGlE7ZpszZWSD3winycicLJw72yeVsoCn/2kOhEuCvEShMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } + } + }, + "node_modules/@vitest/coverage-v8": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.10.tgz", + "integrity": "sha512-IM49HmthevbgAO4anp1hwtoT9wYe59w0LR00gr+eagHE+ZJ5lK4sLPeO0ubgoJcwLk6dehU3R24N+FbEEKDc8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.1.10", + "ast-v8-to-istanbul": "^1.0.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.2", + "obug": "^2.1.1", + "std-env": "^4.0.0-rc.1", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.1.10", + "vitest": "4.1.10" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", + "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", + "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.10", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", + "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", + "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.10", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", + "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.10", + "@vitest/utils": "4.1.10", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", + "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", + "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.10", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@webext-core/fake-browser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webext-core/fake-browser/-/fake-browser-2.0.1.tgz", + "integrity": "sha512-4x5z1z8F0KU8ShF4ForXJ8qnA8oZTy7HYjI91Mbpdzp3H3hU9HR6TNPUxfWtSpFz2FwgEircuJKQg0qFIn6RLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@wxt-dev/browser": "*", + "lodash.merge": "^4.6.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@webext-core/isolated-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@webext-core/isolated-element/-/isolated-element-3.0.0.tgz", + "integrity": "sha512-PmSIBMSe+rFw6eXzpJoV+glc1HPO40clcT9FJ7rSfCQzNewK8/nQZoBYWen2cMqdXdBNM9to/IbNNu5mdQLaPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-potential-custom-element-name": "^1.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@webext-core/match-patterns": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@webext-core/match-patterns/-/match-patterns-2.0.0.tgz", + "integrity": "sha512-EsyMMKfQuSE4rWCjP/TifwvUWlhC/OhiGn5OK3p9F0d57UmtLib0nzguzPKyxM1/kZV7SkE+jYXkE8gsdPRLmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/@wxt-dev/auto-icons": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@wxt-dev/auto-icons/-/auto-icons-1.1.2.tgz", + "integrity": "sha512-tLBTgvAAPj5n9VTrNVgbkqSvcuKTZjppgrpGq6YnZ+oOj69bqOWQdFRMhHutTcs4tvIvhgJ80LmhwQXhUyyTxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "defu": "^6.1.4", + "sharp": "^0.35.3" + }, + "peerDependencies": { + "wxt": ">=0.19.0" + } + }, + "node_modules/@wxt-dev/browser": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@wxt-dev/browser/-/browser-0.2.6.tgz", + "integrity": "sha512-rQMn4gr36988e3RYVxsr0Qvy6A/9bIcA0meQvGtfQcEPmRJ73SU3xkHh6eX2yjv/j1QkMLacdzES/jf9xqz5/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/filesystem": "*", + "@types/har-format": "*" + } + }, + "node_modules/@wxt-dev/module-react": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@wxt-dev/module-react/-/module-react-1.2.2.tgz", + "integrity": "sha512-+lRLi1r9dAXpLySWSIWHLJ1h/nFzR20iQnx3RNrKyA6oJg4+ClOluVXozHjfPg9Okfy/umtffiOopGayASrg6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitejs/plugin-react": "^4.4.1 || ^5.0.0 || ^6.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/wxt-dev" + }, + "peerDependencies": { + "vite": "^5.4.19 || ^6.3.4 || ^7.0.0 || ^8.0.0-0", + "wxt": ">=0.19.16" + } + }, + "node_modules/@wxt-dev/storage": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@wxt-dev/storage/-/storage-1.2.9.tgz", + "integrity": "sha512-dh9TJwvLBM2x4wyy9nE7eYE3wA8pgz1TXSyz7RbdjkJxLfFfWkbeIqiU7VkGy7Vx8sJBmQjoSpk9zSUb3Bjy0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@wxt-dev/browser": ">=0.1", + "superlock": "^1.3.2" + }, + "funding": { + "url": "https://github.com/sponsors/wxt-dev" + } + }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/argue-cli": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/argue-cli/-/argue-cli-3.1.0.tgz", + "integrity": "sha512-DhBpBfXL4SS2uC0N922MMajKR3CdrTG0u2or1PNYgXMsrSzViJrbtvT0nCLlLGUI0plam/ZZCs7aAauHtW9thw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://ko-fi.com/dangreen" + } + }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-v8-to-istanbul": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.5.tgz", + "integrity": "sha512-UPAgKJFSEGMWSDr3LX4tqnAb4f7KGT8O40Tyx8wbYmmZ/yn58lNCm8h3svs3eXgiGd5AXxz8NDOvXWvicq+rJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.14", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.14.tgz", + "integrity": "sha512-JyJ954WzuIR8/FFzX0o5krdSTrBAkcCSRfWSleRsIHSWV+cZe2FI1PKggVkFke1hBldRs+LRxUczzE9iPmgZww==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/boolbase": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-2.0.0.tgz", + "integrity": "sha512-DkVaaQHymRhpYEYo9x1oo7Q7B0Y6KJUsjm3c9eTyFDby4MHLBTwZ6ZDWBel5zrYxj1WsZgC5oLpiz+93MluXeA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/browserslist": { + "version": "4.28.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", + "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.12", + "caniuse-lite": "^1.0.30001809", + "electron-to-chromium": "^1.5.402", + "node-releases": "^2.0.53", + "update-browserslist-db": "^1.3.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/c12": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/c12/-/c12-3.3.4.tgz", + "integrity": "sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^5.0.0", + "confbox": "^0.2.4", + "defu": "^6.1.6", + "dotenv": "^17.3.1", + "exsolve": "^1.0.8", + "giget": "^3.2.0", + "jiti": "^2.6.1", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "perfect-debounce": "^2.1.0", + "pkg-types": "^2.3.0", + "rc9": "^3.0.1" + }, + "peerDependencies": { + "magicast": "*" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } + } + }, + "node_modules/cac": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cac/-/cac-7.0.0.tgz", + "integrity": "sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001809", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz", + "integrity": "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/citty": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.2.2.tgz", + "integrity": "sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w==", + "dev": true, + "license": "MIT" + }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/confbox": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/conventional-changelog-angular": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-9.3.0.tgz", + "integrity": "sha512-0MWQLVUT1oVCsUGs9aAWteBVxPlLwJTn5VbQH7B0B3fDizZgrJ9QGnKl/2mp1+5P7153GCBCjO/v1aKJ6eysCg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@conventional-changelog/template": "^1.3.0" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/conventional-changelog-conventionalcommits": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-10.3.0.tgz", + "integrity": "sha512-qag0zFD867Qq1DK0jAWicyWlEMS1FFC/BLVLISaoeI7Y6Em6aWchk7BCkhOTsecRpMsV6qX41XmlNnghiiTmSw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@conventional-changelog/template": "^1.3.0" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/conventional-commits-parser": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-7.1.2.tgz", + "integrity": "sha512-O+x4N2yH+ijvqWlIyTHsXTAP+algNWgGbjY2duCe8w2vUMvUB95cLRslCPfTMQyLAKlet3bhZTdu6ozn4M+QJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@simple-libs/stream-utils": "^2.0.0", + "argue-cli": "^3.1.0" + }, + "bin": { + "conventional-commits-parser": "dist/cli/index.js" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.2.tgz", + "integrity": "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cosmiconfig-typescript-loader": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-6.3.0.tgz", + "integrity": "sha512-Akr82WH1Wfqatyiqpj8HDkO2o2KmJRu1FhKfSNJP3K4IdXwHfEyL7MOb62i1AGQVLtIQM+iCE9CGOtrfhR+mmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jiti": "2.6.1" + }, + "engines": { + "node": ">=v18" + }, + "peerDependencies": { + "@types/node": "*", + "cosmiconfig": ">=9", + "typescript": ">=5" + } + }, + "node_modules/cosmiconfig-typescript-loader/node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crx3": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crx3/-/crx3-2.0.0.tgz", + "integrity": "sha512-f23Oi2Zpl68aBSf5gHwn+lxQyPF+m2NAhMwwycXOxqOx6bpzDqzbcp6k/DRsyHxpsDvg5WwXcHOJSOgJ7Px5LQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mri": "^1.2.0", + "pbf": "^4.0.1", + "yazl": "^3.3.1" + }, + "bin": { + "crx3": "bin/crx3.js" + }, + "engines": { + "bun": ">=1.2.18", + "node": ">=22" + } + }, + "node_modules/css-select": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-7.0.0.tgz", + "integrity": "sha512-snmjEVXy+1LnwXdxhYvTMj1d9tOh4HxkA1YmoayVBeeyR2C14Pum7fcxJIm4SswYspVy866eYNwlH6xC3/VH5g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^2.0.0", + "css-what": "^8.0.0", + "domhandler": "^6.0.1", + "domutils": "^4.0.2", + "nth-check": "^3.0.1" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css-what": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-8.0.0.tgz", + "integrity": "sha512-DH0Bqq3DNp5tdOReuNyAA+Ev4Y2GS5FMbZpeTLP6C4CDi0h5nL0BmUPChXw3o/qbHLDWHl49sbNqQVY7bMSDdw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, + "node_modules/dom-serializer": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-3.1.1.tgz", + "integrity": "sha512-4MEa38/QexBob6gFNwu+EGdWvhJ1OKuNwdYY3Y3NyeWDQfnGeDYQUDfIRzWu5B5gsv03so2Uxd28YC6zrsx3Lw==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^3.0.0", + "domhandler": "^6.0.0", + "entities": "^8.0.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-3.0.0.tgz", + "integrity": "sha512-umCQid3jKbDmVjx8jGaW7uUykm4DEUeyV21hPxNMo2nV955DhUThwqyOIDtreepP31hl84X7G5U9ZfsWvIB3Pg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/domhandler": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-6.0.1.tgz", + "integrity": "sha512-gYzvtM72ZtxQO0T048kd6HWSbbGCNOUwcnfQ01cqIJ4X2IYKFFHZ5mKvrQETcFXxsRObZulDaKmy//R7TPtsBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^3.0.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-4.0.2.tgz", + "integrity": "sha512-qI4JLRKnSzqFqr7hAlS5xQDusBCjKSEG4t4+7aNrIQMHBcsC2TGEhuyABJdYkgSewL57PNLYEiibY2iPKhKpaA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^3.0.0", + "domelementtype": "^3.0.0", + "domhandler": "^6.0.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-13.0.0.tgz", + "integrity": "sha512-aBfBS8eYIeXmpHI9ThIlA7/WLq+SLt18iXUZhb52rW89QLKQFoIpPG1bPeewoPZsTyjSSO3T7234FBVUM1V2rA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dotenv": "^17.4.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.408", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.408.tgz", + "integrity": "sha512-SLoprcYpJ/OH2v2ps0+N5biv9H4/KBT3+YmmDew64TwK5y9j2wv7pMOFY7IorVkyMtEyLSCRlXKLsNlakeAlPw==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.24.5", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz", + "integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-module-lexer": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-toolkit": { + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.51.0.tgz", + "integrity": "sha512-zC2lQGkM7QX+Gm6iM3+WIdZJzthsEd14LvRNJneSO2hzyz/zNBENR8+YXWo1cKxgPBtV6ksPYHELbcwBRzmdCw==", + "dev": true, + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks", + "tests/types", + "tests/browser-compat" + ] + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "10.8.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.8.1.tgz", + "integrity": "sha512-wqA7W2jbsC/BnV9Iv1UZpKVFkO1AdNoSmYW8NWG4HNOBbkAMvIqDZ27pI2f07dqn583NcIC44ckjAcOXDL1QbQ==", + "dev": true, + "license": "MIT", + "workspaces": [ + "packages/*" + ], + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.7.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.2", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.4.tgz", + "integrity": "sha512-7bqTKz7T0r+HKWFarNXByDE9/5+73wI2ru+M3zuqGbR7s/b/5/pQJXZoufWlrngqGqoZto73ZkGumCdLxk+4rw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": "^9 || ^10" + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/exsolve": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.1.1.tgz", + "integrity": "sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", + "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/filesize": { + "version": "11.0.22", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-11.0.22.tgz", + "integrity": "sha512-RlCVs9CY+oSsRnNZn95J9vDXjNjOwddKyTFjOYtA4yxYVIxBnwiVVGJX+TFhsmu3uUf81JDGyijtYL9xgawlTw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 10.8.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/giget": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/giget/-/giget-3.3.1.tgz", + "integrity": "sha512-r+mvuDjrjMpsdw46Kmeydb8bdHm7wOKw8wNBtTndkjbPjgAp5oUJUxRE76wZFknxIPokfWvep2qSXK37aXE6zg==", + "dev": true, + "license": "MIT", + "bin": { + "giget": "dist/cli.mjs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/global-directory": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-5.0.0.tgz", + "integrity": "sha512-1pgFdhK3J2LeM+dVf2Pd424yHx2ou338lC0ErNP2hPx4j8eW1Sp0XqSjNxtk6Tc4Kr5wlWtSvz8cn2yb7/SG/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "6.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globals": { + "version": "17.11.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.11.0.tgz", + "integrity": "sha512-Z2I8hM+PbJDXQDq3Icgpzv+mPdwr68iZUU9d5WW4FuXfDUQfkZaZuvjMv42/5crNyw154+9+VWXbYrUgDXbxNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/hookable": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-6.1.1.tgz", + "integrity": "sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/html-escaper": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", + "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/htmlparser2": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "entities": "^7.0.1" + } + }, + "node_modules/htmlparser2/node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/htmlparser2/node_modules/dom-serializer/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/htmlparser2/node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/htmlparser2/node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/htmlparser2/node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/ini": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz", + "integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-reports/node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "29.1.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz", + "integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^5.1.11", + "@asamuzakjp/dom-selector": "^7.1.1", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.3", + "@exodus/bytes": "^1.15.0", + "css-tree": "^3.2.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.3.5", + "parse5": "^8.0.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.1", + "undici": "^7.25.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.1", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/linkedom": { + "version": "0.18.13", + "resolved": "https://registry.npmjs.org/linkedom/-/linkedom-0.18.13.tgz", + "integrity": "sha512-ES/o9qotMpzpN2MHs+Iq/JcVoOj8Fa5wiQYrTdFpvAnwXL0g66XHHUc9WUMk6nAlBtGsFQ24ne+SYnvnaQ2FSw==", + "dev": true, + "license": "ISC", + "dependencies": { + "css-select": "^7.0.0", + "cssom": "^0.5.0", + "html-escaper": "^3.0.3", + "htmlparser2": "^10.1.0", + "uhyphen": "^0.2.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "canvas": ">= 2" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/lint-staged": { + "version": "17.3.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-17.3.0.tgz", + "integrity": "sha512-woZS3vNe3UKqBaLPvbLOtKRY4tLANpWQhom12MGWqC8Mh1lCOO+WgSwmX2amjJAqTY9BkXYW87fCUH5H9Ph6xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^4.0.5", + "string-argv": "^0.3.2", + "tinyexec": "^1.2.4" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=22.22.1" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + }, + "optionalDependencies": { + "yaml": "^2.9.0" + } + }, + "node_modules/local-pkg": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.2.1.tgz", + "integrity": "sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "1.31.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.31.0.tgz", + "integrity": "sha512-G8u2eEtoHUnUa9f8lbvqDhCiORMnYLdUEo06EEG9MQvHQrInKcX3Pa2TH39MM5qyzRcWETxB0+aOwAPI1g1kEg==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.4.tgz", + "integrity": "sha512-llBEhWm1SacoRwgHUoQJYtwp4PBLF4faQi5TCpIGyGs9n4y5+juI0tDgyKIfpqxckRHaHzouUEph3THklWh03w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "source-map-js": "^1.2.1" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/many-keys-map": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/many-keys-map/-/many-keys-map-3.0.3.tgz", + "integrity": "sha512-1DiZmDHPXMBgMRjeUtHy1q1VYmeJscHxhIAexX9z/zjRMP80+0ETuPfssi8z+kMY4DwUgsKuHqpjxgmeA9gBNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/fregante" + } + }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, + "node_modules/mlly/node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mlly/node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.53", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.53.tgz", + "integrity": "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/nth-check": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-3.0.1.tgz", + "integrity": "sha512-GX0gsdbGVCgnRgbeGaubfjpBXyYRWOOCVeYh08bSQvDZqxz5ndXs1OTfAt/h36G1xvI94YIspsI0sVFqAV9+RQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^2.0.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/nypm": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.9.tgz", + "integrity": "sha512-zxlE2yvSWZWmHcNdT3+5zV2lrCogeE9YOklHrR3dFjqutq5wO7GFDYLFDRXLsYnJzwvy/im9fYoxePvS0VTW0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "citty": "^0.2.2", + "pathe": "^2.0.3", + "tinyexec": "^1.2.4" + }, + "bin": { + "nypm": "dist/cli.mjs" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/obug": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", + "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/ohash": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.12.tgz", + "integrity": "sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw==", + "dev": true, + "license": "MIT" + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pbf": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/pbf/-/pbf-4.0.2.tgz", + "integrity": "sha512-J0ajxARhZfpUEebxYs1vhMGMuLSXtBe1e+fFPDrf2uA2hgo+UshKfNUWOz92HJNz6/NFEXseQPddnHkTreWRqg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "resolve-protobuf-schema": "^2.1.0" + }, + "bin": { + "pbf": "bin/pbf" + } + }, + "node_modules/perfect-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", + "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-types": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.1.tgz", + "integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.4", + "exsolve": "^1.0.8", + "pathe": "^2.0.3" + } + }, + "node_modules/postcss": { + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.17", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.9.6", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz", + "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/protocol-buffers-schema": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.1.tgz", + "integrity": "sha512-VG2K63Igkiv9p76tk1lilczEK1cT+kCjKtkdhw1dQZV3k3IXJbd3o6Ho8b9zJZaHSnT2hKe4I+ObmX9w6m5SmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/publish-browser-extension": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/publish-browser-extension/-/publish-browser-extension-6.1.1.tgz", + "integrity": "sha512-ovfBOz+cZIOBHZ+oxTfpv1kSGU7ruzy8CXoFGvhXsUbJuty45srGzf1ufEePs69virOWXfFbhZRoNQUdgPvwXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@topcli/prompts": "^4.0.0", + "cac": "^7.0.0", + "tasuku": "^2.3.0" + }, + "bin": { + "publish-extension": "bin/publish-extension.mjs" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "dev": true, + "funding": [ { - "type": "consulting", - "url": "https://feross.org/support" + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, + "node_modules/rc9": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-3.0.1.tgz", + "integrity": "sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "defu": "^6.1.6", + "destr": "^2.0.5" + } + }, + "node_modules/react": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.8" + } + }, + "node_modules/react-remove-scroll": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/readdirp": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz", + "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-protobuf-schema": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz", + "integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "protocol-buffers-schema": "^3.3.1" + } + }, + "node_modules/rolldown": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.4.tgz", + "integrity": "sha512-rSr7irW0K7QRWzjdJXqZowkcRdDtjRduh43rBltnVKd0VFq839l1lJoDvGJb6gl7+4rTTCrPWu+YfujUL8Ug7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.144.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.2.4", + "@rolldown/binding-darwin-arm64": "1.2.4", + "@rolldown/binding-darwin-x64": "1.2.4", + "@rolldown/binding-freebsd-x64": "1.2.4", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.4", + "@rolldown/binding-linux-arm64-gnu": "1.2.4", + "@rolldown/binding-linux-arm64-musl": "1.2.4", + "@rolldown/binding-linux-ppc64-gnu": "1.2.4", + "@rolldown/binding-linux-s390x-gnu": "1.2.4", + "@rolldown/binding-linux-x64-gnu": "1.2.4", + "@rolldown/binding-linux-x64-musl": "1.2.4", + "@rolldown/binding-openharmony-arm64": "1.2.4", + "@rolldown/binding-win32-arm64-msvc": "1.2.4", + "@rolldown/binding-win32-x64-msvc": "1.2.4" + } + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/scule": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz", + "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.3.tgz", + "integrity": "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@img/colour": "^1.1.0", + "detect-libc": "^2.1.2", + "semver": "^7.8.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.35.3", + "@img/sharp-darwin-x64": "0.35.3", + "@img/sharp-freebsd-wasm32": "0.35.3", + "@img/sharp-libvips-darwin-arm64": "1.3.2", + "@img/sharp-libvips-darwin-x64": "1.3.2", + "@img/sharp-libvips-linux-arm": "1.3.2", + "@img/sharp-libvips-linux-arm64": "1.3.2", + "@img/sharp-libvips-linux-ppc64": "1.3.2", + "@img/sharp-libvips-linux-riscv64": "1.3.2", + "@img/sharp-libvips-linux-s390x": "1.3.2", + "@img/sharp-libvips-linux-x64": "1.3.2", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.2", + "@img/sharp-libvips-linuxmusl-x64": "1.3.2", + "@img/sharp-linux-arm": "0.35.3", + "@img/sharp-linux-arm64": "0.35.3", + "@img/sharp-linux-ppc64": "0.35.3", + "@img/sharp-linux-riscv64": "0.35.3", + "@img/sharp-linux-s390x": "0.35.3", + "@img/sharp-linux-x64": "0.35.3", + "@img/sharp-linuxmusl-arm64": "0.35.3", + "@img/sharp-linuxmusl-x64": "0.35.3", + "@img/sharp-webcontainers-wasm32": "0.35.3", + "@img/sharp-win32-arm64": "0.35.3", + "@img/sharp-win32-ia32": "0.35.3", + "@img/sharp-win32-x64": "0.35.3" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/simple-git-hooks": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/simple-git-hooks/-/simple-git-hooks-2.13.1.tgz", + "integrity": "sha512-WszCLXwT4h2k1ufIXAgsbiTOazqqevFCIncOuUBZJ91DdvWcC5+OFkluWRQPrcuSYd8fjq+o2y1QfWqYMoAToQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "simple-git-hooks": "cli.js" + } + }, + "node_modules/sonner": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.8.tgz", + "integrity": "sha512-UM/ByIoFra8yzV75n1o0Puu0bw5U/9UNnDacrJNspekBewIfsQ3D6ez1nvlWpt7aTsO6rujQtifBpycwIivqlg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", + "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-width": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz", + "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-literal": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-4.0.0.tgz", + "integrity": "sha512-PaqAvfUZKBwc/SLmNZtHmzK+v19Z4O4eS3cKPeGvbIv/U3pnyEq4Tuw3/4v/FwfM8VQaEawsyCcOQ0P+kpwWWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^10.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/superlock": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/superlock/-/superlock-1.3.5.tgz", + "integrity": "sha512-XpWNthvezZnWp0u7/UL8rBbBOnq2Qx39fw+0RNMC/+eotOd81glzsmIWVH0ejarhTeDQihxOKSbjm2XXFo5a8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tailwind-merge": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.6.0.tgz", + "integrity": "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz", + "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tasuku": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tasuku/-/tasuku-2.3.0.tgz", + "integrity": "sha512-9Jtk+XAnttslCw4i9RceSZGr2PT5TLn0vUyWnoP2SdlSYzkiYRY6kB5qnPi5IQ/Em8e4oBow1rpaA39iijTIrA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/tasuku?sponsor=1" + } + }, + "node_modules/tiny-open": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tiny-open/-/tiny-open-1.3.0.tgz", + "integrity": "sha512-GUFS8yjJZq0oWqCKCJVHcBgMpmi2WEGXY1le3E5ncR0DsgTII5uUyxtfk8/vGyDDGBE42UYHR6Ocvlk8mkXSRg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", + "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz", + "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "7.4.10", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.10.tgz", + "integrity": "sha512-GgouD1B+sWwvkaEq8vXC15DjQitxbvs12oIXELpconwm+Tg3zfcEv4jgzq3vtKverDXsg3VI8aRgNL2Nra0Iog==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.4.10" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.4.10", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.10.tgz", + "integrity": "sha512-KnQjp53ZekKgm/r3l+u8kJGGzYgrWdP8+Mql7a4vijh2WE0IrZWspQj/TpTxDho/YxO+AnOZnIjQcCD+q6iJsw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tough-cookie": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz", + "integrity": "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.67.0.tgz", + "integrity": "sha512-S2udFs8tCKEKffuJ4TB1idGUZiXdCPGi3IPBGWXarbLQ5UPXORV8QEVzJ4gCRduURMb5EkpNCdjbk0eDIuI8Yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.67.0", + "@typescript-eslint/parser": "8.67.0", + "@typescript-eslint/typescript-estree": "8.67.0", + "@typescript-eslint/utils": "8.67.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/uhyphen": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/uhyphen/-/uhyphen-0.2.0.tgz", + "integrity": "sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA==", + "dev": true, + "license": "ISC" + }, + "node_modules/undici": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz", + "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/unimport": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/unimport/-/unimport-6.4.0.tgz", + "integrity": "sha512-JJOOuNMFq8b4ZPBKwQUxEcba4MplskDzYI1Lvrf8rJfWphZTWvPNXWa493qsPngHUmub89w6C7j+SeLWTE/UIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.18.0", + "escape-string-regexp": "^5.0.0", + "estree-walker": "^3.0.3", + "local-pkg": "^1.2.1", + "magic-string": "^1.1.0", + "mlly": "^1.8.2", + "pathe": "^2.0.3", + "picomatch": "^4.0.5", + "pkg-types": "^2.3.1", + "scule": "^1.3.0", + "strip-literal": "^4.0.0", + "tinyglobby": "^0.2.17", + "unplugin": "^3.3.0", + "unplugin-utils": "^0.3.2" + }, + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "oxc-parser": "*", + "rolldown": "^1.0.0" + }, + "peerDependenciesMeta": { + "oxc-parser": { + "optional": true + }, + "rolldown": { + "optional": true } - ] + } }, - "node_modules/simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "node_modules/unimport/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unimport/node_modules/magic-string": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.2.0.tgz", + "integrity": "sha512-ptco+HFxTLgjafSLim2LojBSwfg5feBjd+SqyiwdGkzC38UPdZy3zgrHMI2CoTf5fJL38tbHMYWVzIH8BxGqJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/unplugin": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.3.0.tgz", + "integrity": "sha512-qa66K+crbfyE6JK10GjvbJeRrOsuC/JpbnHctfyp/i4oBTxWOzJfRZyDiOk1PtErMFRu8JhsU/wPvOdBNWe5Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "picomatch": "^4.0.4", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@farmfe/core": "*", + "@rspack/core": "*", + "bun-types-no-globals": "*", + "esbuild": "*", + "rolldown": "*", + "rollup": "*", + "unloader": "*", + "vite": "*", + "webpack": "*" + }, + "peerDependenciesMeta": { + "@farmfe/core": { + "optional": true + }, + "@rspack/core": { + "optional": true + }, + "bun-types-no-globals": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "rolldown": { + "optional": true + }, + "rollup": { + "optional": true + }, + "unloader": { + "optional": true + }, + "vite": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/unplugin-utils": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.2.tgz", + "integrity": "sha512-xVToRh2CTmLk2HnEG7ac4rl1MJTT3RFkpS8B++/SnB0kXvuaavD+n3m/vrzyWQOdJNSZQACnbz01pnppbwV5BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pathe": "^2.0.3", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.1.tgz", + "integrity": "sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==", + "dev": true, "funding": [ { - "type": "github", - "url": "https://github.com/sponsors/feross" + "type": "opencollective", + "url": "https://opencollective.com/browserslist" }, { - "type": "patreon", - "url": "https://www.patreon.com/feross" + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" }, { - "type": "consulting", - "url": "https://feross.org/support" + "type": "github", + "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" } }, - "node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/vite": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.1.tgz", + "integrity": "sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.33.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.25", + "rolldown": "~1.2.1", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.4.0", + "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" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "dev": true, + "license": "MPL-2.0", "dependencies": { - "is-arrayish": "^0.3.1" + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/vite/node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/streamx": { - "version": "2.23.0", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", - "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", - "license": "MIT", - "dependencies": { - "events-universal": "^1.0.0", - "fast-fifo": "^1.3.2", - "text-decoder": "^1.1.0" + "node_modules/vite/node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" + "node_modules/vite/node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/vite/node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/vite/node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=8" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/vite/node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=8" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/vitest": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", + "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "@vitest/expect": "4.1.10", + "@vitest/mocker": "4.1.10", + "@vitest/pretty-format": "4.1.10", + "@vitest/runner": "4.1.10", + "@vitest/snapshot": "4.1.10", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" }, "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "engines": { - "node": ">=0.10.0" + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.10", + "@vitest/browser-preview": "4.1.10", + "@vitest/browser-webdriverio": "4.1.10", + "@vitest/coverage-istanbul": "4.1.10", + "@vitest/coverage-v8": "4.1.10", + "@vitest/ui": "4.1.10", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } } }, - "node_modules/tar-fs": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz", - "integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==", + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, "license": "MIT", "dependencies": { - "pump": "^3.0.0", - "tar-stream": "^3.1.5" + "xml-name-validator": "^5.0.0" }, - "optionalDependencies": { - "bare-fs": "^4.0.1", - "bare-path": "^3.0.0" + "engines": { + "node": ">=18" } }, - "node_modules/tar-stream": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.6.tgz", - "integrity": "sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg==", - "dependencies": { - "b4a": "^1.6.4", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" + "node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" } }, - "node_modules/text-decoder": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", - "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", - "license": "Apache-2.0", - "dependencies": { - "b4a": "^1.6.4" - } + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "dev": true, + "license": "MIT" }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dependencies": { - "safe-buffer": "^5.0.1" - }, + "node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", "engines": { - "node": "*" + "node": ">=20" } }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" }, "engines": { - "node": ">=14.17" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, + "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -1746,100 +7867,264 @@ "node": ">= 8" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + "node_modules/wxt": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/wxt/-/wxt-0.21.4.tgz", + "integrity": "sha512-iyVMTXb37BQAbsPHCuPzrK3ZxnO9pZmvbKl7VJWIHe3RG40On2NyP2FOjArtOITO96plEYnh3o+iUlRp9qE90w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@1natsu/wait-element": "^4.1.2", + "@aklinker1/rollup-plugin-visualizer": "5.12.0", + "@aklinker1/zero-zip": "^1.0.1", + "@topcli/prompts": "^4.0.0", + "@webext-core/fake-browser": "^2.0.1", + "@webext-core/isolated-element": "^1.1.3 || ^2 || ^3", + "@webext-core/match-patterns": "^2.0.0", + "@wxt-dev/browser": ">=0.1", + "@wxt-dev/storage": "^1.0.0", + "c12": "^3.3.4", + "cac": "^6.7.14 || ^7.0.0", + "chokidar": "^5.0.0", + "consola": "^3.4.2", + "defu": "^6.1.4", + "dotenv-expand": "^13.0.0", + "filesize": "^11.0.17", + "giget": "^1.2.3 || ^2.0.0 || ^3.0.0", + "hookable": "^6.1.0", + "json5": "^2.2.3", + "linkedom": "^0.18.12", + "magicast": "^0.5.2", + "nypm": "^0.6.5", + "picomatch": "^4.0.3", + "publish-browser-extension": "^5.1.0 || ^6.0.0", + "superlock": "^1.3.2", + "tiny-open": "^1.3.0", + "tinyexec": "^1.2.4", + "tinyglobby": "^0.2.16", + "unimport": "^3.13.1 || ^4.0.0 || ^5.0.0 || ^6.0.0" + }, + "bin": { + "wxt": "bin/wxt.mjs", + "wxt-publish-extension": "bin/wxt-publish-extension.mjs" + }, + "engines": { + "bun": ">=1.2.0", + "node": ">=22" + }, + "funding": { + "url": "https://github.com/sponsors/wxt-dev" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=5.4", + "vite": "^6.3.4 || ^7.0.0 || ^8.0.0-0", + "web-ext": ">=9.2.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + }, + "typescript": { + "optional": true + }, + "web-ext": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, + "license": "ISC", "engines": { "node": ">=10" } }, "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "dev": true, + "license": "ISC", + "optional": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } }, "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.1.0.tgz", + "integrity": "sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==", "dev": true, + "license": "MIT", "dependencies": { - "cliui": "^7.0.2", + "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", + "string-width": "^8.2.1", "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "yargs-parser": "^22.0.0" }, "engines": { - "node": ">=10" + "node": "^20.19.0 || ^22.12.0 || >=23" } }, "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", "dev": true, + "license": "ISC", "engines": { - "node": ">=10" + "node": "^20.19.0 || ^22.12.0 || >=23" } }, - "node_modules/zip-stream": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-5.0.1.tgz", - "integrity": "sha512-UfZ0oa0C8LI58wJ+moL46BDIMgCQbnsb+2PoiJYtonhBsMh2bq1eRBVkvjfVsqbEHd9/EgKPUuL9saSSsec8OA==", + "node_modules/yazl": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/yazl/-/yazl-3.3.1.tgz", + "integrity": "sha512-BbETDVWG+VcMUle37k5Fqp//7SDOK2/1+T7X8TD96M3D9G8jK5VLUdQVdVjGi8im7FGkazX7kk5hkU8X4L5Bng==", "dev": true, + "license": "MIT", "dependencies": { - "archiver-utils": "^4.0.1", - "compress-commons": "^5.0.1", - "readable-stream": "^3.6.0" + "buffer-crc32": "^1.0.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 12.0.0" + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" } } } diff --git a/package.json b/package.json index 001098e..151034f 100644 --- a/package.json +++ b/package.json @@ -1,29 +1,89 @@ { "name": "bauergroup-header-authenticator", - "description": "Sending an Authentication Header for Application Access", - "version": "0.0.5", - "devDependencies": { - "@types/archiver": "^6.0.1", - "@types/chrome": "^0.0.287", - "@types/node": "^22.10.0", - "archiver": "^6.0.1", - "bestzip": "^2.2.1", - "mkdirp": "^3.0.1", - "ncp": "^2.0.0", - "rimraf": "^5.0.5", - "typescript": "^5.7.3" + "displayName": "BAUER GROUP - Header Authenticator", + "description": "Injects a configurable authentication header (secret GUID) into requests to protected BAUER GROUP services.", + "version": "2.0.0", + "type": "module", + "private": true, + "license": "MIT", + "author": "BAUER GROUP", + "engines": { + "node": ">=24" }, "scripts": { - "build": "npm run _version-update && tsc && npm run _copy && npm run _zip", - "build-icons": "tsc -p tsconfig.tools.json && node ./dist-zip/buildIcons.js", - "clean": "rimraf ./dist ./dist-zip", - "_copy": "npm run _mkdirp && npm run _copy-assets", - "_mkdirp": "mkdirp ./dist/resources/icons", - "_copy-assets": "ncp ./src/manifest.json ./dist/manifest.json && ncp ./src/options.html ./dist/options.html && ncp ./resources/icons ./dist/resources/icons", - "_version-update": "tsc -p tsconfig.tools.json && node ./dist-zip/updateVersion.js", - "_zip": "tsc -p tsconfig.tools.json && node ./dist-zip/zipWithVersion.js" + "dev": "wxt", + "dev:firefox": "wxt -b firefox", + "build": "wxt build", + "build:firefox": "wxt build -b firefox", + "build:edge": "wxt build -b edge", + "zip": "wxt zip", + "zip:firefox": "wxt zip -b firefox", + "zip:edge": "wxt zip -b edge", + "typecheck": "wxt prepare && tsc --noEmit", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "format": "prettier --write \"**/*.{ts,tsx,json,md,css,yml,yaml}\"", + "format:check": "prettier --check \"**/*.{ts,tsx,json,md,css,yml,yaml}\"", + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage", + "sign:crx": "node scripts/sign-crx.mjs", + "postinstall": "wxt prepare", + "prepare": "simple-git-hooks" + }, + "simple-git-hooks": { + "pre-commit": "npx lint-staged", + "commit-msg": "npx --no -- commitlint --edit \"$1\"" + }, + "lint-staged": { + "*.{ts,tsx}": [ + "eslint --fix", + "prettier --write" + ], + "*.{json,md,css,yml,yaml}": [ + "prettier --write" + ] + }, + "devDependencies": { + "@commitlint/cli": "^21.0.2", + "@commitlint/config-conventional": "^21.0.2", + "@eslint/js": "^10.0.1", + "@tailwindcss/vite": "^4.3.1", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "@vitest/coverage-v8": "^4.1.10", + "@wxt-dev/auto-icons": "^1.1.1", + "@wxt-dev/module-react": "^1.2.2", + "crx3": "^2.0.0", + "eslint": "^10.5.0", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.3", + "globals": "^17.6.0", + "jsdom": "^29.1.1", + "lint-staged": "^17.0.7", + "prettier": "^3.8.4", + "simple-git-hooks": "^2.13.1", + "tailwindcss": "^4.3.1", + "typescript": "^6.0.3", + "typescript-eslint": "^8.61.1", + "vite": "^8.2.1", + "vitest": "^4.1.9", + "wxt": "^0.21.4" }, "dependencies": { - "sharp": "^0.32.6" + "@radix-ui/react-alert-dialog": "^1.1.17", + "@radix-ui/react-dialog": "^1.1.17", + "@radix-ui/react-label": "^2.1.10", + "@radix-ui/react-slot": "^1.3.0", + "@radix-ui/react-switch": "^1.3.1", + "@radix-ui/react-tooltip": "^1.2.10", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^1.20.0", + "react": "^19.2.7", + "react-dom": "^19.2.7", + "sonner": "^2.0.7", + "tailwind-merge": "^3.6.0", + "zod": "^4.4.3" } } diff --git a/resources/IconSourceFile.png b/resources/IconSourceFile.png deleted file mode 100644 index 1a9ce76..0000000 Binary files a/resources/IconSourceFile.png and /dev/null differ diff --git a/resources/icons/128x128.png b/resources/icons/128x128.png deleted file mode 100644 index f043211..0000000 Binary files a/resources/icons/128x128.png and /dev/null differ diff --git a/resources/icons/16x16.png b/resources/icons/16x16.png deleted file mode 100644 index baf5a46..0000000 Binary files a/resources/icons/16x16.png and /dev/null differ diff --git a/resources/icons/19x19.png b/resources/icons/19x19.png deleted file mode 100644 index 0bb94d8..0000000 Binary files a/resources/icons/19x19.png and /dev/null differ diff --git a/resources/icons/24x24.png b/resources/icons/24x24.png deleted file mode 100644 index 1b2d250..0000000 Binary files a/resources/icons/24x24.png and /dev/null differ diff --git a/resources/icons/32x32.png b/resources/icons/32x32.png deleted file mode 100644 index 19eca68..0000000 Binary files a/resources/icons/32x32.png and /dev/null differ diff --git a/resources/icons/48x48.png b/resources/icons/48x48.png deleted file mode 100644 index 290f479..0000000 Binary files a/resources/icons/48x48.png and /dev/null differ diff --git a/resources/icons/64x64.png b/resources/icons/64x64.png deleted file mode 100644 index 15e7a85..0000000 Binary files a/resources/icons/64x64.png and /dev/null differ diff --git a/scripts/sign-crx.mjs b/scripts/sign-crx.mjs new file mode 100644 index 0000000..a4e5714 --- /dev/null +++ b/scripts/sign-crx.mjs @@ -0,0 +1,54 @@ +// Signs the built extension into a CRX and emits a Chrome update manifest (updates.xml) +// for the self-hosted force-install channel. +// +// Key source (in order): EXTENSION_PRIVATE_KEY env (CI secret) → .keys/key.pem (local). +// Locally, a missing key SKIPS signing so builds work without secrets. +// Under CI it is a HARD FAILURE: a silently unsigned release publishes a GitHub +// Release with no .crx and no updates.xml, and every force-installed client +// keeps the old version with no error surfacing anywhere. +// +// Output: .output/header-authenticator.crx and .output/updates.xml +import crx3 from 'crx3'; +import { existsSync, mkdtempSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; + +const SRC_DIR = '.output/chrome-mv3'; +const OUT_CRX = '.output/header-authenticator.crx'; +const OUT_XML = '.output/updates.xml'; +const CRX_ASSET = 'header-authenticator.crx'; + +function resolveKeyPath() { + const envKey = process.env.EXTENSION_PRIVATE_KEY; + if (envKey && envKey.trim()) { + const dir = mkdtempSync(join(tmpdir(), 'crx-key-')); + const keyPath = join(dir, 'key.pem'); + writeFileSync(keyPath, envKey, 'utf8'); + return keyPath; + } + if (existsSync('.keys/key.pem')) return '.keys/key.pem'; + return null; +} + +const keyPath = resolveKeyPath(); +if (!keyPath) { + const message = '[sign:crx] No signing key (EXTENSION_PRIVATE_KEY / .keys/key.pem).'; + if (process.env.CI) { + console.error(`${message} Refusing to publish an unsigned release.`); + process.exit(1); + } + console.warn(`${message} Skipping (local build).`); + process.exit(0); +} + +if (!existsSync(SRC_DIR)) { + console.error(`[sign:crx] Build output not found at ${SRC_DIR}. Run "wxt build" first.`); + process.exit(1); +} + +// Self-hosted update URL — must be anonymously reachable (public GitHub release asset). +const repo = process.env.GITHUB_REPOSITORY ?? 'bauer-group/IP-Chrome-HeaderAuth'; +const crxURL = `https://github.com/${repo}/releases/latest/download/${CRX_ASSET}`; + +await crx3([SRC_DIR], { keyPath, crxPath: OUT_CRX, xmlPath: OUT_XML, crxURL }); +console.log(`[sign:crx] Wrote ${OUT_CRX} and ${OUT_XML}\n[sign:crx] update_url codebase: ${crxURL}`); diff --git a/resources/IconSourceFile_Transparent.png b/src/assets/icon.png similarity index 100% rename from resources/IconSourceFile_Transparent.png rename to src/assets/icon.png diff --git a/src/assets/tailwind.css b/src/assets/tailwind.css new file mode 100644 index 0000000..4547897 --- /dev/null +++ b/src/assets/tailwind.css @@ -0,0 +1,65 @@ +@import 'tailwindcss'; + +/* + * Design tokens (Tailwind 4 CSS-first @theme). Dark mode is a token set, not a + * code path. shadcn/ui components read these via the `--color-*` custom props. + */ +@theme { + --color-background: oklch(99% 0.003 250); + --color-foreground: oklch(20% 0.02 250); + --color-card: oklch(100% 0 0); + --color-card-foreground: oklch(20% 0.02 250); + --color-muted: oklch(96% 0.005 250); + --color-muted-foreground: oklch(50% 0.02 250); + --color-border: oklch(91% 0.008 250); + --color-input: oklch(91% 0.008 250); + --color-ring: oklch(55% 0.16 255); + --color-primary: oklch(48% 0.16 255); + --color-primary-foreground: oklch(99% 0.003 250); + --color-secondary: oklch(96% 0.005 250); + --color-secondary-foreground: oklch(25% 0.02 250); + --color-destructive: oklch(58% 0.2 25); + --color-destructive-foreground: oklch(99% 0.003 250); + --color-success: oklch(60% 0.15 150); + --color-warning: oklch(70% 0.16 75); + --color-accent: oklch(95% 0.02 255); + --color-accent-foreground: oklch(25% 0.02 250); + --color-popover: oklch(100% 0 0); + --color-popover-foreground: oklch(20% 0.02 250); + + --radius: 0.5rem; +} + +@media (prefers-color-scheme: dark) { + @theme { + --color-background: oklch(20% 0.02 255); + --color-foreground: oklch(96% 0.005 250); + --color-card: oklch(24% 0.02 255); + --color-card-foreground: oklch(96% 0.005 250); + --color-muted: oklch(28% 0.02 255); + --color-muted-foreground: oklch(70% 0.02 250); + --color-border: oklch(33% 0.02 255); + --color-input: oklch(33% 0.02 255); + --color-primary: oklch(70% 0.15 255); + --color-primary-foreground: oklch(20% 0.02 255); + --color-secondary: oklch(30% 0.02 255); + --color-secondary-foreground: oklch(96% 0.005 250); + --color-accent: oklch(32% 0.02 255); + --color-accent-foreground: oklch(96% 0.005 250); + --color-popover: oklch(24% 0.02 255); + --color-popover-foreground: oklch(96% 0.005 250); + } +} + +html, +body { + margin: 0; + background-color: var(--color-background); + color: var(--color-foreground); + font-family: + system-ui, + -apple-system, + 'Segoe UI', + Roboto, + sans-serif; +} diff --git a/src/background.ts b/src/background.ts deleted file mode 100644 index db7fc8d..0000000 --- a/src/background.ts +++ /dev/null @@ -1,63 +0,0 @@ - -const allResourceTypes = Object.values(chrome.declarativeNetRequest.ResourceType); - -// Function to update the dynamic rules -function updateDynamicRules(authSecret: string) { - chrome.declarativeNetRequest.updateDynamicRules({ - removeRuleIds: [1, 2], - - addRules: [ - { - id: 1, - priority: 1, - action: { - type: chrome.declarativeNetRequest.RuleActionType.MODIFY_HEADERS, - requestHeaders: [{ - operation: chrome.declarativeNetRequest.HeaderOperation.SET, - header: 'X-BAUERGROUP-Auth', - value: authSecret, - }], - }, - condition: { - urlFilter: 'https://*.app.bauer-group.com/*', - resourceTypes: allResourceTypes - } - }, - { - id: 2, - priority: 1, - action: { - type: chrome.declarativeNetRequest.RuleActionType.MODIFY_HEADERS, - requestHeaders: [{ - operation: chrome.declarativeNetRequest.HeaderOperation.SET, - header: 'X-BAUERGROUP-Auth', - value: authSecret, - }], - }, - condition: { - urlFilter: 'wss://*.app.bauer-group.com/*', - resourceTypes: allResourceTypes - } - } - ] - }); -} - -// Function to fetch and handle the authSecret from storage -function fetchAndApplyAuthSecret() { - chrome.storage.sync.get('authSecret', (result) => { - const authSecret = result.authSecret || '00000000-0000-0000-0000-000000000000'; - updateDynamicRules(authSecret); - }); -} - -// Call the function to ensure the rules are set up with the current authSecret value -fetchAndApplyAuthSecret(); - -// Optional: Listen for changes in storage and update rules accordingly -chrome.storage.onChanged.addListener((changes, namespace) => { - if (namespace === 'sync' && changes.authSecret) { - const newAuthSecret = changes.authSecret.newValue || '00000000-0000-0000-0000-000000000000'; - updateDynamicRules(newAuthSecret); - } -}); diff --git a/src/components/RuleStatusBadge.tsx b/src/components/RuleStatusBadge.tsx new file mode 100644 index 0000000..b1ca3d1 --- /dev/null +++ b/src/components/RuleStatusBadge.tsx @@ -0,0 +1,33 @@ +import { AlertTriangle, Shield, ShieldCheck } from 'lucide-react'; +import { Badge } from './ui/badge'; +import { useI18n } from '../lib/i18n'; +import type { EffectiveRule } from '../lib/schema/config'; +import { ruleStatus } from '../lib/rule-status'; + +export function RuleStatusBadge({ rule, granted }: { rule: EffectiveRule; granted: boolean }) { + const { t } = useI18n(); + const { key } = ruleStatus(rule, granted); + + if (key === 'active') { + return ( + + + {t('popup.statusActive')} + + ); + } + if (key === 'needs-access') { + return ( + + + {t('popup.statusNeedsAccess')} + + ); + } + return ( + + + {t('popup.statusDisabled')} + + ); +} diff --git a/src/components/options/DomainInput.tsx b/src/components/options/DomainInput.tsx new file mode 100644 index 0000000..7e56cb6 --- /dev/null +++ b/src/components/options/DomainInput.tsx @@ -0,0 +1,67 @@ +import { useState, type KeyboardEvent } from 'react'; +import { Plus, X } from 'lucide-react'; +import { Input } from '../ui/input'; +import { Button } from '../ui/button'; +import { Badge } from '../ui/badge'; +import { useI18n } from '../../lib/i18n'; + +export function DomainInput({ + value, + onChange, +}: { + value: string[]; + onChange: (v: string[]) => void; +}) { + const { t } = useI18n(); + const [draft, setDraft] = useState(''); + + const add = () => { + const domain = draft.trim().toLowerCase(); + if (domain && !value.includes(domain)) onChange([...value, domain]); + setDraft(''); + }; + + const onKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Enter') { + e.preventDefault(); + add(); + } + }; + + return ( +
+
+ setDraft(e.target.value)} + onKeyDown={onKeyDown} + spellCheck={false} + autoComplete="off" + /> + +
+ {value.length > 0 && ( +
+ {value.map((domain) => ( + + {domain} + + + ))} +
+ )} +

{t('field.domainsHint')}

+
+ ); +} diff --git a/src/components/options/EmptyState.tsx b/src/components/options/EmptyState.tsx new file mode 100644 index 0000000..bb7e85b --- /dev/null +++ b/src/components/options/EmptyState.tsx @@ -0,0 +1,20 @@ +import { Plus, ShieldCheck } from 'lucide-react'; +import { Button } from '../ui/button'; +import { useI18n } from '../../lib/i18n'; + +export function EmptyState({ onAdd }: { onAdd: () => void }) { + const { t } = useI18n(); + return ( +
+
+ +
+

{t('empty.title')}

+

{t('empty.body')}

+ +
+ ); +} diff --git a/src/components/options/ImportExport.tsx b/src/components/options/ImportExport.tsx new file mode 100644 index 0000000..9e87c00 --- /dev/null +++ b/src/components/options/ImportExport.tsx @@ -0,0 +1,63 @@ +import { useRef } from 'react'; +import { Download, Upload } from 'lucide-react'; +import { Button } from '../ui/button'; +import { useI18n } from '../../lib/i18n'; +import { toast } from '../ui/sonner'; +import { ConfigSchema, type Config } from '../../lib/schema/config'; + +export function ImportExport({ + config, + onImport, +}: { + config: Config; + onImport: (config: Config) => void; +}) { + const { t } = useI18n(); + const fileRef = useRef(null); + + const exportConfig = () => { + const blob = new Blob([JSON.stringify(config, null, 2)], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = 'header-authenticator-config.json'; + link.click(); + URL.revokeObjectURL(url); + toast.success(t('toast.exported')); + }; + + const importConfig = async (file: File) => { + try { + const parsed = ConfigSchema.safeParse(JSON.parse(await file.text())); + if (!parsed.success) throw new Error('invalid'); + onImport(parsed.data); + toast.success(t('toast.imported')); + } catch { + toast.error(t('toast.importError')); + } + }; + + return ( + <> + + + { + const file = e.target.files?.[0]; + if (file) void importConfig(file); + e.target.value = ''; + }} + /> + + ); +} diff --git a/src/components/options/LanguageToggle.tsx b/src/components/options/LanguageToggle.tsx new file mode 100644 index 0000000..0201a8e --- /dev/null +++ b/src/components/options/LanguageToggle.tsx @@ -0,0 +1,29 @@ +import { Globe } from 'lucide-react'; +import { Button } from '../ui/button'; +import type { Locale } from '../../lib/i18n'; + +const LOCALES: Locale[] = ['de', 'en']; + +export function LanguageToggle({ + locale, + onChange, +}: { + locale: Locale; + onChange: (locale: Locale) => void; +}) { + return ( +
+ + {LOCALES.map((l) => ( + + ))} +
+ ); +} diff --git a/src/components/options/OptionsApp.tsx b/src/components/options/OptionsApp.tsx new file mode 100644 index 0000000..8bfdd10 --- /dev/null +++ b/src/components/options/OptionsApp.tsx @@ -0,0 +1,157 @@ +import { useState } from 'react'; +import { Building2, Plus } from 'lucide-react'; +import { I18nProvider, useI18n, type Locale } from '../../lib/i18n'; +import { TooltipProvider } from '../ui/tooltip'; +import { Toaster, toast } from '../ui/sonner'; +import { Button } from '../ui/button'; +import { Switch } from '../ui/switch'; +import { EmptyState } from './EmptyState'; +import { RulesTable } from './RulesTable'; +import { RuleDialog } from './RuleDialog'; +import { LanguageToggle } from './LanguageToggle'; +import { ImportExport } from './ImportExport'; +import { useConfig, type UseConfig } from '../../lib/hooks/useConfig'; +import { useGrants } from '../../lib/hooks/useGrants'; +import { + createBlankRule, + type Config, + type EffectiveRule, + type Rule, +} from '../../lib/schema/config'; +import { removeOriginsForPatterns, requestOriginsForPatterns } from '../../lib/permissions'; + +export function OptionsApp() { + const cfg = useConfig(); + return ( + + + + + + + ); +} + +interface DialogState { + rule: Rule; + isNew: boolean; +} + +function OptionsContent({ cfg }: { cfg: UseConfig }) { + const { t } = useI18n(); + const { config, managed, effective, update } = cfg; + const { granted, refresh } = useGrants(effective.rules); + const [dialog, setDialog] = useState(null); + + const masterManaged = managed?.masterEnabled !== undefined; + const hasManaged = effective.rules.some((r) => r.source === 'managed'); + const isEmpty = effective.rules.length === 0; + + const saveRule = async (rule: Rule) => { + setDialog(null); + // Request host access first so the user gesture from the Save click is preserved. + const ok = await requestOriginsForPatterns(rule.domainPatterns); + const exists = config.rules.some((r) => r.id === rule.id); + const rules = exists + ? config.rules.map((r) => (r.id === rule.id ? rule : r)) + : [...config.rules, rule]; + await update({ ...config, rules }); + refresh(); + toast.success(ok ? t('toast.saved') : t('toast.permDenied')); + }; + + const deleteRule = async (rule: EffectiveRule) => { + const rules = config.rules.filter((r) => r.id !== rule.id); + const stillUsed = rules.flatMap((r) => r.domainPatterns); + await removeOriginsForPatterns(rule.domainPatterns, stillUsed); + await update({ ...config, rules }); + refresh(); + toast.success(t('toast.deleted')); + }; + + const toggleRule = (rule: EffectiveRule, enabled: boolean) => { + void update({ + ...config, + rules: config.rules.map((r) => (r.id === rule.id ? { ...r, enabled } : r)), + }); + }; + + const grantRule = async (rule: EffectiveRule) => { + const ok = await requestOriginsForPatterns(rule.domainPatterns); + refresh(); + if (ok) toast.success(t('toast.permGranted')); + else toast.error(t('toast.permDenied')); + }; + + return ( +
+
+
+ +
+

{t('options.title')}

+

{t('options.subtitle')}

+
+
+
+ void update({ ...config, uiLocale: l })} + /> + void update(c)} /> +
+
+ +
+
+

{t('options.master')}

+

{t('options.masterHint')}

+
+ void update({ ...config, masterEnabled: v })} + /> +
+ +
+

{t('options.rules')}

+ +
+ + {hasManaged && ( +
+ + {t('managed.hint')} +
+ )} + + {isEmpty ? ( + setDialog({ rule: createBlankRule(), isNew: true })} /> + ) : ( +
+ setDialog({ rule, isNew: false })} + onDelete={(rule) => void deleteRule(rule)} + onToggle={toggleRule} + onGrant={(rule) => void grantRule(rule)} + /> +
+ )} + + {dialog && ( + setDialog(null)} + onSave={(rule) => void saveRule(rule)} + /> + )} +
+ ); +} diff --git a/src/components/options/RuleDialog.tsx b/src/components/options/RuleDialog.tsx new file mode 100644 index 0000000..001a14c --- /dev/null +++ b/src/components/options/RuleDialog.tsx @@ -0,0 +1,128 @@ +import { useState, type ReactNode } from 'react'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '../ui/dialog'; +import { Button } from '../ui/button'; +import { Input } from '../ui/input'; +import { Label } from '../ui/label'; +import { Switch } from '../ui/switch'; +import { SecretField } from './SecretField'; +import { DomainInput } from './DomainInput'; +import { useI18n } from '../../lib/i18n'; +import { RuleSchema, type Rule } from '../../lib/schema/config'; + +export function RuleDialog({ + initial, + isNew, + onClose, + onSave, +}: { + initial: Rule; + isNew: boolean; + onClose: () => void; + onSave: (rule: Rule) => void; +}) { + const { t } = useI18n(); + const [draft, setDraft] = useState(initial); + const [errors, setErrors] = useState>>({}); + + const set = (key: K, value: Rule[K]) => + setDraft((d) => ({ ...d, [key]: value }) as Rule); + + const submit = () => { + const result = RuleSchema.safeParse(draft); + if (!result.success) { + const next: Partial> = {}; + for (const issue of result.error.issues) { + const key = issue.path[0] as keyof Rule | undefined; + if (key && !next[key]) next[key] = issue.message; + } + setErrors(next); + return; + } + onSave(result.data); + }; + + return ( + { + if (!o) onClose(); + }} + > + + + {isNew ? t('dialog.addTitle') : t('dialog.editTitle')} + {t('dialog.description')} + + +
+ + set('label', e.target.value)} + /> + + + + set('domainPatterns', v)} /> + + + + set('headerName', e.target.value)} + className="font-mono" + /> + + + + set('secretValue', v)} /> + + + +
+ + + + + +
+
+ ); +} + +function Field({ + label, + error, + hint, + children, +}: { + label: string; + error?: string | undefined; + hint?: string | undefined; + children: ReactNode; +}) { + return ( +
+ + {children} + {hint && !error &&

{hint}

} + {error &&

{error}

} +
+ ); +} diff --git a/src/components/options/RulesTable.tsx b/src/components/options/RulesTable.tsx new file mode 100644 index 0000000..a8d211c --- /dev/null +++ b/src/components/options/RulesTable.tsx @@ -0,0 +1,153 @@ +import { Building2, Pencil, Trash2 } from 'lucide-react'; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '../ui/table'; +import { Button } from '../ui/button'; +import { Switch } from '../ui/switch'; +import { Badge } from '../ui/badge'; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from '../ui/alert-dialog'; +import { RuleStatusBadge } from '../RuleStatusBadge'; +import { useI18n } from '../../lib/i18n'; +import type { EffectiveConfig, EffectiveRule } from '../../lib/schema/config'; + +export function RulesTable({ + effective, + granted, + onEdit, + onDelete, + onToggle, + onGrant, +}: { + effective: EffectiveConfig; + granted: Set; + onEdit: (rule: EffectiveRule) => void; + onDelete: (rule: EffectiveRule) => void; + onToggle: (rule: EffectiveRule, enabled: boolean) => void; + onGrant: (rule: EffectiveRule) => void; +}) { + const { t } = useI18n(); + + return ( + + + + {t('col.status')} + {t('col.label')} + {t('col.domains')} + {t('col.header')} + {t('col.secret')} + {t('col.actions')} + + + + {effective.rules.map((rule) => { + const isManaged = rule.source === 'managed'; + const isGranted = granted.has(rule.id); + return ( + + + {rule.enabled && !isGranted ? ( + + ) : ( + + )} + + +
+ {rule.label} + {isManaged && ( + + + {t('managed.badge')} + + )} +
+
+ + + {rule.domainPatterns.join(', ')} + + + + {rule.headerName} + + + + {maskSecret(rule.secretValue)} + + + +
+ {!isManaged && ( + <> + onToggle(rule, v)} + title={t('field.enabled')} + /> + + + + )} +
+
+
+ ); + })} +
+
+ ); +} + +function maskSecret(secret: string): string { + if (secret.length <= 4) return '••••'; + return `••••••••-••••-••••-••••-••••••••${secret.slice(-4)}`; +} + +function DeleteButton({ + rule, + onDelete, +}: { + rule: EffectiveRule; + onDelete: (rule: EffectiveRule) => void; +}) { + const { t } = useI18n(); + return ( + + + + + + + {t('delete.title')} + {t('delete.body', { label: rule.label })} + + + {t('action.cancel')} + onDelete(rule)}> + {t('delete.confirm')} + + + + + ); +} diff --git a/src/components/options/SecretField.tsx b/src/components/options/SecretField.tsx new file mode 100644 index 0000000..2776375 --- /dev/null +++ b/src/components/options/SecretField.tsx @@ -0,0 +1,57 @@ +import { useState } from 'react'; +import { Copy, Eye, EyeOff, RefreshCw } from 'lucide-react'; +import { Input } from '../ui/input'; +import { Button } from '../ui/button'; +import { useI18n } from '../../lib/i18n'; +import { toast } from '../ui/sonner'; + +export function SecretField({ value, onChange }: { value: string; onChange: (v: string) => void }) { + const { t } = useI18n(); + const [reveal, setReveal] = useState(false); + + const copy = async () => { + await navigator.clipboard.writeText(value); + toast.success(t('toast.copied')); + }; + + return ( +
+ onChange(e.target.value)} + spellCheck={false} + autoComplete="off" + className="font-mono" + /> + + + +
+ ); +} diff --git a/src/components/popup/PopupApp.tsx b/src/components/popup/PopupApp.tsx new file mode 100644 index 0000000..ce2951a --- /dev/null +++ b/src/components/popup/PopupApp.tsx @@ -0,0 +1,119 @@ +import { browser } from 'wxt/browser'; +import { ExternalLink, Building2 } from 'lucide-react'; +import { I18nProvider, useI18n } from '../../lib/i18n'; +import { TooltipProvider } from '../ui/tooltip'; +import { Button } from '../ui/button'; +import { Switch } from '../ui/switch'; +import { Badge } from '../ui/badge'; +import { RuleStatusBadge } from '../RuleStatusBadge'; +import { useConfig, type UseConfig } from '../../lib/hooks/useConfig'; +import { useGrants } from '../../lib/hooks/useGrants'; +import { requestOriginsForPatterns } from '../../lib/permissions'; +import type { EffectiveRule } from '../../lib/schema/config'; + +export function PopupApp() { + const cfg = useConfig(); + return ( + + + + + + ); +} + +function PopupContent({ cfg }: { cfg: UseConfig }) { + const { t } = useI18n(); + const { effective, config, managed, update } = cfg; + const { granted, refresh } = useGrants(effective.rules); + + const activeCount = effective.rules.filter((r) => r.enabled && granted.has(r.id)).length; + const masterManaged = managed?.masterEnabled !== undefined; + + return ( +
+
+
+ + {t('app.name')} +
+ 0 ? 'success' : 'secondary'}> + {t('popup.activeCount', { count: activeCount })} + +
+ +
+ {t('popup.master')} + void update({ ...config, masterEnabled: v })} + /> +
+ +
+ + {t('popup.protectedDomains')} + + {effective.rules.length === 0 ? ( +

{t('popup.noRules')}

+ ) : ( + effective.rules.map((rule) => ( + + )) + )} +
+ + +
+ ); +} + +function PopupRow({ + rule, + granted, + onGranted, +}: { + rule: EffectiveRule; + granted: boolean; + onGranted: () => void; +}) { + const { t } = useI18n(); + const needsAccess = rule.enabled && !granted; + + const grant = async () => { + const ok = await requestOriginsForPatterns(rule.domainPatterns); + if (ok) onGranted(); + }; + + return ( +
+
+
+ {rule.label} + {rule.source === 'managed' && ( + + )} +
+ + {rule.domainPatterns.join(', ')} + +
+ {needsAccess ? ( + + ) : ( + + )} +
+ ); +} diff --git a/src/components/ui/alert-dialog.tsx b/src/components/ui/alert-dialog.tsx new file mode 100644 index 0000000..4d72160 --- /dev/null +++ b/src/components/ui/alert-dialog.tsx @@ -0,0 +1,103 @@ +import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog'; +import { + forwardRef, + type ComponentPropsWithoutRef, + type ElementRef, + type HTMLAttributes, +} from 'react'; +import { cn } from '../../lib/utils'; +import { buttonVariants } from './button'; + +export const AlertDialog = AlertDialogPrimitive.Root; +export const AlertDialogTrigger = AlertDialogPrimitive.Trigger; + +const AlertDialogOverlay = forwardRef< + ElementRef, + ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogOverlay.displayName = 'AlertDialogOverlay'; + +export const AlertDialogContent = forwardRef< + ElementRef, + ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + +)); +AlertDialogContent.displayName = 'AlertDialogContent'; + +export function AlertDialogHeader({ className, ...props }: HTMLAttributes) { + return
; +} + +export function AlertDialogFooter({ className, ...props }: HTMLAttributes) { + return ( +
+ ); +} + +export const AlertDialogTitle = forwardRef< + ElementRef, + ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogTitle.displayName = 'AlertDialogTitle'; + +export const AlertDialogDescription = forwardRef< + ElementRef, + ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogDescription.displayName = 'AlertDialogDescription'; + +export const AlertDialogAction = forwardRef< + ElementRef, + ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogAction.displayName = 'AlertDialogAction'; + +export const AlertDialogCancel = forwardRef< + ElementRef, + ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogCancel.displayName = 'AlertDialogCancel'; diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx new file mode 100644 index 0000000..ce3edec --- /dev/null +++ b/src/components/ui/badge.tsx @@ -0,0 +1,29 @@ +import { cva, type VariantProps } from 'class-variance-authority'; +import { type HTMLAttributes } from 'react'; +import { cn } from '../../lib/utils'; + +const badgeVariants = cva( + 'inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs font-medium', + { + variants: { + variant: { + default: 'border-transparent bg-primary text-primary-foreground', + secondary: 'border-transparent bg-secondary text-secondary-foreground', + success: 'border-transparent bg-success text-white', + warning: 'border-transparent bg-warning text-black', + destructive: 'border-transparent bg-destructive text-destructive-foreground', + outline: 'border-border text-foreground', + }, + }, + defaultVariants: { variant: 'default' }, + }, +); + +export interface BadgeProps + extends HTMLAttributes, VariantProps {} + +export function Badge({ className, variant, ...props }: BadgeProps) { + return
; +} + +export { badgeVariants }; diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx new file mode 100644 index 0000000..7fae109 --- /dev/null +++ b/src/components/ui/button.tsx @@ -0,0 +1,44 @@ +import { Slot } from '@radix-ui/react-slot'; +import { cva, type VariantProps } from 'class-variance-authority'; +import { forwardRef, type ButtonHTMLAttributes } from 'react'; +import { cn } from '../../lib/utils'; + +const buttonVariants = cva( + 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50', + { + variants: { + variant: { + default: 'bg-primary text-primary-foreground hover:opacity-90', + destructive: 'bg-destructive text-destructive-foreground hover:opacity-90', + outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground', + secondary: 'bg-secondary text-secondary-foreground hover:opacity-80', + ghost: 'hover:bg-accent hover:text-accent-foreground', + link: 'text-primary underline-offset-4 hover:underline', + }, + size: { + default: 'h-9 px-4 py-2', + sm: 'h-8 rounded-md px-3 text-xs', + lg: 'h-10 rounded-md px-6', + icon: 'h-9 w-9', + }, + }, + defaultVariants: { variant: 'default', size: 'default' }, + }, +); + +export interface ButtonProps + extends ButtonHTMLAttributes, VariantProps { + asChild?: boolean; +} + +export const Button = forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : 'button'; + return ( + + ); + }, +); +Button.displayName = 'Button'; + +export { buttonVariants }; diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx new file mode 100644 index 0000000..53e6a22 --- /dev/null +++ b/src/components/ui/dialog.tsx @@ -0,0 +1,82 @@ +import * as DialogPrimitive from '@radix-ui/react-dialog'; +import { X } from 'lucide-react'; +import { + forwardRef, + type ComponentPropsWithoutRef, + type ElementRef, + type HTMLAttributes, +} from 'react'; +import { cn } from '../../lib/utils'; + +export const Dialog = DialogPrimitive.Root; +export const DialogTrigger = DialogPrimitive.Trigger; +export const DialogClose = DialogPrimitive.Close; + +const DialogOverlay = forwardRef< + ElementRef, + ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogOverlay.displayName = 'DialogOverlay'; + +export const DialogContent = forwardRef< + ElementRef, + ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)); +DialogContent.displayName = 'DialogContent'; + +export function DialogHeader({ className, ...props }: HTMLAttributes) { + return
; +} + +export function DialogFooter({ className, ...props }: HTMLAttributes) { + return ( +
+ ); +} + +export const DialogTitle = forwardRef< + ElementRef, + ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogTitle.displayName = 'DialogTitle'; + +export const DialogDescription = forwardRef< + ElementRef, + ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogDescription.displayName = 'DialogDescription'; diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx new file mode 100644 index 0000000..b2c1c5e --- /dev/null +++ b/src/components/ui/input.tsx @@ -0,0 +1,16 @@ +import { forwardRef, type InputHTMLAttributes } from 'react'; +import { cn } from '../../lib/utils'; + +export const Input = forwardRef>( + ({ className, ...props }, ref) => ( + + ), +); +Input.displayName = 'Input'; diff --git a/src/components/ui/label.tsx b/src/components/ui/label.tsx new file mode 100644 index 0000000..c8cbf0a --- /dev/null +++ b/src/components/ui/label.tsx @@ -0,0 +1,18 @@ +import * as LabelPrimitive from '@radix-ui/react-label'; +import { forwardRef, type ComponentPropsWithoutRef, type ElementRef } from 'react'; +import { cn } from '../../lib/utils'; + +export const Label = forwardRef< + ElementRef, + ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +Label.displayName = 'Label'; diff --git a/src/components/ui/sonner.tsx b/src/components/ui/sonner.tsx new file mode 100644 index 0000000..a8e26cc --- /dev/null +++ b/src/components/ui/sonner.tsx @@ -0,0 +1,8 @@ +import { Toaster as Sonner } from 'sonner'; + +/** Toast host. Place once near the app root. */ +export function Toaster() { + return ; +} + +export { toast } from 'sonner'; diff --git a/src/components/ui/switch.tsx b/src/components/ui/switch.tsx new file mode 100644 index 0000000..7103a31 --- /dev/null +++ b/src/components/ui/switch.tsx @@ -0,0 +1,20 @@ +import * as SwitchPrimitive from '@radix-ui/react-switch'; +import { forwardRef, type ComponentPropsWithoutRef, type ElementRef } from 'react'; +import { cn } from '../../lib/utils'; + +export const Switch = forwardRef< + ElementRef, + ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)); +Switch.displayName = 'Switch'; diff --git a/src/components/ui/table.tsx b/src/components/ui/table.tsx new file mode 100644 index 0000000..337f261 --- /dev/null +++ b/src/components/ui/table.tsx @@ -0,0 +1,64 @@ +import { + forwardRef, + type HTMLAttributes, + type TdHTMLAttributes, + type ThHTMLAttributes, +} from 'react'; +import { cn } from '../../lib/utils'; + +export const Table = forwardRef>( + ({ className, ...props }, ref) => ( +
+ + + ), +); +Table.displayName = 'Table'; + +export const TableHeader = forwardRef< + HTMLTableSectionElement, + HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableHeader.displayName = 'TableHeader'; + +export const TableBody = forwardRef< + HTMLTableSectionElement, + HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableBody.displayName = 'TableBody'; + +export const TableRow = forwardRef>( + ({ className, ...props }, ref) => ( + + ), +); +TableRow.displayName = 'TableRow'; + +export const TableHead = forwardRef>( + ({ className, ...props }, ref) => ( +
+ ), +); +TableHead.displayName = 'TableHead'; + +export const TableCell = forwardRef>( + ({ className, ...props }, ref) => ( + + ), +); +TableCell.displayName = 'TableCell'; diff --git a/src/components/ui/tooltip.tsx b/src/components/ui/tooltip.tsx new file mode 100644 index 0000000..77e16b7 --- /dev/null +++ b/src/components/ui/tooltip.tsx @@ -0,0 +1,25 @@ +import * as TooltipPrimitive from '@radix-ui/react-tooltip'; +import { forwardRef, type ComponentPropsWithoutRef, type ElementRef } from 'react'; +import { cn } from '../../lib/utils'; + +export const TooltipProvider = TooltipPrimitive.Provider; +export const Tooltip = TooltipPrimitive.Root; +export const TooltipTrigger = TooltipPrimitive.Trigger; + +export const TooltipContent = forwardRef< + ElementRef, + ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + + + +)); +TooltipContent.displayName = 'TooltipContent'; diff --git a/src/contentScript.ts b/src/contentScript.ts deleted file mode 100644 index 8ad63a0..0000000 --- a/src/contentScript.ts +++ /dev/null @@ -1,9 +0,0 @@ - -chrome.storage.sync.get('authSecret', (result) => { - const authSecret = result.authSecret || '00000000-0000-0000-0000-000000000000'; - - chrome.runtime.sendMessage({ - type: 'updateAuthSecret', - authSecret: authSecret - }); -}); diff --git a/src/entrypoints/background.ts b/src/entrypoints/background.ts new file mode 100644 index 0000000..ae4f8d6 --- /dev/null +++ b/src/entrypoints/background.ts @@ -0,0 +1,35 @@ +import { defineBackground } from 'wxt/utils/define-background'; +import { browser } from 'wxt/browser'; +import { loadConfig, onConfigChanged } from '../lib/storage'; +import { loadManagedConfig } from '../lib/storage/managed'; +import { computeEffectiveConfig } from '../lib/effective'; +import { computeRuleInputs } from '../lib/dnr/compute'; +import { buildRule } from '../lib/dnr/build-rules'; +import { applyRules } from '../lib/dnr/apply-rules'; +import { patternsToOrigins } from '../lib/permissions'; + +/** Recompute the effective config and apply the resulting declarativeNetRequest rules. */ +async function refreshRules(): Promise { + const [user, managed] = await Promise.all([loadConfig(), loadManagedConfig()]); + const effective = computeEffectiveConfig(user, managed); + + // A rule only takes effect once its host permission is granted, so filter to granted rules. + const grantChecks = await Promise.all( + effective.rules.map(async (rule) => { + const origins = patternsToOrigins(rule.domainPatterns); + const granted = await browser.permissions.contains({ origins }).catch(() => false); + return granted ? rule.id : null; + }), + ); + const grantedRuleIds = new Set(grantChecks.filter((id): id is string => id !== null)); + + const inputs = computeRuleInputs(effective, grantedRuleIds); + await applyRules(inputs.map(buildRule)); +} + +export default defineBackground(() => { + void refreshRules(); + onConfigChanged(() => void refreshRules()); + browser.permissions.onAdded.addListener(() => void refreshRules()); + browser.permissions.onRemoved.addListener(() => void refreshRules()); +}); diff --git a/src/entrypoints/options/index.html b/src/entrypoints/options/index.html new file mode 100644 index 0000000..8556ecd --- /dev/null +++ b/src/entrypoints/options/index.html @@ -0,0 +1,12 @@ + + + + + + Header Authenticator – Einstellungen + + +
+ + + diff --git a/src/entrypoints/options/main.tsx b/src/entrypoints/options/main.tsx new file mode 100644 index 0000000..8b7a4a1 --- /dev/null +++ b/src/entrypoints/options/main.tsx @@ -0,0 +1,6 @@ +import { createRoot } from 'react-dom/client'; +import { OptionsApp } from '../../components/options/OptionsApp'; +import '../../assets/tailwind.css'; + +const root = document.getElementById('root'); +if (root) createRoot(root).render(); diff --git a/src/entrypoints/popup/index.html b/src/entrypoints/popup/index.html new file mode 100644 index 0000000..affb34f --- /dev/null +++ b/src/entrypoints/popup/index.html @@ -0,0 +1,12 @@ + + + + + + Header Authenticator + + +
+ + + diff --git a/src/entrypoints/popup/main.tsx b/src/entrypoints/popup/main.tsx new file mode 100644 index 0000000..15d7c07 --- /dev/null +++ b/src/entrypoints/popup/main.tsx @@ -0,0 +1,6 @@ +import { createRoot } from 'react-dom/client'; +import { PopupApp } from '../../components/popup/PopupApp'; +import '../../assets/tailwind.css'; + +const root = document.getElementById('root'); +if (root) createRoot(root).render(); diff --git a/src/lib/dnr/apply-rules.test.ts b/src/lib/dnr/apply-rules.test.ts new file mode 100644 index 0000000..9b8199a --- /dev/null +++ b/src/lib/dnr/apply-rules.test.ts @@ -0,0 +1,78 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { fakeBrowser } from 'wxt/testing/fake-browser'; +import { browser } from 'wxt/browser'; +import { applyRules, DnrLimitError, MAX_UNSAFE_DYNAMIC_RULES } from './apply-rules'; +import { buildRule } from './build-rules'; + +// Built through buildRule rather than a hand-written literal so these tests +// cannot drift from the real ModifyHeaderRule shape. +function makeRule(ruleId: number) { + return buildRule({ + ruleId, + headerName: 'X-BAUERGROUP-Auth', + secretValue: '11111111-1111-1111-1111-111111111111', + domains: ['app.bauer-group.com'], + }); +} + +describe('applyRules', () => { + beforeEach(() => fakeBrowser.reset()); + + it('removes every live rule id before adding the new set', async () => { + const getDynamicRules = vi + .spyOn(browser.declarativeNetRequest, 'getDynamicRules') + .mockResolvedValue([{ id: 7 }, { id: 9 }] as never); + const updateDynamicRules = vi + .spyOn(browser.declarativeNetRequest, 'updateDynamicRules') + .mockResolvedValue(undefined as never); + + const rules = [makeRule(1)]; + await applyRules(rules); + + expect(getDynamicRules).toHaveBeenCalledOnce(); + expect(updateDynamicRules).toHaveBeenCalledWith({ + removeRuleIds: [7, 9], + addRules: rules, + }); + }); + + it('still clears live rules when the new set is empty', async () => { + vi.spyOn(browser.declarativeNetRequest, 'getDynamicRules').mockResolvedValue([ + { id: 3 }, + ] as never); + const updateDynamicRules = vi + .spyOn(browser.declarativeNetRequest, 'updateDynamicRules') + .mockResolvedValue(undefined as never); + + await applyRules([]); + + expect(updateDynamicRules).toHaveBeenCalledWith({ removeRuleIds: [3], addRules: [] }); + }); + + it('throws DnrLimitError above the binding ceiling and touches nothing', async () => { + const getDynamicRules = vi.spyOn(browser.declarativeNetRequest, 'getDynamicRules'); + const updateDynamicRules = vi + .spyOn(browser.declarativeNetRequest, 'updateDynamicRules') + .mockResolvedValue(undefined as never); + + const tooMany = Array.from({ length: MAX_UNSAFE_DYNAMIC_RULES + 1 }, (_, i) => makeRule(i + 1)); + + await expect(applyRules(tooMany)).rejects.toBeInstanceOf(DnrLimitError); + await expect(applyRules(tooMany)).rejects.toThrow(String(MAX_UNSAFE_DYNAMIC_RULES)); + // The guard runs before any binding call — nothing is read, nothing written. + expect(getDynamicRules).not.toHaveBeenCalled(); + expect(updateDynamicRules).not.toHaveBeenCalled(); + }); + + it('accepts exactly the ceiling', async () => { + vi.spyOn(browser.declarativeNetRequest, 'getDynamicRules').mockResolvedValue([] as never); + const updateDynamicRules = vi + .spyOn(browser.declarativeNetRequest, 'updateDynamicRules') + .mockResolvedValue(undefined as never); + + const atLimit = Array.from({ length: MAX_UNSAFE_DYNAMIC_RULES }, (_, i) => makeRule(i + 1)); + + await expect(applyRules(atLimit)).resolves.toBeUndefined(); + expect(updateDynamicRules).toHaveBeenCalledOnce(); + }); +}); diff --git a/src/lib/dnr/apply-rules.ts b/src/lib/dnr/apply-rules.ts new file mode 100644 index 0000000..43adf64 --- /dev/null +++ b/src/lib/dnr/apply-rules.ts @@ -0,0 +1,28 @@ +import { browser } from 'wxt/browser'; +import type { ModifyHeaderRule } from './build-rules'; + +/** `modifyHeaders` is an "unsafe" DNR action — the binding ceiling is 5000 dynamic rules. */ +export const MAX_UNSAFE_DYNAMIC_RULES = 5000; + +export class DnrLimitError extends Error { + constructor(public readonly count: number) { + super(`Too many dynamic rules: ${count} exceeds the limit of ${MAX_UNSAFE_DYNAMIC_RULES}`); + this.name = 'DnrLimitError'; + } +} + +type UpdateArg = Parameters[0]; + +/** + * Replace ALL current dynamic rules with the freshly computed set. The live rule + * list is the source of truth for removal (robust against config drift between runs). + */ +export async function applyRules(rules: ModifyHeaderRule[]): Promise { + if (rules.length > MAX_UNSAFE_DYNAMIC_RULES) throw new DnrLimitError(rules.length); + const existing = await browser.declarativeNetRequest.getDynamicRules(); + const removeRuleIds = existing.map((rule) => rule.id); + await browser.declarativeNetRequest.updateDynamicRules({ + removeRuleIds, + addRules: rules, + } as unknown as UpdateArg); +} diff --git a/src/lib/dnr/build-rules.test.ts b/src/lib/dnr/build-rules.test.ts new file mode 100644 index 0000000..b87721f --- /dev/null +++ b/src/lib/dnr/build-rules.test.ts @@ -0,0 +1,23 @@ +import { describe, it, expect } from 'vitest'; +import { buildRule, DNR_RESOURCE_TYPES } from './build-rules'; + +describe('buildRule', () => { + it('builds a modifyHeaders/set rule with the expected shape', () => { + const rule = buildRule({ + ruleId: 7, + headerName: 'X-BAUERGROUP-Auth', + secretValue: '11111111-1111-1111-1111-111111111111', + domains: ['app.bauer-group.com'], + }); + expect(rule.id).toBe(7); + expect(rule.priority).toBe(1); + expect(rule.action.type).toBe('modifyHeaders'); + expect(rule.action.requestHeaders[0]).toEqual({ + header: 'X-BAUERGROUP-Auth', + operation: 'set', + value: '11111111-1111-1111-1111-111111111111', + }); + expect(rule.condition.requestDomains).toEqual(['app.bauer-group.com']); + expect(rule.condition.resourceTypes).toEqual([...DNR_RESOURCE_TYPES]); + }); +}); diff --git a/src/lib/dnr/build-rules.ts b/src/lib/dnr/build-rules.ts new file mode 100644 index 0000000..0219a7f --- /dev/null +++ b/src/lib/dnr/build-rules.ts @@ -0,0 +1,50 @@ +/** + * Self-contained declarativeNetRequest rule shape. We model the runtime JSON Chrome + * actually expects (string-literal actions/operations) rather than coupling to a + * specific @types flavour; the single cast to the API parameter type lives in + * apply-rules.ts. + */ +export interface ModifyHeaderRule { + id: number; + priority: number; + action: { + type: 'modifyHeaders'; + requestHeaders: Array<{ header: string; operation: 'set'; value: string }>; + }; + condition: { + requestDomains: string[]; + resourceTypes: string[]; + }; +} + +/** Resource types the auth header should be attached to (incl. websocket — best-effort). */ +export const DNR_RESOURCE_TYPES = [ + 'main_frame', + 'sub_frame', + 'xmlhttprequest', + 'websocket', + 'other', +] as const; + +export interface RuleInput { + ruleId: number; + headerName: string; + secretValue: string; + /** Bare registrable domains for `condition.requestDomains` (scheme-agnostic, matches subdomains). */ + domains: string[]; +} + +export function buildRule(input: RuleInput): ModifyHeaderRule { + return { + id: input.ruleId, + priority: 1, + action: { + type: 'modifyHeaders', + requestHeaders: [{ header: input.headerName, operation: 'set', value: input.secretValue }], + }, + condition: { + requestDomains: input.domains, + resourceTypes: [...DNR_RESOURCE_TYPES], + }, + }; +} diff --git a/src/lib/dnr/compute.test.ts b/src/lib/dnr/compute.test.ts new file mode 100644 index 0000000..c166275 --- /dev/null +++ b/src/lib/dnr/compute.test.ts @@ -0,0 +1,51 @@ +import { describe, it, expect } from 'vitest'; +import { computeRuleInputs } from './compute'; +import type { EffectiveConfig, EffectiveRule } from '../schema/config'; + +function rule(partial: Partial): EffectiveRule { + return { + id: 'r1', + enabled: true, + label: 'R', + domainPatterns: ['app.bauer-group.com'], + headerName: 'X-BAUERGROUP-Auth', + secretValue: '11111111-1111-1111-1111-111111111111', + syncSecret: true, + source: 'user', + ...partial, + }; +} +const granted = (ids: string[]) => new Set(ids); + +describe('computeRuleInputs', () => { + it('returns nothing when the master switch is off', () => { + const eff: EffectiveConfig = { masterEnabled: false, rules: [rule({})] }; + expect(computeRuleInputs(eff, granted(['r1']))).toEqual([]); + }); + + it('skips disabled, zero-secret and ungranted rules', () => { + const eff: EffectiveConfig = { + masterEnabled: true, + rules: [ + rule({ id: 'a', enabled: false }), + rule({ id: 'b', secretValue: '00000000-0000-0000-0000-000000000000' }), + rule({ id: 'c' }), + ], + }; + expect(computeRuleInputs(eff, granted([]))).toEqual([]); + }); + + it('emits granted rules with sequential ids and de-duplicated domains', () => { + const eff: EffectiveConfig = { + masterEnabled: true, + rules: [ + rule({ id: 'a', domainPatterns: ['*.app.bauer-group.com', 'app.bauer-group.com'] }), + rule({ id: 'b', domainPatterns: ['x.com'] }), + ], + }; + const out = computeRuleInputs(eff, granted(['a', 'b'])); + expect(out.map((r) => r.ruleId)).toEqual([1, 2]); + expect(out[0]!.domains).toEqual(['app.bauer-group.com']); + expect(out[1]!.domains).toEqual(['x.com']); + }); +}); diff --git a/src/lib/dnr/compute.ts b/src/lib/dnr/compute.ts new file mode 100644 index 0000000..38e2f4f --- /dev/null +++ b/src/lib/dnr/compute.ts @@ -0,0 +1,39 @@ +import type { EffectiveConfig } from '../schema/config'; +import type { RuleInput } from './build-rules'; +import { patternToRequestDomain } from '../permissions/patterns'; + +const ZERO_GUID = '00000000-0000-0000-0000-000000000000'; + +/** + * Turn the effective config into DNR rule inputs with deterministic sequential ids. + * Pure: the caller pre-resolves which rules currently have their host permission + * granted and passes the id set in. A rule is emitted only when it is enabled, has + * a real secret, is granted, and has at least one domain. + */ +export function computeRuleInputs( + effective: EffectiveConfig, + grantedRuleIds: ReadonlySet, +): RuleInput[] { + if (!effective.masterEnabled) return []; + + const inputs: RuleInput[] = []; + let nextId = 1; + + for (const rule of effective.rules) { + if (!rule.enabled) continue; + if (!rule.secretValue || rule.secretValue === ZERO_GUID) continue; + if (!grantedRuleIds.has(rule.id)) continue; + + const domains = [...new Set(rule.domainPatterns.map(patternToRequestDomain))].filter(Boolean); + if (domains.length === 0) continue; + + inputs.push({ + ruleId: nextId++, + headerName: rule.headerName, + secretValue: rule.secretValue, + domains, + }); + } + + return inputs; +} diff --git a/src/lib/effective.test.ts b/src/lib/effective.test.ts new file mode 100644 index 0000000..e2f74ce --- /dev/null +++ b/src/lib/effective.test.ts @@ -0,0 +1,54 @@ +import { describe, it, expect } from 'vitest'; +import { computeEffectiveConfig } from './effective'; +import { ManagedConfigSchema } from './storage/managed'; +import type { Config } from './schema/config'; + +const SECRET = '11111111-1111-1111-1111-111111111111'; + +function userConfig(overrides: Partial = {}): Config { + return { + schemaVersion: 1, + masterEnabled: true, + uiLocale: 'de', + rules: [ + { + id: 'u1', + enabled: true, + label: 'User rule', + domainPatterns: ['user.example.com'], + headerName: 'X-BAUERGROUP-Auth', + secretValue: SECRET, + syncSecret: true, + }, + ], + ...overrides, + }; +} + +describe('computeEffectiveConfig', () => { + it('tags user rules as user-sourced when no policy exists', () => { + const effective = computeEffectiveConfig(userConfig(), null); + expect(effective.rules).toHaveLength(1); + expect(effective.rules[0]?.source).toBe('user'); + expect(effective.masterEnabled).toBe(true); + }); + + it('places managed rules before user rules', () => { + const managed = ManagedConfigSchema.parse({ + rules: [{ label: 'Corp', domainPatterns: ['corp.example.com'], secretValue: SECRET }], + }); + const effective = computeEffectiveConfig(userConfig(), managed); + expect(effective.rules.map((rule) => rule.source)).toEqual(['managed', 'user']); + }); + + it('lets a managed masterEnabled override the user switch', () => { + const managed = ManagedConfigSchema.parse({ masterEnabled: false, rules: [] }); + expect(computeEffectiveConfig(userConfig(), managed).masterEnabled).toBe(false); + }); + + it('falls back to the user switch when the policy omits masterEnabled', () => { + const managed = ManagedConfigSchema.parse({ rules: [] }); + const user = userConfig({ masterEnabled: false }); + expect(computeEffectiveConfig(user, managed).masterEnabled).toBe(false); + }); +}); diff --git a/src/lib/effective.ts b/src/lib/effective.ts new file mode 100644 index 0000000..c68cb1a --- /dev/null +++ b/src/lib/effective.ts @@ -0,0 +1,17 @@ +import type { Config, EffectiveConfig, EffectiveRule } from './schema/config'; +import { type ManagedConfig, managedRulesToEffective } from './storage/managed'; + +/** + * Combine the user config with the (optional) managed policy into the effective + * config the DNR engine and UI consume. Managed rules come first and are read-only; + * a managed `masterEnabled` overrides the user's switch when present. + */ +export function computeEffectiveConfig( + user: Config, + managed: ManagedConfig | null, +): EffectiveConfig { + const managedRules = managed ? managedRulesToEffective(managed) : []; + const userRules: EffectiveRule[] = user.rules.map((rule) => ({ ...rule, source: 'user' })); + const masterEnabled = managed?.masterEnabled ?? user.masterEnabled; + return { masterEnabled, rules: [...managedRules, ...userRules] }; +} diff --git a/src/lib/hooks/useConfig.ts b/src/lib/hooks/useConfig.ts new file mode 100644 index 0000000..e1a25d6 --- /dev/null +++ b/src/lib/hooks/useConfig.ts @@ -0,0 +1,40 @@ +import { useCallback, useEffect, useState } from 'react'; +import { type Config, type EffectiveConfig, DEFAULT_CONFIG } from '../schema/config'; +import { loadConfig, saveConfig, onConfigChanged } from '../storage'; +import { loadManagedConfig, type ManagedConfig } from '../storage/managed'; +import { computeEffectiveConfig } from '../effective'; + +export interface UseConfig { + config: Config; + managed: ManagedConfig | null; + effective: EffectiveConfig; + loading: boolean; + update: (next: Config) => Promise; +} + +/** Load user + managed config, expose the merged effective view, and persist updates. */ +export function useConfig(): UseConfig { + const [config, setConfig] = useState(DEFAULT_CONFIG); + const [managed, setManaged] = useState(null); + const [loading, setLoading] = useState(true); + + const reload = useCallback(async () => { + const [user, mgd] = await Promise.all([loadConfig(), loadManagedConfig()]); + setConfig(user); + setManaged(mgd); + setLoading(false); + }, []); + + useEffect(() => { + void reload(); + return onConfigChanged(() => void reload()); + }, [reload]); + + const update = useCallback(async (next: Config) => { + setConfig(next); // optimistic + await saveConfig(next); + }, []); + + const effective = computeEffectiveConfig(config, managed); + return { config, managed, effective, loading, update }; +} diff --git a/src/lib/hooks/useGrants.ts b/src/lib/hooks/useGrants.ts new file mode 100644 index 0000000..0933bc1 --- /dev/null +++ b/src/lib/hooks/useGrants.ts @@ -0,0 +1,37 @@ +import { useCallback, useEffect, useState } from 'react'; +import { browser } from 'wxt/browser'; +import type { EffectiveRule } from '../schema/config'; +import { patternsToOrigins } from '../permissions'; + +/** Track which rules currently have their host permission granted; refresh on permission changes. */ +export function useGrants(rules: EffectiveRule[]): { granted: Set; refresh: () => void } { + const [granted, setGranted] = useState>(new Set()); + const signature = rules.map((r) => `${r.id}:${r.domainPatterns.join(',')}`).join('|'); + + const refresh = useCallback(async () => { + const entries = await Promise.all( + rules.map(async (rule) => { + const ok = await browser.permissions + .contains({ origins: patternsToOrigins(rule.domainPatterns) }) + .catch(() => false); + return [rule.id, ok] as const; + }), + ); + setGranted(new Set(entries.filter(([, ok]) => ok).map(([id]) => id))); + // rules referenced via stable `signature` + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [signature]); + + useEffect(() => { + void refresh(); + const onChange = () => void refresh(); + browser.permissions.onAdded.addListener(onChange); + browser.permissions.onRemoved.addListener(onChange); + return () => { + browser.permissions.onAdded.removeListener(onChange); + browser.permissions.onRemoved.removeListener(onChange); + }; + }, [refresh]); + + return { granted, refresh }; +} diff --git a/src/lib/i18n/index.tsx b/src/lib/i18n/index.tsx new file mode 100644 index 0000000..c799db4 --- /dev/null +++ b/src/lib/i18n/index.tsx @@ -0,0 +1,35 @@ +import { createContext, useContext, useMemo, type ReactNode } from 'react'; +import { messages, type Locale, type MessageKey } from './messages'; + +export type { Locale, MessageKey } from './messages'; + +export type TranslateFn = (key: MessageKey, vars?: Record) => string; + +interface I18nValue { + locale: Locale; + t: TranslateFn; +} + +const I18nContext = createContext(null); + +export function I18nProvider({ locale, children }: { locale: Locale; children: ReactNode }) { + const value = useMemo(() => { + const dict = messages[locale]; + const t: TranslateFn = (key, vars) => { + let str: string = dict[key] ?? messages.en[key] ?? key; + if (vars) { + for (const [k, v] of Object.entries(vars)) str = str.replaceAll(`{${k}}`, String(v)); + } + return str; + }; + return { locale, t }; + }, [locale]); + + return {children}; +} + +export function useI18n(): I18nValue { + const ctx = useContext(I18nContext); + if (!ctx) throw new Error('useI18n must be used within an I18nProvider'); + return ctx; +} diff --git a/src/lib/i18n/messages.ts b/src/lib/i18n/messages.ts new file mode 100644 index 0000000..4267a15 --- /dev/null +++ b/src/lib/i18n/messages.ts @@ -0,0 +1,188 @@ +/** + * Lightweight, type-safe message catalog (DE/EN). Chosen over a heavyweight i18n + * runtime because the extension has a small string set and needs an in-app locale + * toggle — which chrome.i18n cannot do. `en` is the source of truth for the key set; + * `de` must cover the same keys (enforced by `satisfies` below). + */ +const en = { + 'app.name': 'BAUER GROUP Header Authenticator', + + // Popup + 'popup.master': 'Protection', + 'popup.protectedDomains': 'Protected domains', + 'popup.activeCount': '{count} active', + 'popup.statusActive': 'Active', + 'popup.statusNeedsAccess': 'Needs access', + 'popup.statusDisabled': 'Disabled', + 'popup.statusManaged': 'Managed', + 'popup.wssBestEffort': 'WSS best-effort', + 'popup.grant': 'Grant access', + 'popup.noRules': 'No rules yet.', + 'popup.openSettings': 'Open settings', + + // Options shell + 'options.title': 'BAUER GROUP Header Authenticator', + 'options.subtitle': 'Manage protected domains and their authentication secrets.', + 'options.master': 'Header injection', + 'options.masterHint': 'Master switch for all rules on this device.', + 'options.addRule': 'Add rule', + 'options.rules': 'Rules', + + // Empty state + 'empty.title': 'No protection rules yet', + 'empty.body': 'Add a rule to start sending the authentication header to a protected service.', + 'empty.cta': 'Add your first rule', + + // Table columns + 'col.status': 'Status', + 'col.label': 'Name', + 'col.domains': 'Domains', + 'col.header': 'Header', + 'col.secret': 'Secret', + 'col.actions': 'Actions', + + // Managed + 'managed.badge': 'Managed by your organization', + 'managed.hint': 'This rule is provisioned by your organization and cannot be edited here.', + + // Rule dialog + 'dialog.addTitle': 'Add rule', + 'dialog.editTitle': 'Edit rule', + 'dialog.description': 'A rule injects a header with your secret into requests to its domains.', + 'field.label': 'Name', + 'field.labelPh': 'e.g. BAUER GROUP Apps', + 'field.domains': 'Domains', + 'field.domainsPh': 'app.bauer-group.com', + 'field.domainsHint': 'Bare domain or *.wildcard. Subdomains are matched automatically.', + 'field.addDomain': 'Add', + 'field.header': 'Header name', + 'field.headerHint': 'The HTTP header the secret is sent in.', + 'field.secret': 'Secret (GUID)', + 'field.secretPh': '00000000-0000-0000-0000-000000000000', + 'field.generate': 'Generate', + 'field.reveal': 'Reveal', + 'field.hide': 'Hide', + 'field.copy': 'Copy', + 'field.syncSecret': 'Sync secret across devices', + 'field.syncSecretHint': 'Off keeps the secret on this device only (not in the account cloud).', + 'field.enabled': 'Enabled', + + // Actions + 'action.save': 'Save', + 'action.cancel': 'Cancel', + 'action.delete': 'Delete', + 'action.edit': 'Edit', + 'action.close': 'Close', + + // Delete confirm + 'delete.title': 'Delete rule?', + 'delete.body': 'The rule "{label}" will be removed and its host access revoked.', + 'delete.confirm': 'Delete', + + // Import / export + 'io.export': 'Export', + 'io.import': 'Import', + + // Language + 'lang.label': 'Language', + + // Toasts + 'toast.saved': 'Saved.', + 'toast.deleted': 'Rule deleted.', + 'toast.permGranted': 'Access granted.', + 'toast.permDenied': 'Access was not granted — the rule stays inactive.', + 'toast.imported': 'Configuration imported.', + 'toast.importError': 'Import failed: invalid configuration file.', + 'toast.exported': 'Configuration exported.', + 'toast.copied': 'Copied to clipboard.', +} as const; + +export type MessageKey = keyof typeof en; + +const de = { + 'app.name': 'BAUER GROUP Header Authenticator', + + 'popup.master': 'Schutz', + 'popup.protectedDomains': 'Geschützte Domains', + 'popup.activeCount': '{count} aktiv', + 'popup.statusActive': 'Aktiv', + 'popup.statusNeedsAccess': 'Zugriff nötig', + 'popup.statusDisabled': 'Deaktiviert', + 'popup.statusManaged': 'Verwaltet', + 'popup.wssBestEffort': 'WSS best-effort', + 'popup.grant': 'Zugriff erteilen', + 'popup.noRules': 'Noch keine Regeln.', + 'popup.openSettings': 'Einstellungen öffnen', + + 'options.title': 'BAUER GROUP Header Authenticator', + 'options.subtitle': 'Geschützte Domains und ihre Authentifizierungs-Secrets verwalten.', + 'options.master': 'Header-Injektion', + 'options.masterHint': 'Hauptschalter für alle Regeln auf diesem Gerät.', + 'options.addRule': 'Regel hinzufügen', + 'options.rules': 'Regeln', + + 'empty.title': 'Noch keine Schutzregeln', + 'empty.body': + 'Legen Sie eine Regel an, um den Authentifizierungs-Header an einen geschützten Dienst zu senden.', + 'empty.cta': 'Erste Regel anlegen', + + 'col.status': 'Status', + 'col.label': 'Name', + 'col.domains': 'Domains', + 'col.header': 'Header', + 'col.secret': 'Secret', + 'col.actions': 'Aktionen', + + 'managed.badge': 'Von Ihrer Organisation verwaltet', + 'managed.hint': + 'Diese Regel wird von Ihrer Organisation bereitgestellt und kann hier nicht bearbeitet werden.', + + 'dialog.addTitle': 'Regel hinzufügen', + 'dialog.editTitle': 'Regel bearbeiten', + 'dialog.description': + 'Eine Regel injiziert einen Header mit Ihrem Secret in Requests an ihre Domains.', + 'field.label': 'Name', + 'field.labelPh': 'z. B. BAUER GROUP Apps', + 'field.domains': 'Domains', + 'field.domainsPh': 'app.bauer-group.com', + 'field.domainsHint': 'Reine Domain oder *.Wildcard. Subdomains werden automatisch erfasst.', + 'field.addDomain': 'Hinzufügen', + 'field.header': 'Header-Name', + 'field.headerHint': 'Der HTTP-Header, in dem das Secret gesendet wird.', + 'field.secret': 'Secret (GUID)', + 'field.secretPh': '00000000-0000-0000-0000-000000000000', + 'field.generate': 'Generieren', + 'field.reveal': 'Anzeigen', + 'field.hide': 'Verbergen', + 'field.copy': 'Kopieren', + 'field.syncSecret': 'Secret geräteübergreifend synchronisieren', + 'field.syncSecretHint': 'Aus hält das Secret nur auf diesem Gerät (nicht in der Konto-Cloud).', + 'field.enabled': 'Aktiviert', + + 'action.save': 'Speichern', + 'action.cancel': 'Abbrechen', + 'action.delete': 'Löschen', + 'action.edit': 'Bearbeiten', + 'action.close': 'Schließen', + + 'delete.title': 'Regel löschen?', + 'delete.body': 'Die Regel „{label}" wird entfernt und ihr Host-Zugriff widerrufen.', + 'delete.confirm': 'Löschen', + + 'io.export': 'Exportieren', + 'io.import': 'Importieren', + + 'lang.label': 'Sprache', + + 'toast.saved': 'Gespeichert.', + 'toast.deleted': 'Regel gelöscht.', + 'toast.permGranted': 'Zugriff erteilt.', + 'toast.permDenied': 'Zugriff wurde nicht erteilt — die Regel bleibt inaktiv.', + 'toast.imported': 'Konfiguration importiert.', + 'toast.importError': 'Import fehlgeschlagen: ungültige Konfigurationsdatei.', + 'toast.exported': 'Konfiguration exportiert.', + 'toast.copied': 'In die Zwischenablage kopiert.', +} satisfies Record; + +export const messages = { en, de }; +export type Locale = keyof typeof messages; diff --git a/src/lib/permissions/index.test.ts b/src/lib/permissions/index.test.ts new file mode 100644 index 0000000..bbf484b --- /dev/null +++ b/src/lib/permissions/index.test.ts @@ -0,0 +1,84 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { fakeBrowser } from 'wxt/testing/fake-browser'; +import { browser } from 'wxt/browser'; +import { + hasOriginsForPatterns, + requestOriginsForPatterns, + removeOriginsForPatterns, +} from './index'; +import { patternsToOrigins } from './patterns'; + +const DOMAIN = 'app.bauer-group.com'; + +describe('hasOriginsForPatterns', () => { + beforeEach(() => fakeBrowser.reset()); + + it('short-circuits to true when no pattern yields an origin', async () => { + const contains = vi.spyOn(browser.permissions, 'contains'); + await expect(hasOriginsForPatterns([])).resolves.toBe(true); + expect(contains).not.toHaveBeenCalled(); + }); + + it('delegates to permissions.contains with the derived origins', async () => { + const contains = vi.spyOn(browser.permissions, 'contains').mockResolvedValue(true as never); + await expect(hasOriginsForPatterns([DOMAIN])).resolves.toBe(true); + expect(contains).toHaveBeenCalledWith({ origins: patternsToOrigins([DOMAIN]) }); + }); + + it('reports false when the grant is absent', async () => { + vi.spyOn(browser.permissions, 'contains').mockResolvedValue(false as never); + await expect(hasOriginsForPatterns([DOMAIN])).resolves.toBe(false); + }); + + it('reports false when the binding rejects', async () => { + vi.spyOn(browser.permissions, 'contains').mockRejectedValue(new Error('nope') as never); + await expect(hasOriginsForPatterns([DOMAIN])).resolves.toBe(false); + }); +}); + +describe('requestOriginsForPatterns', () => { + beforeEach(() => fakeBrowser.reset()); + + it('short-circuits to true when there is nothing to request', async () => { + const request = vi.spyOn(browser.permissions, 'request'); + await expect(requestOriginsForPatterns([])).resolves.toBe(true); + expect(request).not.toHaveBeenCalled(); + }); + + it('reports false when the user denies the prompt', async () => { + vi.spyOn(browser.permissions, 'request').mockResolvedValue(false as never); + await expect(requestOriginsForPatterns([DOMAIN])).resolves.toBe(false); + }); + + it('reports false when the binding rejects', async () => { + vi.spyOn(browser.permissions, 'request').mockRejectedValue(new Error('nope') as never); + await expect(requestOriginsForPatterns([DOMAIN])).resolves.toBe(false); + }); +}); + +describe('removeOriginsForPatterns', () => { + beforeEach(() => fakeBrowser.reset()); + + it('keeps origins that are still used by another rule', async () => { + const remove = vi.spyOn(browser.permissions, 'remove').mockResolvedValue(true as never); + await expect(removeOriginsForPatterns([DOMAIN], [DOMAIN])).resolves.toBe(true); + expect(remove).not.toHaveBeenCalled(); + }); + + it('revokes origins that no remaining rule needs', async () => { + const remove = vi.spyOn(browser.permissions, 'remove').mockResolvedValue(true as never); + await expect(removeOriginsForPatterns(['gone.example.com'], [])).resolves.toBe(true); + expect(remove).toHaveBeenCalledWith({ origins: patternsToOrigins(['gone.example.com']) }); + }); + + it('revokes only the origins that are actually free', async () => { + const remove = vi.spyOn(browser.permissions, 'remove').mockResolvedValue(true as never); + await removeOriginsForPatterns(['gone.example.com', DOMAIN], [DOMAIN]); + expect(remove).toHaveBeenCalledWith({ origins: patternsToOrigins(['gone.example.com']) }); + }); + + it('reports false when the binding rejects', async () => { + vi.spyOn(browser.permissions, 'remove').mockRejectedValue(new Error('nope') as never); + await expect(removeOriginsForPatterns(['gone.example.com'], [])).resolves.toBe(false); + }); +}); diff --git a/src/lib/permissions/index.ts b/src/lib/permissions/index.ts new file mode 100644 index 0000000..df36a53 --- /dev/null +++ b/src/lib/permissions/index.ts @@ -0,0 +1,41 @@ +import { browser } from 'wxt/browser'; +import { patternsToOrigins } from './patterns'; + +export { patternToOrigins, patternToRequestDomain, patternsToOrigins } from './patterns'; + +/** Are all host permissions for these domain patterns currently granted? */ +export async function hasOriginsForPatterns(patterns: string[]): Promise { + const origins = patternsToOrigins(patterns); + if (origins.length === 0) return true; + try { + return await browser.permissions.contains({ origins }); + } catch { + return false; + } +} + +/** Request host permissions for these domain patterns (must be called from a user gesture). */ +export async function requestOriginsForPatterns(patterns: string[]): Promise { + const origins = patternsToOrigins(patterns); + if (origins.length === 0) return true; + try { + return await browser.permissions.request({ origins }); + } catch { + return false; + } +} + +/** Revoke host permissions for these domain patterns (skips origins still in use elsewhere). */ +export async function removeOriginsForPatterns( + patterns: string[], + stillUsedPatterns: string[] = [], +): Promise { + const keep = new Set(patternsToOrigins(stillUsedPatterns)); + const origins = patternsToOrigins(patterns).filter((o) => !keep.has(o)); + if (origins.length === 0) return true; + try { + return await browser.permissions.remove({ origins }); + } catch { + return false; + } +} diff --git a/src/lib/permissions/patterns.test.ts b/src/lib/permissions/patterns.test.ts new file mode 100644 index 0000000..50c21ee --- /dev/null +++ b/src/lib/permissions/patterns.test.ts @@ -0,0 +1,29 @@ +import { describe, it, expect } from 'vitest'; +import { patternToRequestDomain, patternToOrigins, patternsToOrigins } from './patterns'; + +describe('patternToRequestDomain', () => { + it('strips the wildcard prefix', () => { + expect(patternToRequestDomain('*.bauer-group.com')).toBe('bauer-group.com'); + }); + it('lowercases and keeps a bare domain', () => { + expect(patternToRequestDomain('App.Bauer-Group.com')).toBe('app.bauer-group.com'); + }); +}); + +describe('patternToOrigins', () => { + it('produces https + wss wildcard origins for a bare domain', () => { + expect(patternToOrigins('app.bauer-group.com')).toEqual([ + 'https://*.app.bauer-group.com/*', + 'wss://*.app.bauer-group.com/*', + ]); + }); + it('keeps an existing wildcard', () => { + expect(patternToOrigins('*.x.com')).toEqual(['https://*.x.com/*', 'wss://*.x.com/*']); + }); +}); + +describe('patternsToOrigins', () => { + it('flattens and de-duplicates', () => { + expect(patternsToOrigins(['x.com', 'x.com'])).toEqual(['https://*.x.com/*', 'wss://*.x.com/*']); + }); +}); diff --git a/src/lib/permissions/patterns.ts b/src/lib/permissions/patterns.ts new file mode 100644 index 0000000..64361ac --- /dev/null +++ b/src/lib/permissions/patterns.ts @@ -0,0 +1,27 @@ +/** + * Pure mapping helpers between user-facing domain patterns and the two shapes the + * extension needs: DNR `requestDomains` entries and host permission match patterns. + * Kept free of the `browser` global so they are trivially unit-testable. + */ + +/** `*.example.com` / `app.example.com` → registrable host for DNR `requestDomains`. */ +export function patternToRequestDomain(pattern: string): string { + const host = pattern.trim().toLowerCase(); + return host.startsWith('*.') ? host.slice(2) : host; +} + +/** + * Domain pattern → host-permission match patterns (https + wss). + * A leading `*.` host pattern matches the apex domain AND all subdomains, which + * aligns with DNR `requestDomains` subdomain semantics. + */ +export function patternToOrigins(pattern: string): string[] { + const trimmed = pattern.trim().toLowerCase(); + const host = trimmed.startsWith('*.') ? trimmed : `*.${trimmed}`; + return [`https://${host}/*`, `wss://${host}/*`]; +} + +/** Flatten + de-duplicate the origins for a set of domain patterns. */ +export function patternsToOrigins(patterns: string[]): string[] { + return [...new Set(patterns.flatMap(patternToOrigins))]; +} diff --git a/src/lib/rule-status.test.ts b/src/lib/rule-status.test.ts new file mode 100644 index 0000000..525247c --- /dev/null +++ b/src/lib/rule-status.test.ts @@ -0,0 +1,39 @@ +import { describe, it, expect } from 'vitest'; +import { ruleStatus } from './rule-status'; +import type { EffectiveRule } from './schema/config'; + +function makeRule(overrides: Partial = {}): EffectiveRule { + return { + id: 'r1', + enabled: true, + label: 'Rule', + domainPatterns: ['app.bauer-group.com'], + headerName: 'X-BAUERGROUP-Auth', + secretValue: '11111111-1111-1111-1111-111111111111', + syncSecret: true, + source: 'user', + ...overrides, + }; +} + +describe('ruleStatus', () => { + it('reports active when the rule is enabled and access is granted', () => { + expect(ruleStatus(makeRule(), true)).toEqual({ key: 'active', managed: false }); + }); + + it('reports needs-access when enabled but access is missing', () => { + expect(ruleStatus(makeRule(), false)).toEqual({ key: 'needs-access', managed: false }); + }); + + it('reports disabled regardless of grant state', () => { + const rule = makeRule({ enabled: false }); + expect(ruleStatus(rule, true)).toEqual({ key: 'disabled', managed: false }); + expect(ruleStatus(rule, false)).toEqual({ key: 'disabled', managed: false }); + }); + + it('flags managed rules through every status', () => { + expect(ruleStatus(makeRule({ source: 'managed' }), true).managed).toBe(true); + expect(ruleStatus(makeRule({ source: 'managed' }), false).managed).toBe(true); + expect(ruleStatus(makeRule({ source: 'managed', enabled: false }), true).managed).toBe(true); + }); +}); diff --git a/src/lib/rule-status.ts b/src/lib/rule-status.ts new file mode 100644 index 0000000..7f772e9 --- /dev/null +++ b/src/lib/rule-status.ts @@ -0,0 +1,14 @@ +import type { EffectiveRule } from './schema/config'; + +export type RuleStatusKey = 'active' | 'needs-access' | 'disabled'; + +/** Derive the display status of an effective rule from its enabled + granted state. */ +export function ruleStatus( + rule: EffectiveRule, + granted: boolean, +): { key: RuleStatusKey; managed: boolean } { + const managed = rule.source === 'managed'; + if (!rule.enabled) return { key: 'disabled', managed }; + if (!granted) return { key: 'needs-access', managed }; + return { key: 'active', managed }; +} diff --git a/src/lib/schema/config.test.ts b/src/lib/schema/config.test.ts new file mode 100644 index 0000000..c1cb165 --- /dev/null +++ b/src/lib/schema/config.test.ts @@ -0,0 +1,51 @@ +import { describe, it, expect } from 'vitest'; +import { RuleSchema, parseConfig, DEFAULT_CONFIG, createBlankRule } from './config'; + +const validRule = { + id: crypto.randomUUID(), + label: 'Apps', + domainPatterns: ['app.bauer-group.com'], + secretValue: '11111111-1111-1111-1111-111111111111', +}; + +describe('RuleSchema', () => { + it('applies defaults for omitted fields', () => { + const r = RuleSchema.parse(validRule); + expect(r.enabled).toBe(true); + expect(r.headerName).toBe('X-BAUERGROUP-Auth'); + expect(r.syncSecret).toBe(true); + }); + it('rejects a non-GUID secret', () => { + expect(RuleSchema.safeParse({ ...validRule, secretValue: 'nope' }).success).toBe(false); + }); + it('rejects an empty domain list', () => { + expect(RuleSchema.safeParse({ ...validRule, domainPatterns: [] }).success).toBe(false); + }); + it('rejects a domain pattern with scheme', () => { + expect(RuleSchema.safeParse({ ...validRule, domainPatterns: ['http://x'] }).success).toBe( + false, + ); + }); + it('accepts a wildcard subdomain', () => { + expect( + RuleSchema.safeParse({ ...validRule, domainPatterns: ['*.bauer-group.com'] }).success, + ).toBe(true); + }); +}); + +describe('parseConfig', () => { + it('falls back to defaults for invalid input', () => { + expect(parseConfig('garbage')).toEqual(DEFAULT_CONFIG); + }); + it('parses a valid config', () => { + const cfg = parseConfig({ rules: [validRule] }); + expect(cfg.rules).toHaveLength(1); + expect(cfg.masterEnabled).toBe(true); + }); +}); + +describe('createBlankRule', () => { + it('generates a fresh id each call', () => { + expect(createBlankRule().id).not.toBe(createBlankRule().id); + }); +}); diff --git a/src/lib/schema/config.ts b/src/lib/schema/config.ts new file mode 100644 index 0000000..45cbf8d --- /dev/null +++ b/src/lib/schema/config.ts @@ -0,0 +1,88 @@ +import { z } from 'zod'; + +/** HTTP header name: letters, digits and hyphens only (RFC token subset we allow). */ +export const HeaderNameSchema = z + .string() + .trim() + .min(1) + .max(128) + .regex(/^[A-Za-z0-9-]+$/, 'Header name may contain letters, digits and hyphens only'); + +/** + * GUID shape (8-4-4-4-12 hex). Intentionally lenient vs. RFC UUID variant/version + * checks: the secret is a shared token that may come from any GUID source (.NET, + * backend, manual), and the all-zero null GUID is our "unset" sentinel. + */ +export const GUID_REGEX = + /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/; + +/** Secret value — a GUID-shaped token. */ +export const SecretSchema = z + .string() + .trim() + .regex(GUID_REGEX, 'Secret must be a GUID, e.g. 1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d'); + +/** A bare domain (`app.example.com`) or wildcard subdomain (`*.example.com`). No scheme, no path. */ +export const DomainPatternSchema = z + .string() + .trim() + .min(1) + .regex( + /^(\*\.)?([a-z0-9-]+\.)+[a-z]{2,}$/i, + 'Enter a domain like app.example.com or *.example.com', + ); + +export const RuleSchema = z.object({ + /** Internal stable id (not the numeric DNR rule id); generated via crypto.randomUUID(). */ + id: z.string().min(1), + enabled: z.boolean().default(true), + label: z.string().trim().min(1).max(60), + domainPatterns: z.array(DomainPatternSchema).min(1), + headerName: HeaderNameSchema.default('X-BAUERGROUP-Auth'), + secretValue: SecretSchema, + /** When false the secret is kept device-local instead of synced to the account cloud. */ + syncSecret: z.boolean().default(true), +}); +export type Rule = z.infer; + +export const ConfigSchema = z.object({ + schemaVersion: z.literal(1).default(1), + masterEnabled: z.boolean().default(true), + uiLocale: z.enum(['de', 'en']).default('de'), + rules: z.array(RuleSchema).default([]), +}); +export type Config = z.infer; + +export const DEFAULT_CONFIG: Config = { + schemaVersion: 1, + masterEnabled: true, + uiLocale: 'de', + rules: [], +}; + +/** Where an effective rule originates from — drives the read-only "managed" UI badge. */ +export type RuleSource = 'managed' | 'user'; +export type EffectiveRule = Rule & { source: RuleSource }; +export interface EffectiveConfig { + masterEnabled: boolean; + rules: EffectiveRule[]; +} + +/** A blank rule for seeding the "add rule" form (not yet valid until filled in). */ +export function createBlankRule(): Rule { + return { + id: crypto.randomUUID(), + enabled: true, + label: '', + domainPatterns: [], + headerName: 'X-BAUERGROUP-Auth', + secretValue: '', + syncSecret: true, + }; +} + +/** Parse unknown storage data into a Config, falling back to defaults on failure. */ +export function parseConfig(data: unknown): Config { + const result = ConfigSchema.safeParse(data ?? {}); + return result.success ? result.data : { ...DEFAULT_CONFIG }; +} diff --git a/src/lib/storage/index.test.ts b/src/lib/storage/index.test.ts new file mode 100644 index 0000000..2bfc4a2 --- /dev/null +++ b/src/lib/storage/index.test.ts @@ -0,0 +1,119 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { fakeBrowser } from 'wxt/testing/fake-browser'; +import { loadConfig, saveConfig, onConfigChanged } from './index'; +import type { Config, Rule } from '../schema/config'; + +const SECRET = '11111111-1111-1111-1111-111111111111'; +const ZERO_GUID = '00000000-0000-0000-0000-000000000000'; + +function makeRule(id: string, syncSecret: boolean): Rule { + return { + id, + enabled: true, + label: 'Rule', + domainPatterns: ['app.bauer-group.com'], + headerName: 'X-BAUERGROUP-Auth', + secretValue: SECRET, + syncSecret, + }; +} + +function makeConfig(syncSecret: boolean): Config { + return { + schemaVersion: 1, + masterEnabled: true, + uiLocale: 'de', + rules: [makeRule('r1', syncSecret)], + }; +} + +describe('loadConfig / saveConfig', () => { + beforeEach(() => fakeBrowser.reset()); + + it('returns defaults when storage is empty', async () => { + const config = await loadConfig(); + expect(config.rules).toEqual([]); + expect(config.masterEnabled).toBe(true); + }); + + it('round-trips a synced-secret rule through sync storage', async () => { + await saveConfig(makeConfig(true)); + const loaded = await loadConfig(); + expect(loaded.rules[0]?.secretValue).toBe(SECRET); + expect(loaded.rules[0]?.syncSecret).toBe(true); + }); + + it('keeps a device-local secret out of sync storage', async () => { + await saveConfig(makeConfig(false)); + + const sync = (await fakeBrowser.storage.sync.get(null)) as Record; + expect(JSON.stringify(sync)).not.toContain(SECRET); + expect(JSON.stringify(sync)).toContain(ZERO_GUID); + + const local = (await fakeBrowser.storage.local.get(null)) as Record; + expect(local['secret:r1']).toBe(SECRET); + + const loaded = await loadConfig(); + expect(loaded.rules[0]?.secretValue).toBe(SECRET); + }); + + it('substitutes the null GUID when the device-local secret is missing', async () => { + await saveConfig(makeConfig(false)); + await fakeBrowser.storage.local.remove('secret:r1'); + const loaded = await loadConfig(); + expect(loaded.rules[0]?.secretValue).toBe(ZERO_GUID); + }); + + it('drops the device-local copy when a rule switches back to synced', async () => { + await saveConfig(makeConfig(false)); + await saveConfig(makeConfig(true)); + const local = (await fakeBrowser.storage.local.get(null)) as Record; + expect(local['secret:r1']).toBeUndefined(); + }); + + it('removes stale sync keys when the rule set shrinks', async () => { + const many: Config = { + ...makeConfig(true), + rules: Array.from({ length: 300 }, (_, i) => makeRule(`r${i}`, true)), + }; + await saveConfig(many); + const beforeKeys = Object.keys(await fakeBrowser.storage.sync.get(null)); + + await saveConfig(makeConfig(true)); + const afterKeys = Object.keys(await fakeBrowser.storage.sync.get(null)); + + expect(beforeKeys.length).toBeGreaterThan(afterKeys.length); + const loaded = await loadConfig(); + expect(loaded.rules).toHaveLength(1); + }); +}); + +describe('onConfigChanged', () => { + beforeEach(() => fakeBrowser.reset()); + + it('fires on sync changes and stops after unsubscribe', async () => { + let calls = 0; + const unsubscribe = onConfigChanged(() => { + calls += 1; + }); + + await fakeBrowser.storage.sync.set({ anything: 1 }); + expect(calls).toBeGreaterThan(0); + + const afterSubscribe = calls; + unsubscribe(); + await fakeBrowser.storage.sync.set({ anything: 2 }); + expect(calls).toBe(afterSubscribe); + }); + + it('fires on local changes too', async () => { + let calls = 0; + const unsubscribe = onConfigChanged(() => { + calls += 1; + }); + + await fakeBrowser.storage.local.set({ 'secret:r1': SECRET }); + expect(calls).toBeGreaterThan(0); + unsubscribe(); + }); +}); diff --git a/src/lib/storage/index.ts b/src/lib/storage/index.ts new file mode 100644 index 0000000..d9d98a3 --- /dev/null +++ b/src/lib/storage/index.ts @@ -0,0 +1,59 @@ +import { browser } from 'wxt/browser'; +import { type Config, parseConfig } from '../schema/config'; +import { planWrite, readConfig } from './shard'; + +const LOCAL_SECRET_PREFIX = 'secret:'; +const ZERO_GUID = '00000000-0000-0000-0000-000000000000'; + +/** Load and validate the user config from sync storage, hydrating device-local secrets. */ +export async function loadConfig(): Promise { + const syncRaw = (await browser.storage.sync.get(null)) as Record; + const config = parseConfig(readConfig(syncRaw)); + + const localRaw = (await browser.storage.local.get(null)) as Record; + for (const rule of config.rules) { + if (!rule.syncSecret) { + const local = localRaw[LOCAL_SECRET_PREFIX + rule.id]; + rule.secretValue = typeof local === 'string' && local ? local : ZERO_GUID; + } + } + return config; +} + +/** Persist the user config: synced rules to sync storage, device-local secrets to local. */ +export async function saveConfig(config: Config): Promise { + const localSet: Record = {}; + const localRemove: string[] = []; + + const syncedConfig: Config = { + ...config, + rules: config.rules.map((rule) => { + if (rule.syncSecret) { + localRemove.push(LOCAL_SECRET_PREFIX + rule.id); + return rule; + } + // Keep the secret out of the synced copy; store it device-locally instead. + localSet[LOCAL_SECRET_PREFIX + rule.id] = rule.secretValue; + return { ...rule, secretValue: ZERO_GUID }; + }), + }; + + const existingKeys = Object.keys( + (await browser.storage.sync.get(null)) as Record, + ); + const { set, removeKeys } = planWrite(syncedConfig, existingKeys); + + if (removeKeys.length) await browser.storage.sync.remove(removeKeys); + await browser.storage.sync.set(set); + if (Object.keys(localSet).length) await browser.storage.local.set(localSet); + if (localRemove.length) await browser.storage.local.remove(localRemove); +} + +/** Subscribe to any change in user (sync/local) or managed (policy) storage. */ +export function onConfigChanged(callback: () => void): () => void { + const listener = (_changes: unknown, areaName: string) => { + if (areaName === 'sync' || areaName === 'local' || areaName === 'managed') callback(); + }; + browser.storage.onChanged.addListener(listener); + return () => browser.storage.onChanged.removeListener(listener); +} diff --git a/src/lib/storage/managed.test.ts b/src/lib/storage/managed.test.ts new file mode 100644 index 0000000..4d4f567 --- /dev/null +++ b/src/lib/storage/managed.test.ts @@ -0,0 +1,50 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { fakeBrowser } from 'wxt/testing/fake-browser'; +import { loadManagedConfig, managedRulesToEffective, ManagedConfigSchema } from './managed'; + +const SECRET = '11111111-1111-1111-1111-111111111111'; + +describe('loadManagedConfig', () => { + beforeEach(() => fakeBrowser.reset()); + + it('returns null when no policy is provisioned', async () => { + await expect(loadManagedConfig()).resolves.toBeNull(); + }); + + it('parses a provisioned policy and defaults the header name', async () => { + await fakeBrowser.storage.managed.set({ + masterEnabled: true, + rules: [{ label: 'Corp', domainPatterns: ['*.bauer-group.com'], secretValue: SECRET }], + }); + const config = await loadManagedConfig(); + expect(config?.masterEnabled).toBe(true); + expect(config?.rules).toHaveLength(1); + expect(config?.rules[0]?.headerName).toBe('X-BAUERGROUP-Auth'); + expect(config?.rules[0]?.enabled).toBe(true); + }); + + it('returns null for a policy that fails validation', async () => { + await fakeBrowser.storage.managed.set({ rules: [{ label: '', domainPatterns: [] }] }); + await expect(loadManagedConfig()).resolves.toBeNull(); + }); +}); + +describe('managedRulesToEffective', () => { + it('synthesises ids and tags every rule as managed', () => { + const managed = ManagedConfigSchema.parse({ + rules: [ + { label: 'A', domainPatterns: ['a.example.com'], secretValue: SECRET }, + { label: 'B', domainPatterns: ['b.example.com'], secretValue: SECRET, enabled: false }, + ], + }); + const effective = managedRulesToEffective(managed); + expect(effective.map((rule) => rule.id)).toEqual(['managed-0', 'managed-1']); + expect(effective.every((rule) => rule.source === 'managed')).toBe(true); + expect(effective.every((rule) => rule.syncSecret)).toBe(true); + expect(effective[1]?.enabled).toBe(false); + }); + + it('returns an empty list when the policy has no rules', () => { + expect(managedRulesToEffective(ManagedConfigSchema.parse({}))).toEqual([]); + }); +}); diff --git a/src/lib/storage/managed.ts b/src/lib/storage/managed.ts new file mode 100644 index 0000000..773e7c0 --- /dev/null +++ b/src/lib/storage/managed.ts @@ -0,0 +1,54 @@ +import { browser } from 'wxt/browser'; +import { z } from 'zod'; +import { + DomainPatternSchema, + HeaderNameSchema, + SecretSchema, + type EffectiveRule, +} from '../schema/config'; + +/** + * Shape of a single rule provisioned by enterprise policy (GPO/MDM) via + * chrome.storage.managed. Admins don't set internal ids, so they are omitted here + * and synthesised on read. + */ +export const ManagedRuleSchema = z.object({ + label: z.string().trim().min(1).max(60), + domainPatterns: z.array(DomainPatternSchema).min(1), + headerName: HeaderNameSchema.default('X-BAUERGROUP-Auth'), + secretValue: SecretSchema, + enabled: z.boolean().default(true), +}); + +export const ManagedConfigSchema = z.object({ + masterEnabled: z.boolean().optional(), + rules: z.array(ManagedRuleSchema).default([]), +}); +export type ManagedConfig = z.infer; + +/** Read the (read-only) managed policy. Returns null when no/invalid policy is present. */ +export async function loadManagedConfig(): Promise { + try { + const raw = (await browser.storage.managed.get(null)) as Record; + if (!raw || Object.keys(raw).length === 0) return null; + const parsed = ManagedConfigSchema.safeParse(raw); + return parsed.success ? parsed.data : null; + } catch { + // Managed storage is unavailable on unmanaged devices — not an error. + return null; + } +} + +/** Map managed rules to effective rules tagged as read-only/managed for the UI + engine. */ +export function managedRulesToEffective(managed: ManagedConfig): EffectiveRule[] { + return managed.rules.map((rule, index) => ({ + id: `managed-${index}`, + enabled: rule.enabled, + label: rule.label, + domainPatterns: rule.domainPatterns, + headerName: rule.headerName, + secretValue: rule.secretValue, + syncSecret: true, + source: 'managed', + })); +} diff --git a/src/lib/storage/shard.test.ts b/src/lib/storage/shard.test.ts new file mode 100644 index 0000000..5be56cb --- /dev/null +++ b/src/lib/storage/shard.test.ts @@ -0,0 +1,48 @@ +import { describe, it, expect } from 'vitest'; +import { planWrite, readConfig, CONFIG_KEY, RULE_PREFIX } from './shard'; +import type { Config } from '../schema/config'; + +function makeConfig(ruleCount: number): Config { + return { + schemaVersion: 1, + masterEnabled: true, + uiLocale: 'de', + rules: Array.from({ length: ruleCount }, (_, i) => ({ + id: `id-${i}`, + enabled: true, + label: `Rule ${i}`, + domainPatterns: ['app.bauer-group.com'], + headerName: 'X-BAUERGROUP-Auth', + secretValue: '11111111-1111-1111-1111-111111111111', + syncSecret: true, + })), + }; +} + +describe('planWrite / readConfig', () => { + it('stores a small config as one item and round-trips', () => { + const cfg = makeConfig(2); + const { set, removeKeys } = planWrite(cfg, []); + expect(Object.keys(set)).toEqual([CONFIG_KEY]); + expect(removeKeys).toEqual([]); + const restored = readConfig(set as Record) as Config; + expect(restored.rules).toEqual(cfg.rules); + expect(restored.masterEnabled).toBe(true); + }); + + it('shards a large config into per-rule items and round-trips', () => { + const cfg = makeConfig(400); + const { set } = planWrite(cfg, []); + expect(Object.keys(set).length).toBeGreaterThan(1); + expect(set[RULE_PREFIX + 'id-0']).toBeDefined(); + const restored = readConfig(set as Record) as Config; + expect(restored.rules).toHaveLength(400); + expect(restored.rules[0]).toMatchObject({ id: 'id-0' }); + }); + + it('removes stale shard keys when shrinking to a single item', () => { + const cfg = makeConfig(1); + const { removeKeys } = planWrite(cfg, [CONFIG_KEY, RULE_PREFIX + 'old']); + expect(removeKeys).toContain(RULE_PREFIX + 'old'); + }); +}); diff --git a/src/lib/storage/shard.ts b/src/lib/storage/shard.ts new file mode 100644 index 0000000..c0a0b7e --- /dev/null +++ b/src/lib/storage/shard.ts @@ -0,0 +1,68 @@ +import type { Config } from '../schema/config'; + +/** + * chrome.storage.sync enforces ~8 KB per item. We store the whole config under a + * single `config` item by default (atomic writes), and transparently shard into + * one item per rule only when the serialized size approaches the limit. + */ +export const SYNC_ITEM_SAFE_BYTES = 7000; +export const CONFIG_KEY = 'config'; +export const RULE_PREFIX = 'rule:'; + +export interface PlannedWrite { + /** Items to write via storage.sync.set. */ + set: Record; + /** Stale keys to remove via storage.sync.remove. */ + removeKeys: string[]; +} + +function byteLength(value: unknown): number { + return JSON.stringify(value).length; +} + +/** Decide how to persist a config across sync items, given the currently stored keys. */ +export function planWrite(config: Config, existingKeys: string[]): PlannedWrite { + const whole = { ...config, _sharded: false as const }; + + if (byteLength(whole) <= SYNC_ITEM_SAFE_BYTES) { + // Single item — drop any leftover shards from a previous large config. + const removeKeys = existingKeys.filter((k) => k.startsWith(RULE_PREFIX)); + return { set: { [CONFIG_KEY]: whole }, removeKeys }; + } + + // Sharded — meta item holds everything except the rule bodies. + const meta = { + schemaVersion: config.schemaVersion, + masterEnabled: config.masterEnabled, + uiLocale: config.uiLocale, + _sharded: true as const, + ruleIds: config.rules.map((r) => r.id), + }; + const set: Record = { [CONFIG_KEY]: meta }; + for (const rule of config.rules) set[RULE_PREFIX + rule.id] = rule; + + const keep = new Set(config.rules.map((r) => RULE_PREFIX + r.id)); + const removeKeys = existingKeys.filter((k) => k.startsWith(RULE_PREFIX) && !keep.has(k)); + return { set, removeKeys }; +} + +/** Reassemble a raw sync-storage snapshot back into a config-shaped object. */ +export function readConfig(raw: Record): unknown { + const meta = raw[CONFIG_KEY] as Record | undefined; + if (!meta) return undefined; + + if (meta['_sharded'] === true) { + const ruleIds = (meta['ruleIds'] as string[]) ?? []; + const rules = ruleIds.map((id) => raw[RULE_PREFIX + id]).filter((r) => r != null); + return { + schemaVersion: meta['schemaVersion'], + masterEnabled: meta['masterEnabled'], + uiLocale: meta['uiLocale'], + rules, + }; + } + + const { _sharded, ...config } = meta; + void _sharded; + return config; +} diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000..2c50d98 --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,7 @@ +import { clsx, type ClassValue } from 'clsx'; +import { twMerge } from 'tailwind-merge'; + +/** Merge Tailwind class names, resolving conflicts (shadcn convention). */ +export function cn(...inputs: ClassValue[]): string { + return twMerge(clsx(inputs)); +} diff --git a/src/manifest.json b/src/manifest.json deleted file mode 100644 index f8fb890..0000000 --- a/src/manifest.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "manifest_version": 3, - "name": "BAUER GROUP - Header Authenticator (for Applications)", - "description": "Sending an Authentication Header for Application Access that are Secured with Headers Secret", - "icons": { - "16": "resources/icons/16x16.png", - "19": "resources/icons/19x19.png", - "24": "resources/icons/24x24.png", - "32": "resources/icons/32x32.png", - "48": "resources/icons/48x48.png", - "64": "resources/icons/64x64.png", - "128": "resources/icons/128x128.png" - }, - "version": "0.0.24", - "permissions": [ - "storage", - "activeTab", - "declarativeNetRequest", - "declarativeNetRequestFeedback" - ], - "background": { - "service_worker": "background.js" - }, - "content_scripts": [ - { - "matches": [ - "https://*.app.bauer-group.com/*" - ], - "js": [ - "contentScript.js" - ], - "run_at": "document_end" - } - ], - "action": { - "default_popup": "options.html", - "default_title": "BAUER GROUP - HeaderAuthenticator" - }, - "host_permissions": [ - "https://*.app.bauer-group.com/*", - "wss://*.app.bauer-group.com/*" - ], - "content_security_policy": { - "extension_pages": "script-src 'self'; object-src 'self'" - } -} \ No newline at end of file diff --git a/src/options.html b/src/options.html deleted file mode 100644 index 7427ff0..0000000 --- a/src/options.html +++ /dev/null @@ -1,67 +0,0 @@ - - - - Options Page - - - -
- - - - -
- - - - diff --git a/src/options.ts b/src/options.ts deleted file mode 100644 index 614c2d9..0000000 --- a/src/options.ts +++ /dev/null @@ -1,30 +0,0 @@ - -document.addEventListener('DOMContentLoaded', () => { - const authSecretInput = document.getElementById('authSecret') as HTMLInputElement; - const saveButton = document.getElementById('save'); - - // Function to load and set the stored authSecret - const loadStoredAuthSecret = () => { - chrome.storage.sync.get('authSecret', (result) => { - if (result.authSecret) { - authSecretInput.value = result.authSecret; - } - }); - }; - - // Load the stored authSecret when the page loads - loadStoredAuthSecret(); - - // Save changes made - saveButton?.addEventListener('click', () => { - const authSecretInput = document.getElementById('authSecret') as HTMLInputElement; - const authSecret = authSecretInput?.value; - - if (authSecret !== undefined) { - chrome.storage.sync.set({ authSecret: authSecret }, () => { - console.log('Updated Authentication Secret: ' + authSecret); - window.close(); - }); - } - }); -}); diff --git a/src/public/_locales/de/messages.json b/src/public/_locales/de/messages.json new file mode 100644 index 0000000..f73e0d0 --- /dev/null +++ b/src/public/_locales/de/messages.json @@ -0,0 +1,11 @@ +{ + "extName": { + "message": "BAUER GROUP - Header Authenticator" + }, + "extDescription": { + "message": "Sendet einen konfigurierbaren Authentifizierungs-Header (Secret-GUID) an geschützte Dienste." + }, + "extActionTitle": { + "message": "BAUER GROUP - Header Authenticator" + } +} diff --git a/src/public/_locales/en/messages.json b/src/public/_locales/en/messages.json new file mode 100644 index 0000000..12181c1 --- /dev/null +++ b/src/public/_locales/en/messages.json @@ -0,0 +1,11 @@ +{ + "extName": { + "message": "BAUER GROUP - Header Authenticator" + }, + "extDescription": { + "message": "Injects a configurable authentication header (secret GUID) into requests to protected services." + }, + "extActionTitle": { + "message": "BAUER GROUP - Header Authenticator" + } +} diff --git a/src/public/managed-schema.json b/src/public/managed-schema.json new file mode 100644 index 0000000..b1ae1b6 --- /dev/null +++ b/src/public/managed-schema.json @@ -0,0 +1,45 @@ +{ + "type": "object", + "title": "BAUER GROUP Header Authenticator – Enterprise Policy", + "description": "Centrally provisioned protection rules (GPO/MDM). Read-only for the user.", + "properties": { + "masterEnabled": { + "type": "boolean", + "title": "Master switch", + "description": "Globally enable/disable header injection on managed devices." + }, + "rules": { + "type": "array", + "title": "Protection rules", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string", + "title": "Display name" + }, + "domainPatterns": { + "type": "array", + "title": "Domains", + "items": { "type": "string" }, + "description": "e.g. app.bauer-group.com or *.bauer-group.com" + }, + "headerName": { + "type": "string", + "title": "Header name", + "description": "Defaults to X-BAUERGROUP-Auth" + }, + "secretValue": { + "type": "string", + "title": "Secret (GUID)" + }, + "enabled": { + "type": "boolean", + "title": "Enabled" + } + }, + "required": ["label", "domainPatterns", "secretValue"] + } + } + } +} diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/tsconfig.json b/tsconfig.json index 02e8d68..9da623c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,20 +1,22 @@ { - "compilerOptions": { - "baseUrl": "./", - "rootDir": "./src", - "outDir": "./dist", - "sourceMap": true, - "strict": true, - "target": "es6", - "moduleResolution": "node", - "module": "es2020", - "lib": ["DOM", "ES2020"], - "types": ["chrome"] - }, - "include": [ - "src/**/*.ts" - ], - "exclude": [ - "node_modules" - ] + "extends": "./.wxt/tsconfig.json", + "compilerOptions": { + "jsx": "react-jsx", + "jsxImportSource": "react", + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + "noImplicitOverride": true, + "verbatimModuleSyntax": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.tsx", + "scripts/**/*.mjs", + "wxt.config.ts", + "vitest.config.ts", + "eslint.config.js" + ] } diff --git a/tsconfig.tools.json b/tsconfig.tools.json deleted file mode 100644 index 2105cd9..0000000 --- a/tsconfig.tools.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "compilerOptions": { - "outDir": "./dist-zip", - "strict": true - }, - "include": [ - "**/*.ts" - ], - "exclude": [ - "node_modules", - "src/**/*.ts" - ] -} \ No newline at end of file diff --git a/updateVersion.ts b/updateVersion.ts deleted file mode 100644 index 042c9e8..0000000 --- a/updateVersion.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { readFileSync, writeFileSync } from 'fs'; - -interface PackageJson { - version?: string; -} - -const filesToUpdate: string[] = ['src/manifest.json']; - -filesToUpdate.forEach((file) => { - try { - // Read the current file - const content: string = readFileSync(file, 'utf8'); - const jsonContent: PackageJson = JSON.parse(content); - - // Increment the version - if (jsonContent.version) { - const versionParts: string[] = jsonContent.version.split('.'); - versionParts[versionParts.length - 1] = (parseInt(versionParts[versionParts.length - 1]) + 1).toString(); - jsonContent.version = versionParts.join('.'); - } - - // Write the updated content back to the file - writeFileSync(file, JSON.stringify(jsonContent, null, 2)); - console.log(`Updated version in ${file} to ${jsonContent.version}`); - } catch (error) { - console.error(`Error processing ${file}: ${error}`); - } -}); diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..3021df5 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,37 @@ +import { defineConfig } from 'vitest/config'; +import { WxtVitest } from 'wxt/testing/vitest-plugin'; + +// WxtVitest() wires up WXT's aliases and auto-mocks `wxt/browser` with +// @webext-core/fake-browser, so the modules that import the browser global are +// testable without hand-written mocks. +// +// The include glob covers .tsx as well as .ts: it previously matched only +// `src/**/*.test.ts`, so a component test would have been collected by nothing +// and silently never run — a trap that surfaces the first time someone writes +// one and believes the green result. +// +// The environment stays `node`. jsdom costs ~10s of setup PER TEST FILE here +// (measured: 1.3s -> 60.7s across six files) and nothing currently needs it — +// fakeBrowser is a pure in-memory implementation. A test that does need a DOM +// opts in per file with a docblock on line 1: +// +// // @vitest-environment jsdom +// +// jsdom is installed and ready for exactly that. +export default defineConfig({ + plugins: [WxtVitest()], + test: { + environment: 'node', + include: ['src/**/*.test.{ts,tsx}'], + restoreMocks: true, + coverage: { + provider: 'v8', + include: ['src/lib/**'], + // Exercised through components rather than unit tests; including them + // would drag the lib threshold down without adding signal. + exclude: ['src/lib/hooks/**', 'src/lib/i18n/**'], + reporter: ['text', 'lcov'], + thresholds: { lines: 80, functions: 80, branches: 80, statements: 80 }, + }, + }, +}); diff --git a/wxt.config.ts b/wxt.config.ts new file mode 100644 index 0000000..efc1da3 --- /dev/null +++ b/wxt.config.ts @@ -0,0 +1,44 @@ +import { defineConfig } from 'wxt'; +import tailwindcss from '@tailwindcss/vite'; + +/** + * Public key of the extension signing key pair (`.keys/key.pem`). + * Pinning it gives a STABLE extension ID across "load unpacked", the signed + * `.crx` and the Chrome Web Store build — required for enterprise force-install + * and consistent `chrome.storage` identity. + * + * The matching PRIVATE key is never committed (see .gitignore / .keys/) and is + * provided to CI as the `EXTENSION_PRIVATE_KEY` secret. + * + * Resulting extension ID: jncjhkagdjiiohjfmbpmlemdchbkjaib + */ +const EXTENSION_KEY = + 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsqq4sZrp2Vca8EE0DbBOCGiHTUc6nwqYinW8hOFYLhGrntlxdOuj009ACpSIvVmBPkiK9G+JFxroT79p0bv9JK4qOKSrh7iFjaelJ4Xlojedghz668nZbCBcl6gvKqCfnroMpJQbnZZS7ZTfdBW8C97aP5x1ixe+bW1hqX29SmBHgrPmYgs/Vv1HBlv6FUE93ZC3whkL9YGungjNC5ME0PozOcjye4ScfArZZjXDP+ElD9lWb9S+1xpfgfBTX+PFKNcb1xhouq+iH2q6LSjsv5MzjOJT4Gw2gNiOe+gB9CgRdOAHhdwmn2sLg0Dh05WZdjhCbRGtAwtAw9xS/BAiVwIDAQAB'; + +// https://wxt.dev/api/config.html +export default defineConfig({ + srcDir: 'src', + publicDir: 'src/public', + modules: ['@wxt-dev/module-react', '@wxt-dev/auto-icons'], + vite: () => ({ + plugins: [tailwindcss()], + }), + manifest: { + name: '__MSG_extName__', + description: '__MSG_extDescription__', + default_locale: 'en', + key: EXTENSION_KEY, + permissions: ['storage', 'declarativeNetRequest', 'declarativeNetRequestFeedback'], + // Canonical corporate domains work out of the box (no permission prompt). + host_permissions: ['https://*.app.bauer-group.com/*', 'wss://*.app.bauer-group.com/*'], + // User-added domains request access at runtime (narrower than ). + optional_host_permissions: ['https://*/*', 'wss://*/*'], + action: { + default_title: '__MSG_extActionTitle__', + }, + // Enterprise policy (GPO/MDM) config push via chrome.storage.managed. + storage: { + managed_schema: 'managed-schema.json', + }, + }, +}); diff --git a/zipWithVersion.ts b/zipWithVersion.ts deleted file mode 100644 index f5c07c6..0000000 --- a/zipWithVersion.ts +++ /dev/null @@ -1,28 +0,0 @@ -import * as fs from 'fs'; -import * as archiver from 'archiver'; - -// Read the version from package.json -const manifestJson = JSON.parse(fs.readFileSync('./dist/manifest.json', 'utf8')); -const version = manifestJson.version; - -// Create a zip file name with the version number -const zipFileName = `./dist-zip/bauergroup-header-authenticator_v${version}.zip`; - -// Create a stream to write files to the zip file -const output = fs.createWriteStream(zipFileName); -const archive = archiver('zip', { zlib: { level: 9 }}); - -// Listen for the 'close' event on the stream -output.on('close', function() { - console.log('ZIP file with ' + archive.pointer() + ' total bytes created'); - console.log('Archiver has been finalized and the output file descriptor has closed.'); -}); - -// Pipe the archive data to the file -archive.pipe(output); - -// Append files from the 'dist' directory -archive.directory('dist/', false); - -// Finalize the archive -archive.finalize();