diff --git a/.env.example b/.env.example
index a582588..27aa99d 100644
--- a/.env.example
+++ b/.env.example
@@ -1,2 +1,4 @@
VITE_API_BASE_URL=https://localhost:8080
VITE_APP_BASE_URL=/
+VITE_OBSERVABILITY_URL=https://observability.example.com/events
+VITE_RELEASE_ID=local
diff --git a/.env.test b/.env.test
new file mode 100644
index 0000000..de4a16d
--- /dev/null
+++ b/.env.test
@@ -0,0 +1,2 @@
+VITE_API_BASE_URL=http://localhost
+VITE_APP_BASE_URL=/
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..530bbde
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,11 @@
+* text=auto
+
+*.css text eol=lf
+*.html text eol=lf
+*.js text eol=lf
+*.json text eol=lf
+*.md text eol=lf
+*.ts text eol=lf
+*.tsx text eol=lf
+*.yml text eol=lf
+*.yaml text eol=lf
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 0000000..cc2e19c
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1,32 @@
+# Default reviewer for all repository changes.
+* @sepaseh
+
+# Require explicit review for automation and dependency policy changes.
+/.github/ @sepaseh
+/package.json @sepaseh
+/package-lock.json @sepaseh
+
+# Release policy, version state, and generated release notes.
+/.release-please-manifest.json @sepaseh
+/CHANGELOG.md @sepaseh
+/docs/release-operations.md @sepaseh
+/docs/releasing.md @sepaseh
+/release-please-config.json @sepaseh
+
+# Production-like staging gate.
+/.github/workflows/staging.yml @sepaseh
+/docs/staging.md @sepaseh
+/staging.config.ts @sepaseh
+/staging/ @sepaseh
+
+# Security model, review evidence, and active scanning.
+/.github/workflows/dast.yml @sepaseh
+/docs/security/ @sepaseh
+/SECURITY.md @sepaseh
+
+# Test-effectiveness and frontend/backend compatibility gates.
+/.github/workflows/mutation.yml @sepaseh
+/contract/ @sepaseh
+/docs/contract-testing.md @sepaseh
+/stryker.config.json @sepaseh
+/vitest.contract.config.ts @sepaseh
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..66eecc1
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,48 @@
+version: 2
+
+updates:
+ - package-ecosystem: npm
+ directory: /
+ schedule:
+ interval: weekly
+ day: monday
+ time: "09:00"
+ timezone: Asia/Tehran
+ open-pull-requests-limit: 10
+ reviewers:
+ - sepaseh
+ commit-message:
+ prefix: chore
+ include: scope
+ groups:
+ production-minor-and-patch:
+ dependency-type: production
+ update-types:
+ - minor
+ - patch
+ development-minor-and-patch:
+ dependency-type: development
+ update-types:
+ - minor
+ - patch
+
+ - package-ecosystem: github-actions
+ directory: /
+ schedule:
+ interval: weekly
+ day: monday
+ time: "09:00"
+ timezone: Asia/Tehran
+ open-pull-requests-limit: 5
+ reviewers:
+ - sepaseh
+ commit-message:
+ prefix: chore
+ include: scope
+ groups:
+ actions-minor-and-patch:
+ patterns:
+ - "*"
+ update-types:
+ - minor
+ - patch
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 33e291a..1fa9486 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -17,71 +17,139 @@ permissions:
contents: read
env:
- NODE_VERSION: 22
+ NODE_VERSION: 24.18.0
jobs:
check:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
+ with:
+ fetch-depth: 0
+ persist-credentials: false
- - uses: actions/setup-node@v4
+ - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: ${{ env.NODE_VERSION }}
cache: npm
- run: npm ci
+ - run: npm run audit
+
- run: npm run typecheck
- run: npm run lint
+ - run: npm run format:check
+
- run: npm run test:coverage
+ - run: npm run test:contract
+
+ - if: ${{ !cancelled() }}
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
+ with:
+ name: consumer-contracts-${{ github.sha }}
+ path: pacts
+ retention-days: 14
+
+ - name: Validate SonarQube configuration
+ if: >-
+ github.event_name != 'pull_request' ||
+ github.event.pull_request.head.repo.full_name == github.repository
+ env:
+ SONAR_HOST_URL: ${{ vars.SONAR_HOST_URL }}
+ SONAR_PROJECT_KEY: ${{ vars.SONAR_PROJECT_KEY }}
+ SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+ run: |
+ missing=()
+
+ [[ -z "$SONAR_HOST_URL" ]] && missing+=("SONAR_HOST_URL")
+ [[ -z "$SONAR_PROJECT_KEY" ]] && missing+=("SONAR_PROJECT_KEY")
+ [[ -z "$SONAR_TOKEN" ]] && missing+=("SONAR_TOKEN")
+
+ if (( ${#missing[@]} > 0 )); then
+ missing_list=$(IFS=,; echo "${missing[*]}")
+ echo "::error title=Missing SonarQube configuration::Configure these repository settings: $missing_list"
+ exit 1
+ fi
+
+ - name: SonarQube scan
+ if: >-
+ github.event_name != 'pull_request' ||
+ github.event.pull_request.head.repo.full_name == github.repository
+ uses: SonarSource/sonarqube-scan-action@7006c4492b2e0ee0f816d36501671557c97f5995 # v8.1.0
+ env:
+ SONAR_HOST_URL: ${{ vars.SONAR_HOST_URL }}
+ SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+ SONAR_PROJECT_KEY: ${{ vars.SONAR_PROJECT_KEY }}
+ with:
+ args: -Dsonar.projectKey=${{ env.SONAR_PROJECT_KEY }}
+
- run: npm run knip
build:
needs: check
runs-on: ubuntu-latest
+ env:
+ VITE_API_BASE_URL: https://api.example.com
+ VITE_APP_BASE_URL: /
+ VITE_RELEASE_ID: ${{ github.sha }}
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
+ with:
+ persist-credentials: false
- - uses: actions/setup-node@v4
+ - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: ${{ env.NODE_VERSION }}
cache: npm
- run: npm ci
- - run: npm run build
+ - run: npm run performance
- - uses: actions/upload-artifact@v4
+ - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: dist-${{ github.sha }}
path: dist
+ - if: ${{ !cancelled() }}
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
+ with:
+ name: lighthouse-${{ github.sha }}
+ path: .lighthouseci
+ retention-days: 14
+
e2e:
needs: check
runs-on: ubuntu-latest
+ env:
+ VITE_API_BASE_URL: https://api.example.com
+ VITE_APP_BASE_URL: /
+ VITE_RELEASE_ID: ${{ github.sha }}
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
+ with:
+ persist-credentials: false
- - uses: actions/setup-node@v4
+ - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: ${{ env.NODE_VERSION }}
cache: npm
- run: npm ci
- - run: npx playwright install --with-deps chromium
+ - run: npx playwright install --with-deps chromium firefox webkit
- run: npm run test:e2e
- if: ${{ !cancelled() }}
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: playwright-report-${{ github.sha }}
path: |
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
new file mode 100644
index 0000000..c002bf6
--- /dev/null
+++ b/.github/workflows/codeql.yml
@@ -0,0 +1,37 @@
+name: CodeQL
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+ branches:
+ - main
+ schedule:
+ - cron: "30 3 * * 1"
+ workflow_dispatch:
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
+
+permissions:
+ contents: read
+
+jobs:
+ analyze:
+ name: Analyze JavaScript and TypeScript
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ security-events: write
+
+ steps:
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
+
+ - uses: github/codeql-action/init@f52b05f4acaaa234e44466e66d29050e135ea9ef # v4.36.0
+ with:
+ languages: javascript-typescript
+ queries: security-extended
+
+ - uses: github/codeql-action/analyze@f52b05f4acaaa234e44466e66d29050e135ea9ef # v4.36.0
diff --git a/.github/workflows/dast.yml b/.github/workflows/dast.yml
new file mode 100644
index 0000000..a16532c
--- /dev/null
+++ b/.github/workflows/dast.yml
@@ -0,0 +1,63 @@
+name: DAST
+
+on:
+ schedule:
+ - cron: "0 3 1 * *"
+ workflow_dispatch:
+
+concurrency:
+ cancel-in-progress: false
+ group: staging-dast
+
+permissions:
+ contents: read
+
+jobs:
+ zap:
+ environment: staging
+ runs-on: ubuntu-latest
+ timeout-minutes: 60
+ env:
+ DAST_ALLOWED_HOST: ${{ vars.STAGING_ALLOWED_HOST }}
+ DAST_PRODUCTION_HOST: ${{ vars.PRODUCTION_HOST }}
+ DAST_TARGET: ${{ vars.STAGING_BASE_URL }}
+
+ steps:
+ - name: Validate authorized target
+ shell: bash
+ run: |
+ node -e '
+ const value = process.env.DAST_TARGET;
+ const allowedHost = process.env.DAST_ALLOWED_HOST?.toLowerCase();
+ const productionHost = process.env.DAST_PRODUCTION_HOST?.toLowerCase();
+ if (!value) throw new Error("STAGING_BASE_URL is required");
+ if (!allowedHost) throw new Error("STAGING_ALLOWED_HOST is required");
+ if (!productionHost) throw new Error("PRODUCTION_HOST is required");
+ const url = new URL(value);
+ if (url.protocol !== "https:") {
+ throw new Error("STAGING_BASE_URL must use HTTPS");
+ }
+ if (url.username || url.password) {
+ throw new Error("STAGING_BASE_URL must not contain credentials");
+ }
+ if (url.hostname.toLowerCase() !== allowedHost) {
+ throw new Error("STAGING_BASE_URL host is not explicitly allowed");
+ }
+ if (url.hostname.toLowerCase() === productionHost) {
+ throw new Error("DAST must not target the production host");
+ }
+ '
+
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
+ with:
+ persist-credentials: false
+
+ - name: Run authorized ZAP full scan
+ uses: zaproxy/action-full-scan@bee4e5be916301228d2b1b2dcad73aab6fa6a25b # v0.13.0
+ with:
+ allow_issue_writing: false
+ artifact_name: staging-dast-${{ github.run_id }}
+ cmd_options: "-a"
+ docker_name: ghcr.io/zaproxy/zaproxy:2.17.0@sha256:c558ee87358911ab17278c70991e856f57793e115d9cd0f88ca475cf82907a1a
+ fail_action: true
+ target: ${{ env.DAST_TARGET }}
diff --git a/.github/workflows/deployment-smoke.yml b/.github/workflows/deployment-smoke.yml
new file mode 100644
index 0000000..c46aa80
--- /dev/null
+++ b/.github/workflows/deployment-smoke.yml
@@ -0,0 +1,67 @@
+name: Deployment smoke tests
+
+on:
+ deployment_status:
+ workflow_dispatch:
+ inputs:
+ application_url:
+ description: Deployed application base URL
+ required: true
+ type: string
+ api_health_url:
+ description: Public API health endpoint
+ required: true
+ type: string
+
+concurrency:
+ cancel-in-progress: true
+ group: deployment-smoke-${{ github.event.deployment.environment || inputs.application_url }}
+
+permissions:
+ contents: read
+
+jobs:
+ smoke:
+ if: >-
+ github.event_name == 'workflow_dispatch' ||
+ github.event.deployment_status.state == 'success'
+ runs-on: ubuntu-latest
+ env:
+ NODE_VERSION: 24.18.0
+ SMOKE_API_HEALTH_URL: >-
+ ${{ github.event_name == 'workflow_dispatch' &&
+ inputs.api_health_url || vars.SMOKE_API_HEALTH_URL }}
+ SMOKE_BASE_URL: >-
+ ${{ github.event_name == 'workflow_dispatch' &&
+ inputs.application_url ||
+ github.event.deployment_status.environment_url }}
+
+ steps:
+ - name: Validate smoke-test targets
+ run: |
+ if [[ -z "$SMOKE_BASE_URL" || -z "$SMOKE_API_HEALTH_URL" ]]; then
+ echo "SMOKE_BASE_URL and SMOKE_API_HEALTH_URL are required."
+ exit 1
+ fi
+
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
+
+ - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
+ with:
+ cache: npm
+ node-version: ${{ env.NODE_VERSION }}
+
+ - run: npm ci
+
+ - run: npx playwright install --with-deps chromium
+
+ - run: npm run test:smoke
+
+ - if: ${{ !cancelled() }}
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
+ with:
+ name: deployment-smoke-${{ github.run_id }}
+ path: |
+ playwright-report/smoke
+ test-results/smoke
+ retention-days: 14
diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml
new file mode 100644
index 0000000..13964fd
--- /dev/null
+++ b/.github/workflows/mutation.yml
@@ -0,0 +1,49 @@
+name: Mutation testing
+
+on:
+ schedule:
+ - cron: "0 4 * * 1"
+ workflow_dispatch:
+ pull_request:
+ branches:
+ - main
+ paths:
+ - "src/api/token.ts"
+ - "src/api/token.test.ts"
+ - "src/storage/**"
+ - "src/utils/**"
+ - "stryker.config.json"
+
+concurrency:
+ cancel-in-progress: true
+ group: mutation-${{ github.ref }}
+
+permissions:
+ contents: read
+
+jobs:
+ mutation:
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+ env:
+ NODE_VERSION: 24.18.0
+ VITE_API_BASE_URL: http://localhost
+
+ steps:
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
+
+ - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
+ with:
+ cache: npm
+ node-version: ${{ env.NODE_VERSION }}
+
+ - run: npm ci
+
+ - run: npm run test:mutation
+
+ - if: ${{ !cancelled() }}
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
+ with:
+ name: mutation-report-${{ github.run_id }}
+ path: reports/mutation
+ retention-days: 14
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..fec6310
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,23 @@
+name: Release
+
+on:
+ push:
+ branches:
+ - main
+ workflow_dispatch:
+
+permissions:
+ contents: write
+ pull-requests: write
+
+jobs:
+ release:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Create or update release
+ uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0
+ with:
+ config-file: release-please-config.json
+ manifest-file: .release-please-manifest.json
+ token: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/staging.yml b/.github/workflows/staging.yml
new file mode 100644
index 0000000..268ab10
--- /dev/null
+++ b/.github/workflows/staging.yml
@@ -0,0 +1,70 @@
+name: Staging validation
+
+on:
+ deployment_status:
+ workflow_dispatch:
+ inputs:
+ application_url:
+ description: Staging application base URL
+ required: true
+ type: string
+ api_health_url:
+ description: Staging API health endpoint
+ required: true
+ type: string
+
+concurrency:
+ cancel-in-progress: true
+ group: staging-${{ github.event.deployment.id || inputs.application_url }}
+
+permissions:
+ contents: read
+
+jobs:
+ validate:
+ if: >-
+ github.event_name == 'workflow_dispatch' ||
+ (github.event.deployment_status.state == 'success' &&
+ github.event.deployment.environment == 'staging')
+ runs-on: ubuntu-latest
+ env:
+ NODE_VERSION: 24.18.0
+ STAGING_API_HEALTH_URL: >-
+ ${{ github.event_name == 'workflow_dispatch' &&
+ inputs.api_health_url || vars.STAGING_API_HEALTH_URL }}
+ STAGING_BASE_URL: >-
+ ${{ github.event_name == 'workflow_dispatch' &&
+ inputs.application_url ||
+ github.event.deployment_status.environment_url }}
+
+ steps:
+ - name: Validate staging targets
+ run: |
+ if [[ -z "$STAGING_BASE_URL" || -z "$STAGING_API_HEALTH_URL" ]]; then
+ echo "STAGING_BASE_URL and STAGING_API_HEALTH_URL are required."
+ exit 1
+ fi
+
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
+ with:
+ persist-credentials: false
+
+ - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
+ with:
+ cache: npm
+ node-version: ${{ env.NODE_VERSION }}
+
+ - run: npm ci
+
+ - run: npx playwright install --with-deps chromium firefox webkit
+
+ - run: npm run test:staging
+
+ - if: ${{ !cancelled() }}
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
+ with:
+ name: staging-validation-${{ github.run_id }}
+ path: |
+ playwright-report/staging
+ test-results/staging
+ retention-days: 14
diff --git a/.gitignore b/.gitignore
index baba8a2..a4e70ee 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,7 +11,10 @@ node_modules
dist
dist-ssr
coverage
+.lighthouseci
playwright-report
+pacts
+reports/mutation
test-results
blob-report
*.local
diff --git a/.nvmrc b/.nvmrc
new file mode 100644
index 0000000..ca5c350
--- /dev/null
+++ b/.nvmrc
@@ -0,0 +1 @@
+24.18.0
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
new file mode 100644
index 0000000..37fcefa
--- /dev/null
+++ b/.release-please-manifest.json
@@ -0,0 +1,3 @@
+{
+ ".": "1.0.0"
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..04576a4
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,10 @@
+# Changelog
+
+Notable changes to Kernel are recorded here. Release entries after the initial
+stable baseline are generated from Conventional Commits by Release Please.
+
+## 1.0.0 (2026-07-30)
+
+- Established the first stable release baseline.
+- Added production, security, quality, accessibility, and browser test gates.
+- Added deployment smoke testing and structured observability.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..f26c374
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,66 @@
+# Contributing to Kernel
+
+Thank you for helping improve Kernel. Keep changes focused, tested, and easy to
+review.
+
+## Development setup
+
+Kernel uses Node.js 24.15.0 or newer in the Node.js 24 LTS line. If you use
+`nvm`, select the repository version before installing dependencies:
+
+```bash
+nvm use
+npm ci
+cp .env.example .env.local
+npm run dev
+```
+
+On Windows, copy `.env.example` to `.env.local` using your preferred file
+manager or shell.
+
+## Working on a change
+
+1. Create a branch from the latest `main`.
+2. Keep each pull request limited to one coherent change.
+3. Add or update tests for behavior changes.
+4. Do not commit secrets, local environment files, generated reports, or build
+ output.
+5. Update documentation when behavior, configuration, or public APIs change.
+
+Use Conventional Commits such as `feat:`, `fix:`, `test:`, `docs:`, and
+`chore:`. These messages drive semantic versioning and automated release notes,
+so pull request titles must also follow the format when squash merging. See
+[docs/releasing.md](docs/releasing.md) for the versioning and release policy.
+
+## Required checks
+
+Run the same core checks used by continuous integration before opening a pull
+request:
+
+```bash
+npm run typecheck
+npm run audit
+npm run lint
+npm run format:check
+npm run test:coverage
+npm run knip
+npm run build
+npm run test:e2e
+```
+
+Use `npm run format` and `npm run lint:fix` to apply safe automatic fixes.
+
+## Pull requests
+
+A pull request should:
+
+- Explain the problem and the chosen solution.
+- Link related issues when applicable.
+- Describe testing performed and any known limitations.
+- Include screenshots or recordings for visible interface changes.
+- Pass all required checks.
+- Avoid unrelated formatting or refactoring.
+
+At least one code-owner review is expected before merging. Security
+vulnerabilities must follow [SECURITY.md](SECURITY.md) instead of being
+disclosed in a public issue.
diff --git a/README.md b/README.md
index bbb8938..5d1ed36 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@ React starter kit upgraded as a dashboard-ready foundation under the `kernel` pr
| UI framework | React 19 + TypeScript |
| Build tool | Vite |
| Component library | Ant Design 6 |
-| Routing | `react-router-dom` 7 |
+| Routing | `react-router` 8.3 |
| Internationalization | `i18next` + `react-i18next` |
| HTTP | Axios |
| Date handling | Day.js + Jalaliday |
@@ -44,6 +44,7 @@ npm run build # typecheck and build for production
npm run preview # preview production build
npm run lint # run ESLint
npm run lint:fix # run ESLint with auto-fix
+npm run audit # check dependencies for high-severity vulnerabilities
npm run typecheck # typecheck only
npm run knip # detect unused files, exports, and dependencies
```
@@ -134,7 +135,7 @@ Provider-neutral AI assistant guidance lives in `.agents/`. It documents project
## Icons
-Icons are centralized in `src/components/Icon.tsx`. Add new icon names to the internal `iconMap`, then render them with:
+Icons are centralized in `src/components/icon/Icon.tsx`. Add new icon names to the internal `iconMap`, then render them with:
```tsx
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..a3b2277
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,48 @@
+# Security Policy
+
+## Supported versions
+
+Kernel is currently maintained on the `main` branch. Security fixes are applied
+to the latest code; older commits and forks are not separately supported.
+
+## Reporting a vulnerability
+
+Do not disclose suspected vulnerabilities in a public issue, discussion, pull
+request, or commit.
+
+Use the repository's
+[private vulnerability reporting](https://github.com/sepaseh/kernel/security/advisories/new)
+page to provide:
+
+- A description of the vulnerability and its potential impact.
+- Reproduction steps or a minimal proof of concept.
+- Affected routes, components, versions, or commits.
+- Any suggested mitigation, if known.
+
+If private vulnerability reporting is unavailable, contact the repository
+owner privately through their GitHub profile and request a secure communication
+channel. Do not include sensitive vulnerability details in the initial public
+contact.
+
+Reports will be acknowledged as soon as practical. The maintainer will validate
+the issue, determine severity and scope, coordinate a fix, and disclose it after
+users have had a reasonable opportunity to update. Please avoid public
+disclosure until that process is complete.
+
+## Security expectations
+
+Contributors must not commit credentials, tokens, private keys, production
+data, or populated environment files. Use `.env.example` for documented
+configuration and rotate any secret that may have been exposed.
+
+## Security assurance
+
+The maintained security program includes:
+
+- The [application threat model](docs/security/threat-model.md).
+- The [authentication and session review](docs/security/authentication-review.md).
+- The [security testing and review schedule](docs/security/reviews.md).
+- Monthly active DAST against an explicitly authorized staging environment.
+
+Security scan artifacts and unresolved vulnerability details are private.
+Automated results require human triage and do not replace manual review.
diff --git a/contract/api.contract.test.ts b/contract/api.contract.test.ts
new file mode 100644
index 0000000..00ddad0
--- /dev/null
+++ b/contract/api.contract.test.ts
@@ -0,0 +1,100 @@
+import path from "node:path";
+
+import { Pact } from "@pact-foundation/pact";
+import { afterEach, describe, expect, it, vi } from "vitest";
+
+const createPact = () =>
+ new Pact({
+ consumer: "kernel-web",
+ dir: path.resolve("pacts"),
+ provider: "kernel-api",
+ });
+
+const loadApiFor = async (baseUrl: string) => {
+ vi.resetModules();
+ vi.stubEnv("VITE_API_BASE_URL", baseUrl);
+
+ return Promise.all([
+ import("@/api/account"),
+ import("@/api/auth"),
+ import("@/api/token"),
+ ]);
+};
+
+afterEach(() => {
+ vi.unstubAllEnvs();
+});
+
+describe("Kernel API consumer contract", () => {
+ it("logs in with the expected request and token response", async () => {
+ await createPact()
+ .addInteraction()
+ .given("a valid user exists")
+ .uponReceiving("a login request")
+ .withRequest("POST", "/auth/login", (request) => {
+ request.jsonBody({
+ identifier: "ada",
+ password: "correct horse battery staple",
+ });
+ })
+ .willRespondWith(200, (response) => {
+ response.jsonBody({
+ access_token: "access-token",
+ });
+ })
+ .executeTest(async ({ url }) => {
+ const [, { login }] = await loadApiFor(url);
+
+ await expect(
+ login({
+ identifier: "ada",
+ password: "correct horse battery staple",
+ }),
+ ).resolves.toEqual({ accessToken: "access-token" });
+ });
+ });
+
+ it("reads the authenticated account response", async () => {
+ await createPact()
+ .addInteraction()
+ .given("an authenticated account exists")
+ .uponReceiving("an account details request")
+ .withRequest("GET", "/account/me", (request) => {
+ request.headers({
+ Authorization: "Bearer access-token",
+ });
+ })
+ .willRespondWith(200, (response) => {
+ response.jsonBody({
+ email: "ada@example.com",
+ first_name: "Ada",
+ id: "user-1",
+ is_system_admin: false,
+ last_name: "Lovelace",
+ mobile: "09121111111",
+ permissions: ["users.read"],
+ personnel_code: "200",
+ status: "active",
+ username: "ada",
+ });
+ })
+ .executeTest(async ({ url }) => {
+ const [{ getAccount }, , { setAccessToken }] = await loadApiFor(url);
+
+ setAccessToken("access-token");
+
+ await expect(getAccount()).resolves.toEqual({
+ email: "ada@example.com",
+ firstName: "Ada",
+ id: "user-1",
+ isSystemAdmin: false,
+ lastName: "Lovelace",
+ mobile: "09121111111",
+ permissions: ["users.read"],
+ personnelCode: "200",
+ status: "active",
+ username: "ada",
+ });
+ });
+ });
+});
diff --git a/docs/architecture.md b/docs/architecture.md
index 036d48e..079294f 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -6,8 +6,8 @@
The main runtime pieces are:
-- `src/providers/Core.tsx` for language, theme, current route, and authenticated user state.
-- `src/providers/Antd.tsx` for Ant Design locale, direction, and theme tokens.
+- `src/providers/core/Core.tsx` for language, theme, current route, and authenticated user state.
+- `src/providers/antd/Antd.tsx` for Ant Design locale, direction, and theme tokens.
- `src/Routes.tsx` for browser routing.
## Provider Responsibilities
@@ -63,6 +63,9 @@ to JavaScript.
## Styling
-Global styles live in `src/styles`. The app keeps `IRANSansX` first in the font stack, but font binaries are not committed because their public redistribution license is unclear. Copy licensed files into `public/fonts/iransans` locally and add matching `@font-face` rules in `src/styles/iransans.css` when needed.
+Global styles live in `src/styles`. The application bundles the Vazirmatn
+variable WOFF2 from `src/assets/fonts/vazirmatn` and uses weights 100–900 with
+`font-display: swap`. Vite fingerprints the local asset during production
+builds; the application does not depend on an external font provider.
Prefer Ant Design components and theme tokens for new UI so layout, spacing, RTL behavior, and dark/light themes stay consistent.
diff --git a/docs/branch-protection.md b/docs/branch-protection.md
new file mode 100644
index 0000000..767a113
--- /dev/null
+++ b/docs/branch-protection.md
@@ -0,0 +1,33 @@
+# Main Branch Protection
+
+The `main` branch is protected on GitHub. Changes must be merged through a pull
+request after the branch is current and these checks pass:
+
+| Required check | Coverage |
+| -------------------------------------------- | ------------------------------------------------------- |
+| `CI / check` | Audit, types, lint, format, tests, SonarQube, dead code |
+| `CI / build` | Validated production build |
+| `CI / e2e` | Critical browser journeys |
+| `CodeQL / Analyze JavaScript and TypeScript` | CodeQL security analysis |
+
+Protection also:
+
+- Applies to repository administrators.
+- Requires review conversations to be resolved.
+- Prevents force-pushes.
+- Prevents deletion of `main`.
+
+An approving review is not required for ordinary changes while the repository
+has only one active maintainer. Release pull requests follow the explicit
+approval process in the
+[release operations runbook](release-operations.md). Enable at least one
+approval and Code Owner review when another maintainer can review pull requests
+without blocking all development.
+
+Configure the GitHub `production` environment separately with required
+reviewers. Production deployment jobs must reference that environment so its
+approval gate applies after merge and before deployment.
+
+The SonarQube scan is part of the required `check` job. Configure the repository
+settings described in [Testing](testing.md#sonarqube) before treating the
+quality gate as active.
diff --git a/docs/contract-testing.md b/docs/contract-testing.md
new file mode 100644
index 0000000..fbb818a
--- /dev/null
+++ b/docs/contract-testing.md
@@ -0,0 +1,41 @@
+# Mutation and contract testing
+
+## Mutation testing
+
+`npm run test:mutation` uses Stryker to make controlled faults in the token,
+storage, error, and data-transformation modules. The existing unit tests must
+detect, or “kill,” at least 80% of generated mutants for the command to pass.
+Scores below 90% remain visible for improvement.
+
+The focused scope covers small, security-sensitive modules and keeps feedback
+practical. Expand it as dedicated unit coverage grows. Do not exclude a mutant
+only to raise the score; add an assertion or document why the mutation is
+equivalent to the original behavior.
+
+Mutation testing runs weekly and when its selected source or tests change.
+Reports are retained as private workflow artifacts.
+
+## Consumer contracts
+
+`npm run test:contract` exercises the real frontend API modules against a Pact
+mock provider. It verifies the request method, path, JSON body, wire-format
+field names, response shape, and the frontend's snake-case to camel-case
+transformation.
+
+The generated `pacts/kernel-web-kernel-api.json` contract is a CI artifact. The
+API repository must verify that contract against the candidate API before
+frontend or backend deployment. Publish it to the team's Pact Broker when one
+is configured, using the frontend commit SHA as the consumer version and the
+branch or release environment for deployment metadata.
+
+Changes are compatible only when:
+
+- Consumer contract generation passes.
+- The provider verifies every interaction.
+- The broker's deployment check reports that the frontend and API versions can
+ be deployed together.
+
+Add interactions when the frontend adopts a new endpoint or materially depends
+on a new request or response field. Keep provider states deterministic and
+free of production data. Pact files are generated evidence and are not
+committed.
diff --git a/docs/deployment.md b/docs/deployment.md
index 17158e7..e5abe9e 100644
--- a/docs/deployment.md
+++ b/docs/deployment.md
@@ -1,6 +1,11 @@
# Deployment
-This repository includes Docker, nginx, and Docker Compose example files for serving the built frontend.
+This repository includes an nginx configuration for serving the built frontend
+with client-side routing, long-lived asset caching, and required security
+headers.
+
+Release candidates must pass the production-like
+[staging environment](staging.md) before production promotion.
## Production Build
@@ -20,7 +25,8 @@ npm run preview
## Environment Variables
-Vite reads environment variables at build time. Make sure production values are present before building the image or static bundle.
+Vite reads environment variables at build time. Production builds fail when
+either required value is missing or invalid.
Required values usually include:
@@ -35,32 +41,75 @@ VITE_APP_BASE_URL=/
VITE_APP_BASE_URL=/app/
```
-## Docker
+`VITE_API_BASE_URL` must be an absolute HTTPS URL. `VITE_APP_BASE_URL` must
+start and end with `/`; use `/` when the application is served at the domain
+root.
-Build the image:
+## Observability
-```bash
-docker build -t kernel .
-```
+Set `VITE_RELEASE_ID` to an immutable deployment identifier, such as the Git
+commit SHA. This release is included with every error and performance event.
-Run the image:
+To deliver events, set `VITE_OBSERVABILITY_URL` to an HTTPS endpoint that
+accepts JSON `POST` requests. If it is omitted, instrumentation remains active
+but no events leave the browser. Reports include sanitized application errors,
+unhandled failures, React component failures, largest contentful paint,
+cumulative layout shift, and long tasks. Credential-like values, email
+addresses, and URL query values are redacted before delivery.
-```bash
-docker run --rm -p 8080:80 kernel
-```
+## nginx
+
+Build the application, copy `dist/` to `/usr/share/nginx/html`, and install
+`nginx.conf` as the server configuration. It supports client-side routing by
+falling back to `index.html`.
+
+When adding routes in React, no nginx route changes are normally required as long as the fallback remains in place.
+
+## Security headers
-## Docker Compose
+The nginx configuration applies:
-Use the example compose file as a starting point:
+- Content Security Policy restricted to same-origin application resources and
+ HTTPS API connections
+- HTTP Strict Transport Security for one year, including subdomains
+- Denial of framing through CSP and `X-Frame-Options`
+- MIME sniffing protection
+- Strict-origin-when-cross-origin referrer behavior
+- Disabled camera, geolocation, microphone, payment, and USB browser features
+
+HSTS is honored by browsers only over HTTPS. When TLS terminates at a CDN or
+load balancer, configure that edge to preserve these response headers. Run the
+Playwright suite against the production build to verify the complete policy.
+
+## Post-deployment smoke tests
+
+The deployment smoke workflow runs after a successful GitHub deployment status
+or can be started manually. It checks that:
+
+- The deployed HTML and application root load.
+- Same-origin scripts, stylesheets, and fonts resolve successfully.
+- Direct navigation to `/auth/register` reaches the client-side route.
+- The configured public API health endpoint returns a successful status.
+
+Set the repository variable `SMOKE_API_HEALTH_URL` to the public HTTPS health
+endpoint used by automatic deployment runs. The deployment provider must
+include its application URL in the successful deployment status. For a manual
+run, provide both URLs as workflow inputs.
+
+To run the same checks locally against a deployed environment:
```bash
-docker compose -f docker-compose.example.yml up --build
+SMOKE_BASE_URL=https://app.example.com/ \
+SMOKE_API_HEALTH_URL=https://api.example.com/health \
+npm run test:smoke
```
-Review environment values and service names before using it outside local development.
-
-## nginx
+## Release control and rollback
-`nginx.conf` serves the static build and supports client-side routing by falling back to `index.html`.
+Production deployments must use the immutable artifact associated with the
+approved GitHub Release. Keep at least the current and previous production
+artifacts available so recovery does not require rebuilding old source.
-When adding routes in React, no nginx route changes are normally required as long as the fallback remains in place.
+Follow the [release operations runbook](release-operations.md) for approval
+evidence, observation windows, rollback triggers, recovery steps, and incident
+follow-up.
diff --git a/docs/development.md b/docs/development.md
index 08591a6..5ff73c8 100644
--- a/docs/development.md
+++ b/docs/development.md
@@ -4,7 +4,7 @@ This project is a Vite React application written in TypeScript.
## Prerequisites
-- Node.js compatible with the versions required by the dependencies in `package-lock.json`
+- Node.js 24.15.0 or newer in the Node.js 24 LTS line
- npm
- Access to the backend API
@@ -57,6 +57,10 @@ Build the app:
npm run build
```
+Production builds require `VITE_API_BASE_URL` and `VITE_APP_BASE_URL` from
+`.env.local` or the build environment. See [Deployment](deployment.md) for
+their validation rules.
+
Check unused files, exports, and dependencies:
```bash
@@ -84,4 +88,4 @@ Keep feature code close to the existing structure:
The application currently ships with English (`en`) and Persian (`fa`). Translation strings live in `src/locales/en.ts` and `src/locales/fa.ts`.
-Ant Design direction and locale are selected in `src/providers/Antd.tsx`. Day.js locale/calendar setup is handled in `src/providers/Core.tsx`, using the Jalali calendar for Persian.
+Ant Design direction and locale are selected in `src/providers/antd/Antd.tsx`. Day.js locale/calendar setup is handled in `src/providers/core/Core.tsx`, using the Jalali calendar for Persian.
diff --git a/docs/quality-gate.md b/docs/quality-gate.md
new file mode 100644
index 0000000..5ee1185
--- /dev/null
+++ b/docs/quality-gate.md
@@ -0,0 +1,37 @@
+# SonarQube Quality Gate
+
+Kernel follows Clean as You Code. The SonarQube project must use a quality gate
+that fails when new code violates any of these conditions:
+
+| New-code condition | Required value |
+| -------------------------- | -------------- |
+| Issues | `0` |
+| Security Hotspots reviewed | `100%` |
+| Coverage | `>= 80%` |
+| Duplicated lines | `<= 3%` |
+
+These conditions apply to new code only. Existing debt should be improved
+incrementally instead of weakening the gate for new changes.
+
+## SonarQube setup
+
+An administrator must:
+
+1. Create a custom quality gate based on `Sonar way`.
+2. Require no issues on new code.
+3. Keep Security Hotspots reviewed at 100%.
+4. Keep new-code coverage at 80% or higher.
+5. Keep duplicated lines on new code at 3% or lower.
+6. Assign the gate to the Kernel project.
+7. Define new code using the previous-version or reference-branch strategy.
+
+The repository disables SonarQube's small-change exception so coverage and
+duplication conditions also apply when a change contains fewer than 20 lines.
+CI waits for the quality-gate result and fails when the gate fails.
+
+The remote gate cannot be configured from this repository. It requires
+SonarQube project-administrator access and all three GitHub Actions settings:
+the `SONAR_TOKEN` repository secret plus the `SONAR_PROJECT_KEY` and
+`SONAR_HOST_URL` repository variables. CI fails before analysis when any
+setting is missing. See [Testing](testing.md#sonarqube) for configuration
+details.
diff --git a/docs/release-operations.md b/docs/release-operations.md
new file mode 100644
index 0000000..ef859ec
--- /dev/null
+++ b/docs/release-operations.md
@@ -0,0 +1,112 @@
+# Release approval and rollback
+
+This runbook governs production releases of Kernel. It complements the
+[versioning policy](releasing.md) and the
+[deployment guide](deployment.md).
+
+## Ownership
+
+| Role | Owner | Responsibility |
+| ------------------- | ------------------------------------ | ----------------------------------------------------------------------- |
+| Release manager | `@sepaseh` | Reviews the release scope, records approval, and coordinates deployment |
+| Deployment operator | Production environment administrator | Deploys the approved immutable artifact and performs rollback |
+| Incident lead | On-call maintainer | Decides whether to halt or roll back and coordinates recovery |
+
+The release manager and deployment operator may be the same person while the
+team has one maintainer. With two or more active maintainers, the release pull
+request and production deployment should be approved by someone other than the
+change author.
+
+Repository ownership is enforced for release configuration and version files
+through `CODEOWNERS`. Configure the GitHub `production` environment with required
+reviewers and prevent administrators from bypassing its protection rules.
+
+## Release approval
+
+The Release Please pull request is the release candidate. Before approving it,
+the release manager must verify:
+
+- The version matches the intended SemVer impact.
+- The changelog is complete, understandable, and contains no sensitive data.
+- CI `check`, `build`, and `e2e` jobs pass for the exact candidate commit.
+- CodeQL and the configured SonarQube quality gate pass.
+- Dependency audit, performance budgets, and production security-header tests
+ pass.
+- Required DAST and manual security reviews have no unresolved release-blocking
+ findings.
+- Required configuration or data migrations have a tested rollback path.
+- The previous production artifact and its configuration remain available.
+- A deployment operator and incident lead are available for the release window.
+- The exact candidate artifact passes the
+ [staging validation gate](staging.md).
+
+Approval is recorded by approving and merging the release pull request. Never
+publish a tag or deploy from an unreviewed commit. The generated GitHub Release,
+tag, built artifact, and deployment must all identify the same commit.
+
+## Deployment verification
+
+After deployment:
+
+1. Record the version, commit SHA, artifact identifier, operator, and start time
+ in the deployment record.
+2. Run the automated deployment smoke tests.
+3. Verify login, protected routing, and one read-only authenticated journey.
+4. Confirm security headers and API health checks succeed.
+5. Compare error rate, failed requests, latency, largest contentful paint,
+ layout shift, and long tasks with the previous release.
+6. Announce success only after the observation window completes.
+
+Use a minimum 15-minute observation window for routine changes. Extend it for
+authentication, authorization, configuration, infrastructure, or major-version
+changes.
+
+## Rollback triggers
+
+The incident lead should halt rollout or begin rollback when any of these occur:
+
+- Smoke tests or API health checks fail.
+- Users cannot authenticate, authorize, navigate, or complete a critical flow.
+- Error rate, failed requests, or latency materially regress from the previous
+ release.
+- Security controls are missing or a new vulnerability or data-exposure risk is
+ discovered.
+- Assets fail to load, client routing breaks, or the deployment configuration
+ is invalid.
+- A migration is incomplete or incompatible with the running frontend.
+
+When impact is uncertain, prefer stopping further rollout while evidence is
+collected. Do not attempt an unrelated forward fix during an active rollback.
+
+## Rollback procedure
+
+1. Declare the release unhealthy, assign the incident lead, and pause further
+ deployments.
+2. Capture the failing version, commit SHA, timestamps, symptoms, dashboards,
+ logs, and smoke-test artifacts.
+3. Select the last known-good immutable artifact. Verify its checksum, release
+ identifier, configuration compatibility, and API compatibility.
+4. Promote that artifact using the hosting provider's rollback or deployment
+ mechanism. Do not rebuild the old source because dependencies and output may
+ have changed.
+5. Restore the matching environment configuration. Run only a separately tested
+ backward migration when data or schema changes require it.
+6. Purge or invalidate cached HTML when necessary; fingerprinted static assets
+ may remain cached.
+7. Re-run deployment smoke tests, critical authentication checks, API health
+ checks, and security-header verification.
+8. Observe production until the rollback version returns to its prior baseline.
+9. Record the recovery time and announce restoration. Keep the failed Git tag
+ and GitHub Release for traceability; mark them as withdrawn instead of
+ deleting or moving tags.
+
+If rollback cannot safely restore service, keep the rollout halted, activate
+the service's incident-response process, and make the smallest reviewed forward
+fix from the last known-good version.
+
+## Follow-up
+
+Within two business days, document the cause, customer impact, detection gap,
+timeline, and corrective actions. Add regression coverage before retrying the
+release. A retry receives a new patch version and repeats the complete approval
+process; released versions and tags are never reused.
diff --git a/docs/releasing.md b/docs/releasing.md
new file mode 100644
index 0000000..630d0b1
--- /dev/null
+++ b/docs/releasing.md
@@ -0,0 +1,51 @@
+# Versioning and releases
+
+Kernel follows [Semantic Versioning](https://semver.org/). Versions use
+`MAJOR.MINOR.PATCH`:
+
+- `MAJOR` for incompatible behavior or configuration changes.
+- `MINOR` for backward-compatible features.
+- `PATCH` for backward-compatible fixes.
+
+The current `1.0.0` version is the first stable baseline. Pre-release versions
+use SemVer identifiers such as `2.0.0-rc.1`.
+
+## Commit policy
+
+Use Conventional Commits for changes merged into `main`. The release automation
+uses these prefixes to determine the next version and build release notes:
+
+- `fix:` produces a patch release.
+- `feat:` produces a minor release.
+- A `BREAKING CHANGE:` footer or `!` after the type produces a major release.
+- `docs:`, `test:`, `refactor:`, `perf:`, `build:`, `ci:`, and `chore:` do not
+ request a version bump on their own.
+
+Scopes are optional, for example `feat(auth): add passkey login`. Pull requests
+should be squash-merged with a compliant title so each merged change has one
+clear release-note entry.
+
+## Automated release flow
+
+After releasable commits reach `main`, the Release workflow creates or updates a
+release pull request. That pull request updates:
+
+- `CHANGELOG.md`
+- `package.json` and `package-lock.json`
+- `.release-please-manifest.json`
+
+Review and merge the release pull request only after its required checks pass.
+The next workflow run then creates the `vMAJOR.MINOR.PATCH` tag and GitHub
+Release with generated notes. This application is private and is not published
+to npm.
+
+Every production release must follow the approval, verification, rollback, and
+follow-up steps in the [release operations runbook](release-operations.md).
+
+The workflow uses the repository `GITHUB_TOKEN` by default. If repository policy
+requires release pull requests to trigger other workflows, configure a
+fine-grained token or GitHub App token as the `RELEASE_PLEASE_TOKEN` repository
+secret according to the Release Please documentation.
+
+Do not edit generated version or changelog entries outside a release pull
+request, except when correcting the release configuration itself.
diff --git a/docs/security/authentication-review.md b/docs/security/authentication-review.md
new file mode 100644
index 0000000..8b4d59b
--- /dev/null
+++ b/docs/security/authentication-review.md
@@ -0,0 +1,90 @@
+# Authentication and session review
+
+Complete this checklist quarterly and before any authentication, authorization,
+cookie, token, account-recovery, or identity-provider change.
+
+## Client evidence
+
+- [x] Access tokens remain in memory only.
+- [x] Login and registration replace the in-memory access token.
+- [x] Logout clears the access token even when the API request fails.
+- [x] A single refresh request is shared by concurrent unauthorized responses.
+- [x] Each failed request is retried at most once.
+- [x] Refresh failure clears authentication state and returns to public routes.
+- [x] Public authentication failures do not start refresh loops.
+- [ ] Credentials and account data are absent from local storage, session
+ storage, URLs, analytics, and observability reports.
+- [ ] Redirect targets cannot send users to an untrusted origin.
+
+## API and cookie evidence
+
+- [ ] Refresh cookies use `HttpOnly`, `Secure`, an appropriate `SameSite`
+ setting, the narrowest `Path`, and no overly broad `Domain`.
+- [ ] Refresh tokens are rotated on use, detect reuse, expire, and are revoked
+ on logout, password change, account disablement, and recovery.
+- [ ] Credentialed state-changing requests have validated CSRF protection and
+ origin checks.
+- [ ] CORS allows only explicit application origins and credential requirements.
+- [ ] Login, OTP, registration, recovery, refresh, and verification endpoints
+ have rate limits and abuse monitoring.
+- [ ] Password and OTP reset artifacts are single-use, short-lived, and never
+ exposed in logs or URLs beyond the minimum required flow.
+- [ ] Authorization is enforced server-side for every object and operation,
+ including object ownership and tenant/workspace boundaries.
+- [ ] Authentication responses and errors do not enable account enumeration.
+- [ ] Session creation, refresh, revocation, privilege changes, and suspicious
+ failures create protected audit events.
+
+## Browser verification
+
+- [ ] Cookies and cache headers are inspected in browser developer tools.
+- [ ] Cross-origin requests are tested from an untrusted origin.
+- [ ] Back-button, refresh, duplicate-tab, expiry, and clock-skew behavior are
+ tested.
+- [ ] Concurrent `401` responses, offline recovery, and API timeouts are tested.
+- [ ] Logout is verified in every tab and the old refresh token cannot be used.
+- [ ] Role removal takes effect without relying on stale client permissions.
+
+Record the reviewed release, frontend and API commit SHAs, reviewer, date,
+evidence links, findings, owners, severity, and due dates. Put sensitive
+findings in a private security advisory rather than a public issue.
+
+## Initial frontend review — 2026-07-30
+
+- Reviewed release: `1.0.0`
+- Frontend commit: `c8aa8a63fe2ca8a89f09898e3c95f6015f643e8f`
+- API commit: Not supplied; API controls remain unverified and block release
+ approval.
+- Reviewer: Sepaseh
+- Evidence:
+ - [Token storage implementation](../../src/api/token.ts)
+ - [Authentication operations](../../src/api/auth.ts)
+ - [Refresh and retry implementation](../../src/api/instance.ts)
+ - [Authentication operation tests](../../src/api/auth.test.ts)
+ - [Refresh, retry, and concurrency tests](../../src/api/instance.test.ts)
+ - [Authenticated-layout cleanup tests](../../src/layouts/default/Default.test.tsx)
+
+The frontend review confirmed:
+
+- Access tokens are held in module memory and are not persisted.
+- Login and registration set the token; logout clears it in a `finally` block.
+- Unauthorized protected requests share one refresh operation, retry once, and
+ clear authentication after terminal failure.
+- Refresh and logout use credentialed HTTPS API requests.
+- Unit tests cover concurrency, successful retry, refresh failure, retry-loop
+ prevention, and cleanup.
+
+Release approval still requires API-owner evidence for refresh-cookie flags,
+rotation and reuse detection, CSRF and CORS enforcement, revocation, endpoint
+rate limits, account-enumeration resistance, audit events, and server-side
+authorization. These controls cannot be established from frontend source.
+
+| Finding | Severity | Owner | Due date | Status |
+| ----------------------------------------------------------------------------------------------- | -------- | ---------------------------- | ------------------------------- | --------------------------------------------- |
+| API authentication and session evidence, including the reviewed API SHA, has not been supplied. | Major | API owner | Before `1.0.0` release approval | Open |
+| Same-origin script injection can read the in-memory access token and act as the user. | Major | Frontend and security owners | Reassess by 2026-10-30 | Risk accepted pending defense-in-depth review |
+
+The residual client risk is that any successful same-origin script injection
+can read the in-memory access token and act as the user. CSP, dependency
+controls, output encoding, short token lifetime, and server-side authorization
+remain required defense in depth.
diff --git a/docs/security/reviews.md b/docs/security/reviews.md
new file mode 100644
index 0000000..36bd4a1
--- /dev/null
+++ b/docs/security/reviews.md
@@ -0,0 +1,57 @@
+# Security testing and review schedule
+
+Security checks are defense in depth and do not replace review by a qualified
+person.
+
+| Activity | Frequency | Owner | Evidence |
+| --------------------------------------------- | ------------------------------------------- | ------------------------------ | -------------------------------------- |
+| Dependency audit, CodeQL, and secret scanning | Every change | Maintainer | Required CI checks |
+| Staging DAST full scan | Monthly and before major releases | Security reviewer | Private workflow artifact |
+| Threat-model review | Quarterly and on trust-boundary changes | Security reviewer | Reviewed document change |
+| Authentication/session review | Quarterly and on auth changes | Frontend and API owners | Completed checklist |
+| Manual application security review | At least annually and before major releases | Independent qualified reviewer | Private report and tracked remediation |
+| Rollback exercise | At least twice yearly | Release manager | Exercise record |
+
+## DAST rules
+
+The `DAST` workflow runs an active OWASP ZAP full scan against the repository
+variable `STAGING_BASE_URL`. The target must be an explicitly authorized,
+disposable staging environment. Active scans may submit forms, create data,
+trigger email or OTP delivery, and place load on the API.
+
+Configure a protected GitHub environment named `staging`. Store
+`STAGING_BASE_URL`, `STAGING_ALLOWED_HOST`, and `PRODUCTION_HOST` as
+environment-scoped variables so repository-level values cannot redirect the
+scan. Require approval from a staging security owner before the job can start.
+
+Before enabling the schedule:
+
+1. Obtain written authorization from the application, API, hosting, CDN, and
+ security owners.
+2. Seed non-sensitive test data and disable real notifications or downstream
+ side effects.
+3. Confirm backups, rate limits, monitoring, and a cleanup procedure.
+4. Set `STAGING_BASE_URL` to the exact HTTPS application origin,
+ `STAGING_ALLOWED_HOST` to that origin's hostname, and `PRODUCTION_HOST` to the
+ production hostname. The workflow requires an exact staging-host match and
+ rejects the production host.
+5. Run the workflow manually and review the private artifact.
+
+The job fails on reported alerts. Do not suppress a rule merely to make the
+scan pass. Record a suppression only after review, with the rule ID, affected
+URL, evidence, owner, expiry date, and justification.
+
+Do not run active DAST against production. Authenticated scanning requires a
+separate least-privileged test account, explicit scope approval, and secure
+injection of short-lived credentials; never commit scan credentials.
+
+## Finding handling
+
+Triage every finding for exploitability, affected versions, data exposure, and
+release impact. Handle sensitive details through GitHub private vulnerability
+reporting. Assign an owner and due date, add regression coverage, and rerun the
+relevant scan after remediation.
+
+Critical or actively exploited findings block deployment and trigger the
+incident process. High-severity findings block the affected release unless the
+security owner documents a time-limited exception with compensating controls.
diff --git a/docs/security/threat-model.md b/docs/security/threat-model.md
new file mode 100644
index 0000000..87a1460
--- /dev/null
+++ b/docs/security/threat-model.md
@@ -0,0 +1,81 @@
+# Threat model
+
+## Scope
+
+This model covers the Kernel browser application, its static hosting layer, the
+configured API origin, and the authentication/session exchange between them.
+The backend implementation, identity provider, CI platform, and hosting
+provider are dependencies and must maintain their own threat models.
+
+Review this document for authentication, authorization, data-flow,
+infrastructure, or trust-boundary changes and at least quarterly.
+
+## Assets
+
+- Access and refresh credentials.
+- User identity, profile, role, permission, and account data.
+- Administrative operations for users and roles.
+- Build artifacts, release identifiers, source code, and CI credentials.
+- Application availability, integrity, audit evidence, and observability data.
+
+## Trust boundaries and data flow
+
+1. The browser downloads immutable static assets through the CDN or web server.
+2. The browser sends HTTPS API requests to `VITE_API_BASE_URL`.
+3. The access token exists only in JavaScript memory and is sent as a bearer
+ header. It is not intentionally persisted in browser storage.
+4. Refresh and logout requests include credentials, so the API controls the
+ refresh cookie and its lifecycle.
+5. Language and theme preferences cross the browser-storage boundary but must
+ never contain credentials or sensitive account data.
+6. CI builds artifacts and security reports using repository variables and
+ secrets. Staging and production are separate deployment boundaries.
+7. Sanitized browser errors and performance events may cross into the configured
+ observability service.
+
+## Primary threats and controls
+
+| Threat | Impact | Current controls | Required verification |
+| ------------------------------------------ | ----------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
+| Script injection or compromised dependency | Credential theft and actions as the user | CSP, dependency audit, CodeQL, no token persistence | Review CSP exceptions and dependency provenance |
+| Stolen or replayed refresh cookie | Session takeover | Credentialed refresh endpoint and server logout | Verify `HttpOnly`, `Secure`, `SameSite`, rotation, expiry, and revocation |
+| Cross-site request forgery | Unauthorized refresh, logout, or mutation | Same-origin deployment assumptions and API controls | Verify origin checks or CSRF tokens on every credentialed state change |
+| Broken access control | Unauthorized user or role administration | Client route/action filtering | Enforce every permission on the API; client checks are not security boundaries |
+| Sensitive data exposure | Privacy or credential compromise | HTTPS validation, redacted observability, memory-only access token | Inspect logs, errors, caches, source maps, and browser storage |
+| Malicious or vulnerable build input | Compromised production artifact | Pinned Actions, audit, CodeQL, SBOM/license checks, protected branch | Review workflow permissions and artifact provenance |
+| Unsafe deployment or rollback | Extended outage or vulnerable release | Staging gate, immutable releases, smoke tests, rollback runbook | Exercise rollback and compare release, artifact, and deployed SHA |
+| Denial of service or automated abuse | Unavailable authentication/API | Client request cancellation where applicable | Verify API rate limits, quotas, timeouts, and alerting |
+| Clickjacking or content-type confusion | Deceptive UI or script execution | Frame denial, MIME sniffing protection, CSP | Validate headers on CDN and direct routes |
+| Observability leakage | Secrets in monitoring systems | Client-side redaction and optional endpoint | Test server-side scrubbing and access/retention policy |
+
+## Abuse cases
+
+- An unauthenticated user directly requests an administrative route or API.
+- A user edits client state or permissions to reveal a hidden action.
+- Multiple expired requests race to refresh a session or replay a token.
+- An attacker submits oversized, malformed, or script-bearing form values.
+- A malicious site causes credentialed cross-origin requests.
+- A compromised dependency reads memory, DOM content, or browser storage.
+- A release serves stale HTML that references missing or incompatible assets.
+- A scanner or attacker repeatedly triggers OTP, login, registration, or
+ password-recovery endpoints.
+
+## Security invariants
+
+- Authorization decisions are enforced by the API, never only by React.
+- Production and security-test traffic use HTTPS.
+- Access tokens are not written to local or session storage.
+- Refresh cookies are inaccessible to JavaScript and narrowly scoped.
+- Logout and terminal refresh failure clear client and server session state.
+- Error reports, logs, build artifacts, and test evidence contain no secrets.
+- Released tags are immutable and production can return to a known-good
+ artifact.
+- Active security testing targets only explicitly authorized, disposable
+ staging systems.
+
+## Residual risks
+
+The frontend cannot verify API authorization, cookie flags, refresh-token
+rotation, revocation, rate limiting, audit logging, database isolation, or
+backend input validation. These remain release blockers until confirmed through
+backend evidence, integration tests, or a documented security review.
diff --git a/docs/staging.md b/docs/staging.md
new file mode 100644
index 0000000..1ca8821
--- /dev/null
+++ b/docs/staging.md
@@ -0,0 +1,73 @@
+# Staging environment
+
+Staging is the production-like release gate for Kernel. It must use the same
+build artifact, web-server configuration, routing rules, security headers, and
+API contract intended for production.
+
+## Environment requirements
+
+Provision a GitHub environment named exactly `staging` and configure:
+
+- A stable HTTPS application URL.
+- Non-production API, identity, and data stores with production-compatible
+ schemas.
+- The same CDN, reverse-proxy, cache, and security-header behavior as
+ production.
+- Sanitized seed data and test accounts. Never copy production credentials,
+ tokens, or personal data.
+- Access controls that still allow GitHub-hosted test runners to reach the
+ application and health endpoint.
+
+Set the repository Actions variable `STAGING_API_HEALTH_URL` to the public HTTPS
+health endpoint. The validation job intentionally does not attach itself to a
+GitHub environment, because doing so would create another deployment status and
+could retrigger the deployment-status workflow.
+
+Keep staging configuration separate from production secrets. Build once with an
+immutable release identifier and promote that artifact between environments;
+do not rebuild source specifically for production.
+
+## Automated validation
+
+The `Staging validation` workflow runs when GitHub receives a successful
+deployment status for the `staging` environment. It can also be started
+manually with application and API health URLs.
+
+The live suite verifies:
+
+- Application HTML and same-origin assets load successfully.
+- Direct client-side routes work.
+- Public authentication journeys pass automated accessibility checks.
+- CSP, HSTS, framing, MIME-sniffing, referrer, and permissions policies are
+ present.
+- The staging API health endpoint succeeds.
+- The checks pass in Chromium, Firefox, WebKit, and a mobile Chromium viewport.
+
+The workflow rejects missing, non-HTTPS, or credential-bearing target URLs and
+retains traces, screenshots, video, and HTML reports when tests fail.
+
+To run the same gate locally:
+
+```bash
+STAGING_BASE_URL=https://staging.example.com/ \
+STAGING_API_HEALTH_URL=https://staging-api.example.com/health \
+npm run test:staging
+```
+
+## Promotion
+
+A release is eligible for production only when:
+
+1. The ordinary CI, CodeQL, and quality-gate checks pass.
+2. The exact candidate artifact is deployed to staging.
+3. The Staging validation workflow passes for that deployment.
+4. Any required manual acceptance checks complete.
+5. The release manager approves promotion using the
+ [release operations runbook](release-operations.md).
+
+Failed staging checks block promotion. Fix the issue through a reviewed change,
+create a new candidate artifact, and repeat the complete staging gate.
+
+The separate active DAST workflow also targets staging on the
+[security review schedule](security/reviews.md). Its authorization, isolation,
+and side-effect controls must be satisfied before enabling scheduled scans.
diff --git a/docs/testing.md b/docs/testing.md
index e97e0d1..2ef987a 100644
--- a/docs/testing.md
+++ b/docs/testing.md
@@ -10,7 +10,7 @@ fast and maintainable.
| Unit | Vitest | Pure functions, hooks, reducers, and isolated logic |
| Component | React Testing Library and user-event | User-visible rendering and interactions |
| API integration | MSW | Request serialization, response handling, and error states |
-| End-to-end | Playwright | A small set of critical journeys in a real browser |
+| End-to-end | Playwright and axe-core | Critical journeys and automated accessibility checks |
Vitest is the best unit runner for this project because it shares Vite's
TypeScript and module resolution model. React Testing Library encourages tests
@@ -50,15 +50,44 @@ separate because they run against the built application.
- `npm run test:e2e` builds the application and runs Playwright.
- `npm run test:e2e:ui` opens Playwright's interactive UI.
- `npm run test:e2e:report` opens the most recent HTML report.
+- `npm run test:e2e:update-snapshots` reviews and updates visual baselines.
+- `npm run performance` builds the app, checks bundle budgets, and runs
+ Lighthouse.
-Local E2E runs use the installed stable Chrome channel. GitHub CI installs and
-uses Playwright's pinned Chromium build for reproducibility. If you change the
-local project configuration to use bundled Chromium, install it once with:
+Vitest loads the tracked `.env.test` file, which provides deterministic local
+API and application base URLs for unit, component, and API integration tests.
+These values take precedence over developer-specific `.env.local` settings, so
+tests never depend on or contact a configured development backend.
+
+Local Chromium and mobile E2E runs use the installed stable Chrome channel.
+Firefox and WebKit use Playwright's pinned browser builds. GitHub CI installs
+all three pinned engines for reproducibility. Install the required local
+browsers once with:
```sh
-npx playwright install chromium
+npx playwright install firefox webkit
```
+Visual regression tests run in a dedicated Chromium project with stable mocked
+API responses. As with the functional Chromium project, local runs use stable
+Chrome and CI uses the pinned Playwright build. Review every changed image
+before accepting a baseline update. Generated diffs and actual screenshots
+remain available in `test-results/` when a comparison fails.
+
+## Performance budgets
+
+The production build enforces two JavaScript size limits:
+
+- No individual JavaScript chunk may exceed 450 KB.
+- All emitted JavaScript chunks combined may not exceed 1.6 MB.
+
+Lighthouse audits the production login page and requires a performance score of
+at least 80, first contentful paint within 3.5 seconds, largest contentful paint
+within 4 seconds, time to interactive within 4 seconds, total blocking time
+below 300 milliseconds, and cumulative layout shift no greater than 0.1.
+Reports are written to `.lighthouseci/` and uploaded by CI for 14 days,
+including when the performance job fails.
+
## Conventions
- Test behavior users can observe; avoid component state and implementation
@@ -77,17 +106,51 @@ npx playwright install chromium
automatically after each test.
- Use Playwright locators and web-first assertions. Do not select by CSS class
or XPath.
+- Run axe against public forms, authenticated pages, and open dialogs. Keep
+ color-contrast checks enabled and pair automated scans with keyboard and
+ focus assertions.
+- Run critical browser journeys in English/LTR and Persian/RTL, asserting the
+ document language and direction as well as translated controls.
+- Keep visual snapshots deterministic: use fixed test data, wait for visible
+ page content, and disable animations and carets during capture.
- Add E2E tests only for high-value journeys. Cover permutations and error
states lower in the test pyramid.
## Coverage
Coverage uses Vitest's V8 provider and includes untested source files. Reports
-are written to `coverage/`, which is ignored by Git. The initial global
-threshold is intentionally low because Kernel is introducing coverage to an
-existing codebase. Raise thresholds as coverage grows; never lower them to make
-a change pass.
+are written to `coverage/`, which is ignored by Git. Global thresholds preserve
+the established baseline across statements, branches, functions, and lines.
+Raise thresholds as coverage grows; never lower them to make a change pass.
+SonarQube separately requires at least 80% coverage on new code through the
+mandatory quality gate.
Coverage is a guardrail, not a quality score. Critical authentication, token
refresh, authorization, and account-management branches should receive direct
behavioral tests even when the global threshold is already satisfied.
+
+## Test effectiveness and API compatibility
+
+Mutation testing measures whether focused unit tests detect injected faults,
+while Pact consumer contracts protect the request and response boundary with
+the backend. See [Mutation and contract testing](contract-testing.md) for local
+commands, CI behavior, thresholds, and provider verification requirements.
+
+## SonarQube
+
+CI sends the existing LCOV report to SonarQube and requires all of these GitHub
+repository settings:
+
+| Setting | GitHub type | Value |
+| ------------------- | ------------------- | ------------------------------------------- |
+| `SONAR_HOST_URL` | Repository variable | Base URL of the SonarQube service |
+| `SONAR_PROJECT_KEY` | Repository variable | Key of the Kernel project in SonarQube |
+| `SONAR_TOKEN` | Repository secret | Project analysis token created in SonarQube |
+
+Configure them under **Settings → Secrets and variables → Actions**. Store the
+token as a secret, never as a variable or committed file. CI fails with the
+names of missing settings before analysis starts; it never prints their values.
+
+The scan waits for the configured quality gate, so a failed gate fails CI. See
+[SonarQube Quality Gate](quality-gate.md) for the required new-code conditions
+and project setup.
diff --git a/e2e/accessibility.spec.ts b/e2e/accessibility.spec.ts
new file mode 100644
index 0000000..44fe84d
--- /dev/null
+++ b/e2e/accessibility.spec.ts
@@ -0,0 +1,138 @@
+import AxeBuilder from "@axe-core/playwright";
+import { expect, Page, test } from "@playwright/test";
+
+const expectNoAxeViolations = async (page: Page) => {
+ const results = await new AxeBuilder({ page }).analyze();
+ const violations = results.violations.map(({ help, id, impact, nodes }) => ({
+ help,
+ id,
+ impact,
+ targets: nodes.map(({ target }) => target),
+ }));
+
+ expect(violations).toEqual([]);
+};
+
+const mockAccount = async (page: Page, permissions: string[]) => {
+ await page.route("https://api.example.com/account/me", async (route) => {
+ await route.fulfill({
+ contentType: "application/json",
+ json: {
+ email: "manager@example.com",
+ first_name: "Test",
+ id: "manager-1",
+ is_system_admin: false,
+ last_name: "Manager",
+ mobile: "09120000000",
+ permissions,
+ personnel_code: "100",
+ status: "active",
+ username: "manager",
+ },
+ });
+ });
+};
+
+test.describe("accessibility", () => {
+ test.beforeEach(async ({ page }) => {
+ await page.addInitScript(() => {
+ localStorage.setItem("language", "en");
+ localStorage.setItem("theme", "light");
+ });
+ });
+
+ test("public authentication pages have no detectable violations", async ({
+ page,
+ }) => {
+ for (const path of ["/auth", "/auth/forgot-password", "/auth/register"]) {
+ await page.goto(path);
+ await expect(page.locator("main, form").first()).toBeVisible();
+ await expectNoAxeViolations(page);
+ }
+ });
+
+ test("login controls follow a usable keyboard focus order", async ({
+ page,
+ }) => {
+ await page.goto("/auth");
+
+ for (let attempt = 0; attempt < 5; attempt += 1) {
+ if (
+ await page
+ .getByLabel("Username, email, or mobile")
+ .evaluate((element) => element === document.activeElement)
+ ) {
+ break;
+ }
+
+ await page.keyboard.press("Tab");
+ }
+
+ await expect(page.getByLabel("Username, email, or mobile")).toBeFocused();
+ await page.keyboard.press("Tab");
+ await expect(page.getByLabel("Password")).toBeFocused();
+ });
+
+ test("role dialog is labeled, traps focus, and passes axe", async ({
+ page,
+ }) => {
+ await mockAccount(page, ["roles.create", "roles.read"]);
+ await page.route("https://api.example.com/roles", async (route) => {
+ await route.fulfill({ contentType: "application/json", json: [] });
+ });
+ await page.route("https://api.example.com/permissions", async (route) => {
+ await route.fulfill({
+ contentType: "application/json",
+ json: [
+ {
+ name: "users",
+ permissions: [{ name: "users.read", title: "Read users" }],
+ title: "Users",
+ },
+ ],
+ });
+ });
+ await page.goto("/roles");
+ await page.getByRole("button", { name: "Create" }).click();
+ const dialog = page.getByRole("dialog", { name: "Create" });
+
+ await expect(dialog).toBeVisible();
+ await expect(dialog.getByLabel("Name")).toBeFocused();
+ await expect(dialog.getByLabel("Read users")).toBeVisible();
+ await expectNoAxeViolations(page);
+ });
+
+ test("authenticated user management has no detectable violations", async ({
+ page,
+ }) => {
+ await mockAccount(page, ["users.read"]);
+ await page.route(
+ /https:\/\/api\.example\.com\/users(?:\?.*)?$/,
+ async (route) => {
+ await route.fulfill({
+ contentType: "application/json",
+ json: {
+ items: [
+ {
+ email: "ada@example.com",
+ first_name: "Ada",
+ id: "user-1",
+ is_system_admin: false,
+ last_name: "Lovelace",
+ mobile: "09121111111",
+ personnel_code: "200",
+ status: "active",
+ username: "ada",
+ },
+ ],
+ total: 1,
+ },
+ });
+ },
+ );
+ await page.goto("/users");
+ await expect(page.getByText("Ada Lovelace")).toBeVisible();
+
+ await expectNoAxeViolations(page);
+ });
+});
diff --git a/e2e/auth.spec.ts b/e2e/auth.spec.ts
index 09665eb..f173227 100644
--- a/e2e/auth.spec.ts
+++ b/e2e/auth.spec.ts
@@ -1,12 +1,38 @@
-import { expect, test } from "@playwright/test";
+import { expect, Page, test } from "@playwright/test";
+
+import { syntheticCredential } from "./fixtures";
+
+const accountResponse = {
+ email: "ada@example.com",
+ first_name: "Ada",
+ id: "user-1",
+ is_system_admin: true,
+ last_name: "Lovelace",
+ mobile: "09120000000",
+ permissions: [],
+ personnel_code: "100",
+ status: "active",
+ username: "ada",
+};
+
+const fillOtp = async (page: Page, otp: string) => {
+ for (const [index, digit] of [...otp].entries()) {
+ await page
+ .getByRole("textbox", { name: `OTP Input ${index + 1}` })
+ .fill(digit);
+ }
+};
test.describe("public authentication", () => {
- test("shows the login form and public account recovery links", async ({
- page,
- }) => {
+ test.beforeEach(async ({ page }) => {
await page.addInitScript(() => {
localStorage.setItem("language", "en");
});
+ });
+
+ test("shows the login form and public account recovery links", async ({
+ page,
+ }) => {
await page.goto("/auth");
await expect(page.getByRole("heading", { name: "Login" })).toBeVisible();
@@ -19,4 +45,210 @@ test.describe("public authentication", () => {
page.getByRole("link", { name: "Forgot password?" }),
).toHaveAttribute("href", "/auth/forgot-password");
});
+
+ test("shows the API error when credentials are rejected", async ({
+ page,
+ }) => {
+ await page.route("https://api.example.com/auth/login", async (route) => {
+ expect(route.request().postDataJSON()).toEqual({
+ identifier: "ada",
+ password: syntheticCredential,
+ });
+
+ await route.fulfill({
+ contentType: "application/json",
+ json: { message: "Invalid credentials" },
+ status: 401,
+ });
+ });
+ await page.goto("/auth");
+
+ await page.getByLabel("Username, email, or mobile").fill("ada");
+ await page.getByLabel("Password").fill(syntheticCredential);
+ await page.getByRole("button", { name: "Enter" }).click();
+
+ await expect(page.getByText("Invalid credentials")).toBeVisible();
+ await expect(page).toHaveURL(/\/auth$/);
+ });
+
+ test("logs in and loads the authenticated account", async ({ page }) => {
+ await page.route("https://api.example.com/auth/login", async (route) => {
+ await route.fulfill({
+ contentType: "application/json",
+ json: { access_token: "access-token" },
+ });
+ });
+ await page.route("https://api.example.com/account/me", async (route) => {
+ expect(route.request().headers().authorization).toBe(
+ "Bearer access-token",
+ );
+
+ await route.fulfill({
+ contentType: "application/json",
+ json: accountResponse,
+ });
+ });
+ await page.goto("/auth");
+
+ await page.getByLabel("Username, email, or mobile").fill("ada");
+ await page.getByLabel("Password").fill(syntheticCredential);
+ await page.getByRole("button", { name: "Enter" }).click();
+
+ await expect(page).toHaveURL(/\/$/);
+ await expect(page.getByRole("link", { name: "kernel" })).toBeVisible();
+ });
+
+ test("recovers a forgotten password and returns to login", async ({
+ page,
+ }) => {
+ await page.route(
+ "https://api.example.com/auth/otp-request",
+ async (route) => {
+ expect(route.request().postDataJSON()).toEqual({
+ mobile: "09120000000",
+ purpose: "forgot_password",
+ });
+ await route.fulfill({
+ contentType: "application/json",
+ json: { expires_in: 300, remaining_seconds: 60 },
+ });
+ },
+ );
+ await page.route(
+ "https://api.example.com/auth/forgot-password",
+ async (route) => {
+ expect(route.request().postDataJSON()).toEqual({
+ mobile: "09120000000",
+ otp: "123456",
+ password: syntheticCredential,
+ });
+ await route.fulfill({ status: 204 });
+ },
+ );
+ await page.goto("/auth/forgot-password");
+
+ await page.getByLabel("Mobile").fill("09120000000");
+ await page.getByRole("button", { name: "Send code" }).click();
+ await expect(page.getByText("Verification code sent.")).toBeVisible();
+ await fillOtp(page, "123456");
+ await page.getByLabel("New password").fill(syntheticCredential);
+ await page.getByLabel("Confirm password").fill(syntheticCredential);
+ await page.getByRole("button", { name: "Submit" }).click();
+
+ await expect(page).toHaveURL(/\/auth$/);
+ });
+
+ test("registers a new account and enters the application", async ({
+ page,
+ }) => {
+ await page.route(
+ "https://api.example.com/auth/otp-request",
+ async (route) => {
+ expect(route.request().postDataJSON()).toEqual({
+ mobile: "09120000000",
+ purpose: "register",
+ });
+ await route.fulfill({
+ contentType: "application/json",
+ json: { expires_in: 300, remaining_seconds: 60 },
+ });
+ },
+ );
+ await page.route("https://api.example.com/auth/register", async (route) => {
+ expect(route.request().postDataJSON()).toEqual({
+ first_name: "Ada",
+ last_name: "Lovelace",
+ mobile: "09120000000",
+ otp: "123456",
+ password: syntheticCredential,
+ });
+ await route.fulfill({
+ contentType: "application/json",
+ json: { access_token: "registered-token" },
+ });
+ });
+ await page.route("https://api.example.com/account/me", async (route) => {
+ expect(route.request().headers().authorization).toBe(
+ "Bearer registered-token",
+ );
+ await route.fulfill({
+ contentType: "application/json",
+ json: accountResponse,
+ });
+ });
+ await page.goto("/auth/register");
+
+ await page.getByLabel("First name").fill("Ada");
+ await page.getByLabel("Last name").fill("Lovelace");
+ await page.getByLabel("Mobile").fill("09120000000");
+ await page.getByRole("button", { name: "Send code" }).click();
+ await fillOtp(page, "123456");
+ await page
+ .getByLabel("Password", { exact: true })
+ .fill(syntheticCredential);
+ await page.getByLabel("Confirm password").fill(syntheticCredential);
+ await page.getByRole("button", { name: "Register" }).click();
+
+ await expect(page).toHaveURL(/\/$/);
+ await expect(page.getByRole("link", { name: "kernel" })).toBeVisible();
+ });
+
+ test("logs out and returns to login", async ({ page }) => {
+ await page.route("https://api.example.com/auth/login", async (route) => {
+ await route.fulfill({
+ contentType: "application/json",
+ json: { access_token: "access-token" },
+ });
+ });
+ await page.route("https://api.example.com/account/me", async (route) => {
+ await route.fulfill({
+ contentType: "application/json",
+ json: accountResponse,
+ });
+ });
+ await page.route("https://api.example.com/auth/logout", async (route) => {
+ expect(route.request().headers().authorization).toBe(
+ "Bearer access-token",
+ );
+ await route.fulfill({ status: 204 });
+ });
+ await page.goto("/auth");
+ await page.getByLabel("Username, email, or mobile").fill("ada");
+ await page.getByLabel("Password").fill(syntheticCredential);
+ await page.getByRole("button", { name: "Enter" }).click();
+ await expect(page.getByRole("link", { name: "kernel" })).toBeVisible();
+
+ await page.getByRole("button", { name: "Account" }).click();
+ await page.getByRole("menuitem", { name: "Logout" }).click();
+
+ await expect(page).toHaveURL(/\/auth$/);
+ await expect(page.getByRole("heading", { name: "Login" })).toBeVisible();
+ });
+
+ test("redirects unauthenticated users away from protected routes", async ({
+ page,
+ }) => {
+ await page.route("https://api.example.com/account/me", async (route) => {
+ await route.fulfill({
+ contentType: "application/json",
+ json: { message: "Unauthorized" },
+ status: 401,
+ });
+ });
+ await page.route(
+ "https://api.example.com/auth/refresh-token",
+ async (route) => {
+ await route.fulfill({
+ contentType: "application/json",
+ json: { message: "Unauthorized" },
+ status: 401,
+ });
+ },
+ );
+
+ await page.goto("/users");
+
+ await expect(page).toHaveURL(/\/auth$/);
+ await expect(page.getByRole("heading", { name: "Login" })).toBeVisible();
+ });
});
diff --git a/e2e/fixtures.ts b/e2e/fixtures.ts
new file mode 100644
index 0000000..6f64f34
--- /dev/null
+++ b/e2e/fixtures.ts
@@ -0,0 +1 @@
+export const syntheticCredential = "synthetic-test-credential";
diff --git a/e2e/font.spec.ts b/e2e/font.spec.ts
new file mode 100644
index 0000000..01e2433
--- /dev/null
+++ b/e2e/font.spec.ts
@@ -0,0 +1,37 @@
+import { expect, test } from "@playwright/test";
+
+test("loads Vazirmatn from the application origin", async ({ page }) => {
+ const fontRequests: string[] = [];
+
+ page.on("request", (request) => {
+ if (request.resourceType() === "font") {
+ fontRequests.push(request.url());
+ }
+ });
+
+ await page.goto("/auth");
+
+ const typography = await page.evaluate(async () => {
+ await document.fonts.load("400 16px Vazirmatn");
+ await document.fonts.ready;
+
+ const vazirmatnFace = [...document.fonts].find(
+ ({ family }) => family.replaceAll('"', "") === "Vazirmatn",
+ );
+
+ return {
+ family: getComputedStyle(document.documentElement).fontFamily,
+ fontStatus: vazirmatnFace?.status,
+ };
+ });
+
+ expect(typography.family).toBe("Vazirmatn, system-ui, sans-serif");
+ expect(typography.fontStatus).toBe("loaded");
+ expect(fontRequests).toHaveLength(1);
+
+ const applicationOrigin = new URL(page.url()).origin;
+ const fontUrl = new URL(fontRequests[0]);
+
+ expect(fontUrl.origin).toBe(applicationOrigin);
+ expect(fontUrl.pathname).toMatch(/Vazirmatn-Variable-.*\.woff2$/);
+});
diff --git a/e2e/localization.spec.ts b/e2e/localization.spec.ts
new file mode 100644
index 0000000..e4d35a7
--- /dev/null
+++ b/e2e/localization.spec.ts
@@ -0,0 +1,85 @@
+import { expect, test } from "@playwright/test";
+
+import { syntheticCredential } from "./fixtures";
+
+const locales = [
+ {
+ direction: "ltr",
+ enter: "Enter",
+ identifier: "Username, email, or mobile",
+ language: "en",
+ login: "Login",
+ password: "Password",
+ },
+ {
+ direction: "rtl",
+ enter: "ورود",
+ identifier: "نام کاربری، ایمیل یا موبایل",
+ language: "fa",
+ login: "ورود به سیستم",
+ password: "رمز عبور",
+ },
+] as const;
+
+test.describe("localized critical journeys", () => {
+ for (const locale of locales) {
+ test(`logs in using ${locale.language.toUpperCase()}/${locale.direction.toUpperCase()}`, async ({
+ page,
+ }) => {
+ await page.addInitScript((language) => {
+ localStorage.setItem("language", language);
+ localStorage.setItem("theme", "light");
+ }, locale.language);
+ await page.route("https://api.example.com/auth/login", async (route) => {
+ expect(route.request().postDataJSON()).toEqual({
+ identifier: "ada",
+ password: syntheticCredential,
+ });
+ await route.fulfill({
+ contentType: "application/json",
+ json: { access_token: "access-token" },
+ });
+ });
+ await page.route("https://api.example.com/account/me", async (route) => {
+ await route.fulfill({
+ contentType: "application/json",
+ json: {
+ email: "ada@example.com",
+ first_name: "Ada",
+ id: "user-1",
+ is_system_admin: false,
+ last_name: "Lovelace",
+ mobile: "09120000000",
+ permissions: [],
+ personnel_code: "100",
+ status: "active",
+ username: "ada",
+ },
+ });
+ });
+ await page.goto("/auth");
+
+ await expect(page.locator("html")).toHaveAttribute(
+ "dir",
+ locale.direction,
+ );
+ await expect(page.locator("html")).toHaveAttribute(
+ "lang",
+ locale.language,
+ );
+ await expect(
+ page.getByRole("heading", { name: locale.login }),
+ ).toBeVisible();
+ await page.getByLabel(locale.identifier).fill("ada");
+ await page.getByLabel(locale.password).fill(syntheticCredential);
+ await page.getByRole("button", { name: locale.enter }).click();
+
+ await expect(page).toHaveURL(/\/$/);
+ await expect(page.getByRole("link", { name: "kernel" })).toBeVisible();
+ await expect(page.locator("html")).toHaveAttribute(
+ "dir",
+ locale.direction,
+ );
+ });
+ }
+});
diff --git a/e2e/management.spec.ts b/e2e/management.spec.ts
new file mode 100644
index 0000000..b991ca7
--- /dev/null
+++ b/e2e/management.spec.ts
@@ -0,0 +1,337 @@
+import { expect, Page, test } from "@playwright/test";
+
+import { syntheticCredential } from "./fixtures";
+
+type RoleRecord = {
+ id: string;
+ name: string;
+ permissions: string[];
+};
+
+type UserRecord = {
+ email: string | null;
+ first_name: string;
+ id: string;
+ is_system_admin: boolean;
+ last_name: string;
+ mobile: string;
+ personnel_code: string;
+ status: "active" | "inactive";
+ username: string | null;
+};
+
+const mockAccount = async (
+ page: Page,
+ permissions: string[],
+ isSystemAdmin = false,
+) => {
+ await page.route("https://api.example.com/account/me", async (route) => {
+ await route.fulfill({
+ contentType: "application/json",
+ json: {
+ email: "manager@example.com",
+ first_name: "Test",
+ id: "manager-1",
+ is_system_admin: isSystemAdmin,
+ last_name: "Manager",
+ mobile: "09120000000",
+ permissions,
+ personnel_code: "100",
+ status: "active",
+ username: "manager",
+ },
+ });
+ });
+};
+
+test.describe("role and user management", () => {
+ test.beforeEach(async ({ page }) => {
+ await page.addInitScript(() => {
+ localStorage.setItem("language", "en");
+ });
+ });
+
+ test("creates a role after validation and assigns permissions", async ({
+ page,
+ }) => {
+ const roles: RoleRecord[] = [];
+ let createRequestCount = 0;
+
+ await mockAccount(page, ["roles.create", "roles.read"]);
+ await page.route("https://api.example.com/permissions", async (route) => {
+ await route.fulfill({
+ contentType: "application/json",
+ json: [
+ {
+ name: "users",
+ permissions: [
+ { name: "users.read", title: "Read users" },
+ { name: "users.update", title: "Update users" },
+ ],
+ title: "Users",
+ },
+ ],
+ });
+ });
+ await page.route("https://api.example.com/roles", async (route) => {
+ if (route.request().method() === "POST") {
+ createRequestCount += 1;
+ expect(route.request().postDataJSON()).toEqual({
+ name: "Operators",
+ permissions: ["users.read"],
+ });
+ roles.push({
+ id: "role-1",
+ name: "Operators",
+ permissions: ["users.read"],
+ });
+ await route.fulfill({
+ contentType: "application/json",
+ json: roles[0],
+ });
+ return;
+ }
+
+ await route.fulfill({ contentType: "application/json", json: roles });
+ });
+ await page.goto("/roles");
+ await expect(page.getByRole("link", { name: "kernel" })).toBeVisible();
+
+ await page.getByRole("button", { name: "Create" }).click();
+ await page.getByRole("button", { name: "Submit" }).click();
+ await expect(page.getByText("Please enter Name")).toBeVisible();
+ expect(createRequestCount).toBe(0);
+
+ await page.getByLabel("Name").fill("Operators");
+ await page.getByLabel("Read users").click();
+ await expect(page.getByLabel("Read users")).toBeChecked();
+ await page.getByRole("button", { name: "Submit" }).click();
+
+ await expect(page.getByText("Role created successfully.")).toBeVisible();
+ await expect(page.getByText("Operators")).toBeVisible();
+ expect(createRequestCount).toBe(1);
+ });
+
+ test("updates and deletes a role after confirmation", async ({ page }) => {
+ const roles: RoleRecord[] = [
+ {
+ id: "role-1",
+ name: "Operators",
+ permissions: ["users.read"],
+ },
+ ];
+
+ await mockAccount(page, ["roles.delete", "roles.read", "roles.update"]);
+ await page.route("https://api.example.com/permissions", async (route) => {
+ await route.fulfill({ contentType: "application/json", json: [] });
+ });
+ await page.route("https://api.example.com/roles", async (route) => {
+ await route.fulfill({ contentType: "application/json", json: roles });
+ });
+ await page.route("https://api.example.com/roles/role-1", async (route) => {
+ if (route.request().method() === "PATCH") {
+ expect(route.request().postDataJSON()).toEqual({
+ name: "Support",
+ permissions: ["users.read"],
+ });
+ roles[0] = { ...roles[0], name: "Support" };
+ await route.fulfill({
+ contentType: "application/json",
+ json: roles[0],
+ });
+ return;
+ }
+
+ if (route.request().method() === "DELETE") {
+ roles.splice(0, roles.length);
+ await route.fulfill({ status: 204 });
+ return;
+ }
+
+ await route.fulfill({
+ contentType: "application/json",
+ json: roles[0],
+ });
+ });
+ await page.goto("/roles");
+ const roleRow = page.getByRole("row", { name: /Operators/ });
+
+ await roleRow.getByRole("button", { name: "Update" }).click();
+ await page.getByLabel("Name").fill("Support");
+ await page.getByRole("button", { name: "Submit" }).click();
+ await expect(page.getByText("Role updated successfully.")).toBeVisible();
+ await expect(page.getByText("Support")).toBeVisible();
+
+ await page
+ .getByRole("row", { name: /Support/ })
+ .getByRole("button", { name: "Delete" })
+ .click();
+ await expect(
+ page.getByRole("dialog", {
+ name: "Are you sure you want to delete this role?",
+ }),
+ ).toBeVisible();
+ await page.getByRole("button", { name: "Yes" }).click();
+
+ await expect(page.getByText("Role deleted successfully.")).toBeVisible();
+ await expect(page.getByText("Support")).not.toBeVisible();
+ });
+
+ test("creates and updates a user, then displays a delete error", async ({
+ page,
+ }) => {
+ const users: UserRecord[] = [];
+
+ await mockAccount(page, [
+ "users.create",
+ "users.delete",
+ "users.read",
+ "users.update",
+ ]);
+ await page.route("https://api.example.com/roles", async (route) => {
+ await route.fulfill({ contentType: "application/json", json: [] });
+ });
+ await page.route("https://api.example.com/workspaces", async (route) => {
+ await route.fulfill({ contentType: "application/json", json: [] });
+ });
+ await page.route(
+ /https:\/\/api\.example\.com\/users(?:\?.*)?$/,
+ async (route) => {
+ if (route.request().method() === "POST") {
+ expect(route.request().postDataJSON()).toEqual({
+ first_name: "Ada",
+ last_name: "Lovelace",
+ mobile: "09121111111",
+ password: syntheticCredential,
+ personnel_code: "200",
+ });
+ users.push({
+ email: null,
+ first_name: "Ada",
+ id: "user-1",
+ is_system_admin: false,
+ last_name: "Lovelace",
+ mobile: "09121111111",
+ personnel_code: "200",
+ status: "active",
+ username: null,
+ });
+ await route.fulfill({
+ contentType: "application/json",
+ json: { ...users[0], role_ids: [], workspace_ids: [] },
+ });
+ return;
+ }
+
+ await route.fulfill({
+ contentType: "application/json",
+ json: { items: users, total: users.length },
+ });
+ },
+ );
+ await page.route("https://api.example.com/users/user-1", async (route) => {
+ if (route.request().method() === "PATCH") {
+ expect(route.request().postDataJSON()).toEqual({
+ first_name: "Augusta",
+ last_name: "Lovelace",
+ mobile: "09121111111",
+ personnel_code: "200",
+ });
+ users[0] = { ...users[0], first_name: "Augusta" };
+ await route.fulfill({
+ contentType: "application/json",
+ json: { ...users[0], role_ids: [], workspace_ids: [] },
+ });
+ return;
+ }
+
+ if (route.request().method() === "DELETE") {
+ await route.fulfill({
+ contentType: "application/json",
+ json: { message: "User cannot be deleted" },
+ status: 409,
+ });
+ return;
+ }
+
+ await route.fulfill({
+ contentType: "application/json",
+ json: { ...users[0], role_ids: [], workspace_ids: [] },
+ });
+ });
+ await page.goto("/users");
+
+ await page.getByRole("button", { name: "Create" }).click();
+ await page.getByRole("button", { name: "Submit" }).click();
+ await expect(page.getByText("Please enter First name")).toBeVisible();
+ const createDialog = page.getByRole("dialog", { name: "Create" });
+ await createDialog.getByLabel("First name").fill("Ada");
+ await createDialog.getByLabel("Last name").fill("Lovelace");
+ await createDialog.getByRole("textbox").nth(2).fill("09121111111");
+ await createDialog.getByLabel("Personnel code").fill("200");
+ await createDialog
+ .getByLabel("Password", { exact: true })
+ .fill(syntheticCredential);
+ await createDialog.getByLabel("Confirm password").fill(syntheticCredential);
+ await createDialog.getByRole("button", { name: "Submit" }).click();
+ await expect(page.getByText("User created successfully.")).toBeVisible();
+ await expect(page.getByText("Ada Lovelace")).toBeVisible();
+
+ await page
+ .getByRole("row", { name: /Ada Lovelace/ })
+ .getByRole("button", { name: "Update" })
+ .click();
+ const updateDialog = page.getByRole("dialog", { name: "Update" });
+ await updateDialog.getByLabel("First name").fill("Augusta");
+ await updateDialog.getByRole("button", { name: "Submit" }).click();
+ await expect(page.getByText("User updated successfully.")).toBeVisible();
+ await expect(page.getByText("Augusta Lovelace")).toBeVisible();
+
+ await page
+ .getByRole("row", { name: /Augusta Lovelace/ })
+ .getByRole("button", { name: "Delete" })
+ .click();
+ await expect(
+ page.getByRole("dialog", {
+ name: "Are you sure you want to delete this user?",
+ }),
+ ).toBeVisible();
+ await page.getByRole("button", { name: "Yes" }).click();
+ await expect(page.getByText("User cannot be deleted")).toBeVisible();
+ });
+
+ test("hides mutation controls from a read-only user", async ({ page }) => {
+ await mockAccount(page, ["users.read"]);
+ await page.route(
+ /https:\/\/api\.example\.com\/users(?:\?.*)?$/,
+ async (route) => {
+ await route.fulfill({
+ contentType: "application/json",
+ json: {
+ items: [
+ {
+ email: "ada@example.com",
+ first_name: "Ada",
+ id: "user-1",
+ is_system_admin: false,
+ last_name: "Lovelace",
+ mobile: "09121111111",
+ personnel_code: "200",
+ status: "active",
+ username: "ada",
+ },
+ ],
+ total: 1,
+ },
+ });
+ },
+ );
+ await page.goto("/users");
+ await expect(page.getByText("Ada Lovelace")).toBeVisible();
+
+ await expect(page.getByRole("button", { name: "Create" })).toHaveCount(0);
+ await expect(page.getByRole("button", { name: "Update" })).toHaveCount(0);
+ await expect(page.getByRole("button", { name: "Delete" })).toHaveCount(0);
+ await expect(page.getByRole("button", { name: "Active" })).toHaveCount(0);
+ });
+});
diff --git a/e2e/security-headers.spec.ts b/e2e/security-headers.spec.ts
new file mode 100644
index 0000000..4088723
--- /dev/null
+++ b/e2e/security-headers.spec.ts
@@ -0,0 +1,33 @@
+import { expect, test } from "@playwright/test";
+
+test.describe("production security headers", () => {
+ test.beforeEach(async ({ page }) => {
+ await page.addInitScript(() => {
+ localStorage.setItem("language", "en");
+ });
+ });
+
+ test("protects the built application response", async ({ page }) => {
+ const response = await page.goto("/auth");
+
+ expect(response).not.toBeNull();
+ await expect(page.getByRole("heading", { name: "Login" })).toBeVisible();
+
+ const headers = response!.headers();
+ const contentSecurityPolicy = headers["content-security-policy"];
+
+ expect(contentSecurityPolicy).toContain("default-src 'self'");
+ expect(contentSecurityPolicy).toContain("object-src 'none'");
+ expect(contentSecurityPolicy).toContain("frame-ancestors 'none'");
+ expect(contentSecurityPolicy).toContain("upgrade-insecure-requests");
+ expect(headers["strict-transport-security"]).toBe(
+ "max-age=31536000; includeSubDomains; preload",
+ );
+ expect(headers["x-frame-options"]).toBe("DENY");
+ expect(headers["x-content-type-options"]).toBe("nosniff");
+ expect(headers["referrer-policy"]).toBe("strict-origin-when-cross-origin");
+ expect(headers["permissions-policy"]).toBe(
+ "camera=(), geolocation=(), microphone=(), payment=(), usb=()",
+ );
+ });
+});
diff --git a/e2e/visual.spec.ts b/e2e/visual.spec.ts
new file mode 100644
index 0000000..4b6a5cf
--- /dev/null
+++ b/e2e/visual.spec.ts
@@ -0,0 +1,122 @@
+import { expect, Page, test } from "@playwright/test";
+
+const account = {
+ email: "ada@example.com",
+ first_name: "Ada",
+ id: "user-1",
+ is_system_admin: true,
+ last_name: "Lovelace",
+ mobile: "09120000000",
+ permissions: [],
+ personnel_code: "100",
+ status: "active",
+ username: "ada",
+};
+
+const preparePage = async (page: Page) => {
+ await page.addInitScript(() => {
+ localStorage.setItem("language", "en");
+ localStorage.setItem("theme", "light");
+ });
+};
+
+const mockAuthenticatedPage = async (page: Page) => {
+ await page.route("https://api.example.com/account/me", async (route) => {
+ await route.fulfill({
+ contentType: "application/json",
+ json: account,
+ });
+ });
+};
+
+test.describe("visual regression", () => {
+ test.beforeEach(async ({ page }) => {
+ await preparePage(page);
+ });
+
+ test("login page", async ({ page }) => {
+ await page.goto("/auth");
+ await expect(page.getByRole("heading", { name: "Login" })).toBeVisible();
+ await expect(page).toHaveScreenshot("login.png", { fullPage: true });
+ });
+
+ test("forgot password page", async ({ page }) => {
+ await page.goto("/auth/forgot-password");
+ await expect(
+ page.getByRole("heading", { name: "Forgot password" }),
+ ).toBeVisible();
+ await expect(page).toHaveScreenshot("forgot-password.png", {
+ fullPage: true,
+ });
+ });
+
+ test("registration page", async ({ page }) => {
+ await page.goto("/auth/register");
+ await expect(page.getByRole("heading", { name: "Register" })).toBeVisible();
+ await expect(page).toHaveScreenshot("register.png", { fullPage: true });
+ });
+
+ test("authenticated layout and users table", async ({ page }) => {
+ await mockAuthenticatedPage(page);
+ await page.route(
+ /https:\/\/api\.example\.com\/users(?:\?.*)?$/,
+ async (route) => {
+ await route.fulfill({
+ contentType: "application/json",
+ json: {
+ items: [
+ {
+ ...account,
+ is_system_admin: false,
+ },
+ {
+ email: "grace@example.com",
+ first_name: "Grace",
+ id: "user-2",
+ is_system_admin: false,
+ last_name: "Hopper",
+ mobile: "09121111111",
+ personnel_code: "101",
+ status: "inactive",
+ username: "grace",
+ },
+ ],
+ total: 2,
+ },
+ });
+ },
+ );
+
+ await page.goto("/users");
+ await expect(page.getByText("Ada Lovelace")).toBeVisible();
+ await expect(page.getByText("Grace Hopper")).toBeVisible();
+ await expect(page).toHaveScreenshot("users-table.png", { fullPage: true });
+ });
+
+ test("create role form", async ({ page }) => {
+ await mockAuthenticatedPage(page);
+ await page.route("https://api.example.com/permissions", async (route) => {
+ await route.fulfill({
+ contentType: "application/json",
+ json: [
+ {
+ name: "users",
+ permissions: [
+ { name: "users.read", title: "Read users" },
+ { name: "users.update", title: "Update users" },
+ ],
+ title: "Users",
+ },
+ ],
+ });
+ });
+ await page.route("https://api.example.com/roles", async (route) => {
+ await route.fulfill({ contentType: "application/json", json: [] });
+ });
+
+ await page.goto("/roles");
+ await page.getByRole("button", { name: "Create" }).click();
+ await expect(page.getByRole("dialog", { name: "Create" })).toBeVisible();
+ await expect(page).toHaveScreenshot("create-role.png", { fullPage: true });
+ });
+});
diff --git a/e2e/visual.spec.ts-snapshots/create-role-visual-chromium.png b/e2e/visual.spec.ts-snapshots/create-role-visual-chromium.png
new file mode 100644
index 0000000..a74f066
Binary files /dev/null and b/e2e/visual.spec.ts-snapshots/create-role-visual-chromium.png differ
diff --git a/e2e/visual.spec.ts-snapshots/forgot-password-visual-chromium.png b/e2e/visual.spec.ts-snapshots/forgot-password-visual-chromium.png
new file mode 100644
index 0000000..290e6cd
Binary files /dev/null and b/e2e/visual.spec.ts-snapshots/forgot-password-visual-chromium.png differ
diff --git a/e2e/visual.spec.ts-snapshots/login-visual-chromium.png b/e2e/visual.spec.ts-snapshots/login-visual-chromium.png
new file mode 100644
index 0000000..fd5efa2
Binary files /dev/null and b/e2e/visual.spec.ts-snapshots/login-visual-chromium.png differ
diff --git a/e2e/visual.spec.ts-snapshots/register-visual-chromium.png b/e2e/visual.spec.ts-snapshots/register-visual-chromium.png
new file mode 100644
index 0000000..8802312
Binary files /dev/null and b/e2e/visual.spec.ts-snapshots/register-visual-chromium.png differ
diff --git a/e2e/visual.spec.ts-snapshots/users-table-visual-chromium.png b/e2e/visual.spec.ts-snapshots/users-table-visual-chromium.png
new file mode 100644
index 0000000..3cf6d65
Binary files /dev/null and b/e2e/visual.spec.ts-snapshots/users-table-visual-chromium.png differ
diff --git a/index.html b/index.html
index f2c9f7a..150a382 100644
--- a/index.html
+++ b/index.html
@@ -6,7 +6,7 @@
kernel
-
+
diff --git a/nginx.conf b/nginx.conf
new file mode 100644
index 0000000..4be38be
--- /dev/null
+++ b/nginx.conf
@@ -0,0 +1,23 @@
+server {
+ listen 80;
+ server_name _;
+
+ root /usr/share/nginx/html;
+ index index.html;
+
+ add_header Content-Security-Policy "default-src 'self'; base-uri 'self'; connect-src 'self' https:; font-src 'self' data:; form-action 'self'; frame-ancestors 'none'; img-src 'self' data:; object-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; upgrade-insecure-requests" always;
+ add_header Permissions-Policy "camera=(), geolocation=(), microphone=(), payment=(), usb=()" always;
+ add_header Referrer-Policy "strict-origin-when-cross-origin" always;
+ add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
+ add_header X-Content-Type-Options "nosniff" always;
+ add_header X-Frame-Options "DENY" always;
+
+ location / {
+ try_files $uri $uri/ /index.html;
+ }
+
+ location ~* \.(?:css|js|svg|woff2?)$ {
+ expires 1y;
+ try_files $uri =404;
+ }
+}
diff --git a/package-lock.json b/package-lock.json
index dd42f7e..47ca970 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,53 +1,62 @@
{
"name": "kernel",
- "version": "0.0.0",
+ "version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "kernel",
- "version": "0.0.0",
+ "version": "1.0.0",
"dependencies": {
- "antd": "^6.5.0",
+ "antd": "^6.5.2",
"antd-style": "^4.1.0",
- "axios": "^1.18.1",
+ "axios": "^1.19.0",
"dayjs": "^1.11.21",
- "i18next": "^26.3.3",
+ "i18next": "^26.3.6",
"jalaliday": "^3.1.1",
"lodash": "^4.18.1",
- "react": "^19.2.7",
- "react-dom": "^19.2.7",
- "react-i18next": "^17.0.8",
- "react-router-dom": "^7.18.0"
+ "react": "^19.2.8",
+ "react-dom": "^19.2.8",
+ "react-i18next": "^17.0.11",
+ "react-router": "^8.3.0"
},
"devDependencies": {
+ "@axe-core/playwright": "4.12.1",
"@eslint/js": "^10.0.1",
+ "@pact-foundation/pact": "^17.0.1",
"@playwright/test": "^1.62.0",
+ "@stryker-mutator/core": "^9.6.1",
+ "@stryker-mutator/vitest-runner": "^9.6.1",
"@testing-library/dom": "^10.4.1",
"@testing-library/jest-dom": "^7.0.0",
"@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1",
"@types/lodash": "^4.17.24",
- "@types/node": "^26.0.1",
+ "@types/node": "^24.13.3",
"@types/react": "^19.2.17",
"@types/react-dom": "^19.2.3",
- "@typescript-eslint/types": "^8.62.0",
+ "@typescript-eslint/types": "^8.65.0",
"@vitejs/plugin-basic-ssl": "^2.3.0",
- "@vitejs/plugin-react": "^6.0.3",
+ "@vitejs/plugin-react": "^6.0.5",
"@vitest/coverage-v8": "^4.1.10",
- "eslint": "^10.6.0",
+ "chrome-launcher": "^1.2.1",
+ "eslint": "^10.8.0",
"eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-react-refresh": "^0.5.3",
- "eslint-plugin-simple-import-sort": "^13.0.0",
- "globals": "^17.7.0",
- "jsdom": "^29.1.1",
- "knip": "^6.22.0",
+ "eslint-plugin-simple-import-sort": "^14.0.0",
+ "globals": "^17.8.0",
+ "jsdom": "^30.0.1",
+ "knip": "^6.29.0",
+ "lighthouse": "^13.4.1",
"msw": "^2.15.0",
"prettier": "^3.9.6",
"typescript": "~6.0.3",
- "typescript-eslint": "^8.62.0",
- "vite": "^8.1.0",
+ "typescript-eslint": "^8.65.0",
+ "vite": "^8.2.0",
"vitest": "^4.1.10"
+ },
+ "engines": {
+ "node": ">=24.15.0 <25"
}
},
"node_modules/@adobe/css-tools": {
@@ -150,56 +159,127 @@
"react-dom": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
+ "node_modules/@apm-js-collab/code-transformer": {
+ "version": "0.18.1",
+ "resolved": "https://registry.npmjs.org/@apm-js-collab/code-transformer/-/code-transformer-0.18.1.tgz",
+ "integrity": "sha512-u1Hb6bHjWtkSpiprwVP6YaHC1DTN4RAU3zYkUDUe7WMnJwdyU1pwTL9dFKiSJB9IiLue/EQovmyx6xhU7FFtAQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/estree": "^1.0.8",
+ "astring": "^1.9.0",
+ "esquery": "^1.7.0",
+ "meriyah": "^6.1.4",
+ "semifies": "^1.0.0",
+ "source-map": "^0.6.0"
+ },
+ "bin": {
+ "code-transformer": "cli.js"
+ }
+ },
+ "node_modules/@apm-js-collab/code-transformer-bundler-plugins": {
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/@apm-js-collab/code-transformer-bundler-plugins/-/code-transformer-bundler-plugins-0.7.3.tgz",
+ "integrity": "sha512-qNbPwuMZ8f5ZuGj/ttPeB7a6C/S1bB6tNYaEL5vNiRKydSAxa4AU0gxCWgaP4fVju+AuwhcumSFjrEcGF9Dv7Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@apm-js-collab/code-transformer": "^0.18.0",
+ "es-module-lexer": "^2.1.0",
+ "magic-string": "^0.30.21",
+ "module-details-from-path": "^1.0.4"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@apm-js-collab/code-transformer/node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/@apm-js-collab/tracing-hooks": {
+ "version": "0.13.0",
+ "resolved": "https://registry.npmjs.org/@apm-js-collab/tracing-hooks/-/tracing-hooks-0.13.0.tgz",
+ "integrity": "sha512-mTvWz9rnQwx1U3h0XPTHaX7bgfkpipLLTQyjlC2cdhQpQEuoLT0AGzoydeoq2NxfEVv6fWOOETcSbb2nptleyw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@apm-js-collab/code-transformer": "^0.18.0",
+ "debug": "^4.4.1",
+ "module-details-from-path": "^1.0.4"
+ }
+ },
"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==",
+ "version": "6.0.5",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-6.0.5.tgz",
+ "integrity": "sha512-mbhpPMmnw/kwW19aRNmSUl1QzLbdGo1SCuE49BT98MNwqF6zaHb3o2owssFc/PEO/4t2UjqtCNwocuDtJornzA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@asamuzakjp/generational-cache": "^1.0.1",
- "@csstools/css-calc": "^3.2.0",
- "@csstools/css-color-parser": "^4.1.0",
+ "@csstools/css-calc": "^3.2.1",
+ "@csstools/css-color-parser": "^4.1.9",
"@csstools/css-parser-algorithms": "^4.0.0",
- "@csstools/css-tokenizer": "^4.0.0"
+ "@csstools/css-tokenizer": "^4.0.0",
+ "lru-cache": "^11.5.2"
},
"engines": {
- "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ "node": "^22.13.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@asamuzakjp/css-color/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/@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==",
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-8.3.0.tgz",
+ "integrity": "sha512-UJLfKXBhrc8i1vH2eJXuYQMwlsLKWFw3O+CPqXSuVEiikeAim3UgrfWX0k4tA/X8cRFM8iZ7OaqBokFGbYusdg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@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"
+ "is-potential-custom-element-name": "^1.0.1",
+ "lru-cache": "^11.5.2"
},
"engines": {
- "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ "node": "^22.13.0 || >=24.0.0"
}
},
- "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==",
+ "node_modules/@asamuzakjp/dom-selector/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": "MIT",
+ "license": "BlueOak-1.0.0",
"engines": {
- "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ "node": "20 || >=22"
}
},
- "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==",
+ "node_modules/@axe-core/playwright": {
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@axe-core/playwright/-/playwright-4.12.1.tgz",
+ "integrity": "sha512-rMd7xriptqKpP+w5265i4Hdkv2X5kbu6uiBi/B2I7uf3hieRBM3qDCfaKPtxfiYb2mKXfF+yLODJwIx+Jv1GDw==",
"dev": true,
- "license": "MIT"
+ "license": "MPL-2.0",
+ "dependencies": {
+ "axe-core": "~4.12.1"
+ },
+ "peerDependencies": {
+ "playwright-core": ">= 1.0.0"
+ }
},
"node_modules/@babel/code-frame": {
"version": "7.29.7",
@@ -272,6 +352,19 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@babel/helper-annotate-as-pure": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz",
+ "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"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",
@@ -289,6 +382,28 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@babel/helper-create-class-features-plugin": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.7.tgz",
+ "integrity": "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.29.7",
+ "@babel/helper-member-expression-to-functions": "^7.29.7",
+ "@babel/helper-optimise-call-expression": "^7.29.7",
+ "@babel/helper-replace-supers": "^7.29.7",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7",
+ "@babel/traverse": "^7.29.7",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
"node_modules/@babel/helper-globals": {
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
@@ -298,6 +413,20 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@babel/helper-member-expression-to-functions": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz",
+ "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"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",
@@ -329,6 +458,61 @@
"@babel/core": "^7.0.0"
}
},
+ "node_modules/@babel/helper-optimise-call-expression": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz",
+ "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
+ "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-replace-supers": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz",
+ "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-member-expression-to-functions": "^7.29.7",
+ "@babel/helper-optimise-call-expression": "^7.29.7",
+ "@babel/traverse": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-skip-transparent-expression-wrappers": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz",
+ "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"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",
@@ -386,6 +570,163 @@
"node": ">=6.0.0"
}
},
+ "node_modules/@babel/plugin-proposal-decorators": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.29.7.tgz",
+ "integrity": "sha512-EtU0Hi3GvrTqD56xKmZvV/uCXK2ZbwVNPNLAquVItcAZpUhkXwWlo3Fmj0c2LxgSf2I8IDULeAepwNP1OefLXg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-class-features-plugin": "^7.29.7",
+ "@babel/helper-plugin-utils": "^7.29.7",
+ "@babel/plugin-syntax-decorators": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-decorators": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.29.7.tgz",
+ "integrity": "sha512-9MTTLbF39X6sqM92JPEsoI7++26hjZvzkxKZy64aMhWLH2mPkJ/Q3AV4QLmls3R14FpSpkOwQQfUh962JGQxxg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-jsx": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz",
+ "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-typescript": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz",
+ "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-destructuring": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.29.7.tgz",
+ "integrity": "sha512-iPX8aD6H9zV5s7ZsqTdNocPN/MGQ5sSMnElKrktxjJRMnB2jN/1p2+R7GkfD6CAYoVFqy5A4XnSIUeGgJzIWpg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7",
+ "@babel/traverse": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-explicit-resource-management": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.29.7.tgz",
+ "integrity": "sha512-Rstj7coNz8sE+7Ju7ihpHLI564lsK5pUpNNlvptCIC/16E/S5hbl6n3kESPKdNRmqEWlpn5xpS5Q2dvXBsySLw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7",
+ "@babel/plugin-transform-destructuring": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-modules-commonjs": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.29.7.tgz",
+ "integrity": "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-transforms": "^7.29.7",
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-typescript": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.7.tgz",
+ "integrity": "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.29.7",
+ "@babel/helper-create-class-features-plugin": "^7.29.7",
+ "@babel/helper-plugin-utils": "^7.29.7",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7",
+ "@babel/plugin-syntax-typescript": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/preset-typescript": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz",
+ "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/helper-validator-option": "^7.27.1",
+ "@babel/plugin-syntax-jsx": "^7.27.1",
+ "@babel/plugin-transform-modules-commonjs": "^7.27.1",
+ "@babel/plugin-transform-typescript": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
"node_modules/@babel/runtime": {
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz",
@@ -604,9 +945,9 @@
}
},
"node_modules/@emnapi/core": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz",
- "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==",
+ "version": "1.11.2",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.2.tgz",
+ "integrity": "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==",
"dev": true,
"license": "MIT",
"optional": true,
@@ -616,9 +957,9 @@
}
},
"node_modules/@emnapi/runtime": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz",
- "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==",
+ "version": "1.11.2",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.2.tgz",
+ "integrity": "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==",
"dev": true,
"license": "MIT",
"optional": true,
@@ -858,9 +1199,9 @@
}
},
"node_modules/@eslint/config-helpers": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz",
- "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==",
+ "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,
"license": "Apache-2.0",
"dependencies": {
@@ -946,29 +1287,85 @@
}
}
},
- "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==",
+ "node_modules/@formatjs/ecma402-abstract": {
+ "version": "2.3.6",
+ "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.6.tgz",
+ "integrity": "sha512-HJnTFeRM2kVFVr5gr5kH1XP6K0JcJtE7Lzvtr3FS/so5f1kpsqqqxy5JF+FRaO6H2qmcMfAUIox7AJteieRtVw==",
"dev": true,
- "license": "Apache-2.0",
+ "license": "MIT",
"dependencies": {
- "@humanfs/types": "^0.15.0"
- },
- "engines": {
- "node": ">=18.18.0"
+ "@formatjs/fast-memoize": "2.2.7",
+ "@formatjs/intl-localematcher": "0.6.2",
+ "decimal.js": "^10.4.3",
+ "tslib": "^2.8.0"
}
},
- "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==",
+ "node_modules/@formatjs/fast-memoize": {
+ "version": "2.2.7",
+ "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.7.tgz",
+ "integrity": "sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==",
"dev": true,
- "license": "Apache-2.0",
+ "license": "MIT",
"dependencies": {
- "@humanfs/core": "^0.19.2",
- "@humanfs/types": "^0.15.0",
- "@humanwhocodes/retry": "^0.4.0"
+ "tslib": "^2.8.0"
+ }
+ },
+ "node_modules/@formatjs/icu-messageformat-parser": {
+ "version": "2.11.4",
+ "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.11.4.tgz",
+ "integrity": "sha512-7kR78cRrPNB4fjGFZg3Rmj5aah8rQj9KPzuLsmcSn4ipLXQvC04keycTI1F7kJYDwIXtT2+7IDEto842CfZBtw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@formatjs/ecma402-abstract": "2.3.6",
+ "@formatjs/icu-skeleton-parser": "1.8.16",
+ "tslib": "^2.8.0"
+ }
+ },
+ "node_modules/@formatjs/icu-skeleton-parser": {
+ "version": "1.8.16",
+ "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.16.tgz",
+ "integrity": "sha512-H13E9Xl+PxBd8D5/6TVUluSpxGNvFSlN/b3coUp0e0JpuWXXnQDiavIpY3NnvSp4xhEMoXyyBvVfdFX8jglOHQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@formatjs/ecma402-abstract": "2.3.6",
+ "tslib": "^2.8.0"
+ }
+ },
+ "node_modules/@formatjs/intl-localematcher": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.6.2.tgz",
+ "integrity": "sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.8.0"
+ }
+ },
+ "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": {
+ "@humanfs/types": "^0.15.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "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": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/core": "^0.19.2",
+ "@humanfs/types": "^0.15.0",
+ "@humanwhocodes/retry": "^0.4.0"
},
"engines": {
"node": ">=18.18.0"
@@ -1022,6 +1419,30 @@
"node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
}
},
+ "node_modules/@inquirer/checkbox": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-5.2.1.tgz",
+ "integrity": "sha512-b6xmA/VlTe0ZgDQHDui+Nav470u7u49nRd8/iuhOcQPO9Ch7lGuogydhi2VOmNlZ+zXcM8IcPuNSwQcdJaF/kw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/ansi": "^2.0.7",
+ "@inquirer/core": "^11.2.1",
+ "@inquirer/figures": "^2.0.7",
+ "@inquirer/type": "^4.0.7"
+ },
+ "engines": {
+ "node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@inquirer/confirm": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-6.1.1.tgz",
@@ -1071,6 +1492,73 @@
}
}
},
+ "node_modules/@inquirer/editor": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-5.2.2.tgz",
+ "integrity": "sha512-ZRVd/oD+sYsUd5zVm0NflqEzlqfYCyHNsqkHl2oWXEUHs12tCbcSFi+wVFEvD8+LGRaMUsVrE7qeo6lSG/S1Vg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/core": "^11.2.1",
+ "@inquirer/external-editor": "^3.0.3",
+ "@inquirer/type": "^4.0.7"
+ },
+ "engines": {
+ "node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@inquirer/expand": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-5.1.1.tgz",
+ "integrity": "sha512-YmQpenjbFSHAK3sOd44puHh3V1KXXr+JiNpUztoSQ4drLh2rTVzTap/YtlAVu/5xavifIlBfNEzJ/neZJ1a/1g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/core": "^11.2.1",
+ "@inquirer/type": "^4.0.7"
+ },
+ "engines": {
+ "node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@inquirer/external-editor": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-3.0.3.tgz",
+ "integrity": "sha512-6thf5I8q7lZwzGLAxPaaGEREEkZ3nyePPDQ1oyobblxmEE8mqTLguScP7pDjUTAibiyb4hfXl+qjUEJ+di/aNA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chardet": "^2.1.1",
+ "iconv-lite": "^0.7.2"
+ },
+ "engines": {
+ "node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@inquirer/figures": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-2.0.7.tgz",
@@ -1081,6 +1569,172 @@
"node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
}
},
+ "node_modules/@inquirer/input": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-5.1.2.tgz",
+ "integrity": "sha512-9K/DDBSQpOyZSkt6sOVP9Vo0TR7atX2kuILsUu0x3wVcVbe97lJwIJKMLdMw25tDYuXl/qp6erT0Xs1rfmcfZg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/core": "^11.2.1",
+ "@inquirer/type": "^4.0.7"
+ },
+ "engines": {
+ "node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@inquirer/number": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-4.1.1.tgz",
+ "integrity": "sha512-XF4IXAbPnGPgw0wsbC/i2tPcyfdZgDpUlhsqU0SfT4IRIGWha6Xm9VRgN5yYxJq+jnyXlfXI/nQ3ulfk0iEICA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/core": "^11.2.1",
+ "@inquirer/type": "^4.0.7"
+ },
+ "engines": {
+ "node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@inquirer/password": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-5.1.1.tgz",
+ "integrity": "sha512-3XBfF7DAsp5qeDsvN5Rd1HmbNokVvEQoUM0QLrRcybC9nX96w3Pbmu7qUsb3IT3J3jBvs2+mTXaKHOUsgHMLzg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/ansi": "^2.0.7",
+ "@inquirer/core": "^11.2.1",
+ "@inquirer/type": "^4.0.7"
+ },
+ "engines": {
+ "node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@inquirer/prompts": {
+ "version": "8.5.2",
+ "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-8.5.2.tgz",
+ "integrity": "sha512-IYR/3C/paEVVQYQvdDlFZVjRCJVYHHON0XXMH91KO9GSxs0TdKYWlUdvfQl2EfAHDxUaN3IBffkE/BDTh5nJ6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/checkbox": "^5.2.1",
+ "@inquirer/confirm": "^6.1.1",
+ "@inquirer/editor": "^5.2.2",
+ "@inquirer/expand": "^5.1.1",
+ "@inquirer/input": "^5.1.2",
+ "@inquirer/number": "^4.1.1",
+ "@inquirer/password": "^5.1.1",
+ "@inquirer/rawlist": "^5.3.1",
+ "@inquirer/search": "^4.2.1",
+ "@inquirer/select": "^5.2.1"
+ },
+ "engines": {
+ "node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@inquirer/rawlist": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-5.3.1.tgz",
+ "integrity": "sha512-QqdTqQddL3qPX/PPrjobpsO25NZ4dWXgTLenrR445L2ptLEYE6Z+PD5c5CNDJNx4ugRgELAIpSIJxZaO2jJ2Og==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/core": "^11.2.1",
+ "@inquirer/type": "^4.0.7"
+ },
+ "engines": {
+ "node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@inquirer/search": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-4.2.1.tgz",
+ "integrity": "sha512-xJj8QWKRSrfKoBIITLZK61dD3zwo0Rz11fgDImku30/Oe81zMdIdGgrLY2h6RkJ+KZ/GhNYIRMKnH/62qBTA5g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/core": "^11.2.1",
+ "@inquirer/figures": "^2.0.7",
+ "@inquirer/type": "^4.0.7"
+ },
+ "engines": {
+ "node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@inquirer/select": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-5.2.1.tgz",
+ "integrity": "sha512-FlDndEUww8m7BfukO2nJa25vhD+H5jxxCv4oGioKqzyWz3nPHhhw4LKdYRSlXuAx7DsdWia7iyaBPKKS95Evfw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/ansi": "^2.0.7",
+ "@inquirer/core": "^11.2.1",
+ "@inquirer/figures": "^2.0.7",
+ "@inquirer/type": "^4.0.7"
+ },
+ "engines": {
+ "node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@inquirer/type": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.0.7.tgz",
@@ -1171,22 +1825,25 @@
"license": "MIT"
},
"node_modules/@napi-rs/wasm-runtime": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz",
- "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.1.tgz",
+ "integrity": "sha512-KjZdi8Q1wh89gsVmghvbrMgWl6ZWmRmHV6wjB7/g4Zf0dyO+hH3neZUtuDNPO00qq5YE5RITVWvrIZKRaAmzGQ==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@tybys/wasm-util": "^0.10.3"
},
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=23.5.0"
+ },
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Brooooooklyn"
},
"peerDependencies": {
- "@emnapi/core": "^1.7.1",
- "@emnapi/runtime": "^1.7.1"
+ "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.3",
+ "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.3"
}
},
"node_modules/@open-draft/deferred-promise": {
@@ -1214,27 +1871,148 @@
"dev": true,
"license": "MIT"
},
- "node_modules/@oxc-parser/binding-android-arm-eabi": {
- "version": "0.137.0",
- "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm-eabi/-/binding-android-arm-eabi-0.137.0.tgz",
- "integrity": "sha512-KDs+0VPdEmasOkpuJHW9V5WCF+cvYdMQv2Jd+aJXt+cxIx12NToRQRbXaRwUEDsZw+/jMk81Ve8ZFbjUkJTOwA==",
- "cpu": [
- "arm"
- ],
+ "node_modules/@opentelemetry/api": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz",
+ "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==",
"dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/@opentelemetry/api-logs": {
+ "version": "0.220.0",
+ "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.220.0.tgz",
+ "integrity": "sha512-CmVa4ImJ+ynfrPMNaAXHET6Bhb44SwzmfyVJFq9ni2jgXJR/l7C6gfVFddNmHP+ZOkP9cf4f9DBe68qVLTHc9w==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@opentelemetry/api": "^1.3.0"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/@opentelemetry/core": {
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.10.0.tgz",
+ "integrity": "sha512-/wNZ8twnEQQA4HoHu22+vcsdru6pWPWxW+7w+FlxT6Id7PE/WIbZmVKkte+PF72e0F2dnImFeHD2syyE1Mw6MQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@opentelemetry/semantic-conventions": "^1.29.0"
+ },
+ "engines": {
+ "node": "^18.19.0 || >=20.6.0"
+ },
+ "peerDependencies": {
+ "@opentelemetry/api": ">=1.0.0 <1.10.0"
+ }
+ },
+ "node_modules/@opentelemetry/instrumentation": {
+ "version": "0.220.0",
+ "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.220.0.tgz",
+ "integrity": "sha512-xQx3E2WxP1mDvKzxLxX+CTCtNLa560YJZ3087qYHerl2YmiKpv7AH+dAy7vmx+eVrZ5BwhfWUAVoKOoxCNHcpw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@opentelemetry/api-logs": "0.220.0",
+ "import-in-the-middle": "^3.0.0",
+ "require-in-the-middle": "^8.0.0"
+ },
+ "engines": {
+ "node": "^18.19.0 || >=20.6.0"
+ },
+ "peerDependencies": {
+ "@opentelemetry/api": "^1.3.0"
+ }
+ },
+ "node_modules/@opentelemetry/resources": {
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.10.0.tgz",
+ "integrity": "sha512-q6MMm2zhggzsHVNbabYwut+a6nbuQQe3URUoxaojM/8K1IBfwwPzvxIjNi2/lI1TFe+fMHMW9MWhrtDLEXEnkA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@opentelemetry/core": "2.10.0",
+ "@opentelemetry/semantic-conventions": "^1.29.0"
+ },
+ "engines": {
+ "node": "^18.19.0 || >=20.6.0"
+ },
+ "peerDependencies": {
+ "@opentelemetry/api": ">=1.3.0 <1.10.0"
+ }
+ },
+ "node_modules/@opentelemetry/sdk-trace": {
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace/-/sdk-trace-2.10.0.tgz",
+ "integrity": "sha512-MfQGq3GRmTh5fM/y+OjaO0vj6+luCB1XO2gfXCalKCfgKw0eHL++sm75DNweC6ohlp+aFvACqeE0fYayqdRaoQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@opentelemetry/core": "2.10.0",
+ "@opentelemetry/resources": "2.10.0",
+ "@opentelemetry/semantic-conventions": "^1.29.0"
+ },
+ "engines": {
+ "node": "^18.19.0 || >=20.6.0"
+ },
+ "peerDependencies": {
+ "@opentelemetry/api": ">=1.3.0 <1.10.0"
+ }
+ },
+ "node_modules/@opentelemetry/sdk-trace-base": {
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.10.0.tgz",
+ "integrity": "sha512-GuYQQT7QD2EeO8lcZLRQzcbOyhqAzL+6WWTKTU9mSUBYBazkEDl+VrQcXQhbB08OWM9anD1aHleVadzulpOaUQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@opentelemetry/core": "2.10.0",
+ "@opentelemetry/resources": "2.10.0",
+ "@opentelemetry/sdk-trace": "2.10.0",
+ "@opentelemetry/semantic-conventions": "^1.29.0"
+ },
+ "engines": {
+ "node": "^18.19.0 || >=20.6.0"
+ },
+ "peerDependencies": {
+ "@opentelemetry/api": ">=1.3.0 <1.10.0"
+ }
+ },
+ "node_modules/@opentelemetry/semantic-conventions": {
+ "version": "1.43.0",
+ "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.43.0.tgz",
+ "integrity": "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@oxc-parser/binding-android-arm-eabi": {
+ "version": "0.140.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm-eabi/-/binding-android-arm-eabi-0.140.0.tgz",
+ "integrity": "sha512-ZfjDZ422mo7eo3b3VltqNsV9kmv1qt/sPEAMSl64iOSwhVfd0eIZ9LB79Mbs1xYXJnk7WSROwzBCKDIiVxPTvQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@oxc-parser/binding-android-arm64": {
- "version": "0.137.0",
- "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.137.0.tgz",
- "integrity": "sha512-WhALNzfy3x/RfC6bsqX+csavuUY0yHHE7XfgPE5M542uhoBZUUoGTPG+nkMbGoG4+gcfss5s7urMyn5QBHu0sw==",
+ "version": "0.140.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.140.0.tgz",
+ "integrity": "sha512-Ia8jSvikUX6Sf+Ht+KOCUF/k1HpR0VlmqIYymubmWDebOEGtsyliHDR6JxsZ4IX3/c/GbrB1uh09aVGQv/LQmQ==",
"cpu": [
"arm64"
],
@@ -1249,9 +2027,9 @@
}
},
"node_modules/@oxc-parser/binding-darwin-arm64": {
- "version": "0.137.0",
- "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.137.0.tgz",
- "integrity": "sha512-bFPr5hgmNMOMoyPTGtdsK4Ug21RovIPojRMgDDhSp1LtCnc/DkLwGONKjgRjszg677RlGnkYSviQ8hHaUPOVYA==",
+ "version": "0.140.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.140.0.tgz",
+ "integrity": "sha512-G6VK0nK61pH0d0mBjUqSZbVxGqqO5uzeginLDQj+gOO6ObfJjXRwgkD/ol0w1INcnFeAb6YGGO7qc3ueGHaycQ==",
"cpu": [
"arm64"
],
@@ -1266,9 +2044,9 @@
}
},
"node_modules/@oxc-parser/binding-darwin-x64": {
- "version": "0.137.0",
- "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.137.0.tgz",
- "integrity": "sha512-CL5dMm1asqXIDZHg14FLxj3Mc36w8PI7xCWh1uA4is6z8g2XrIILoTcQYOxDbwzuk34RDPX5IAGUxZr6LA9KAg==",
+ "version": "0.140.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.140.0.tgz",
+ "integrity": "sha512-HazBOuZzd2pO1C2uMmp8Gv7mhzMHqKSKDS1OZfcLEvpIcgA+48J92HEtNanVHDIzRD9PRPCV6aS6fkZIWOVl8Q==",
"cpu": [
"x64"
],
@@ -1283,9 +2061,9 @@
}
},
"node_modules/@oxc-parser/binding-freebsd-x64": {
- "version": "0.137.0",
- "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.137.0.tgz",
- "integrity": "sha512-79h8rYGnSlKPGWo7mHr2ixO6ea7aW8B0CT965SZ8SLbNnCOH5aOYBTeVXUY6eMvEaiLyWr8Skuiugr5pDYgLGw==",
+ "version": "0.140.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.140.0.tgz",
+ "integrity": "sha512-9hSUU+HmTUyOe4JzMHxNGgLWNY7rrO+6ShicZwImNJacEAACDMIkuEQQkvXSL+WJN50jaNtLYJv8s4OcBdpyUQ==",
"cpu": [
"x64"
],
@@ -1300,9 +2078,9 @@
}
},
"node_modules/@oxc-parser/binding-linux-arm-gnueabihf": {
- "version": "0.137.0",
- "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.137.0.tgz",
- "integrity": "sha512-ASgmlSimhGyr0lksgVIo6hibz1obnDq4qJbiMX/AzltfgPnanRrzG1Q+23g8ljOHOjv6dsznkUuCYL3gg0sY1Q==",
+ "version": "0.140.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.140.0.tgz",
+ "integrity": "sha512-RAEuQsYtS0KcDFqN0ABTjyyNlokS91JeuDuoW9tEbG0JTbRNXnpQUdbYc/16JoA6Z/2ALbNrE3KmxtqDiuIjCQ==",
"cpu": [
"arm"
],
@@ -1317,9 +2095,9 @@
}
},
"node_modules/@oxc-parser/binding-linux-arm-musleabihf": {
- "version": "0.137.0",
- "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.137.0.tgz",
- "integrity": "sha512-AU2J9aa22Sx32wRGnDjybOU9TQXXQUud5sdUi+ZB0XxwM8aToWLweV+yA0wlQm0yIUVqljquqoHCYEq9II8gJQ==",
+ "version": "0.140.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.140.0.tgz",
+ "integrity": "sha512-c4CkHvPvqfojouredJ0w3e6+jiBq0SbFyhH61kr/zPb/7XsaYTNKQ54vmlSsopfdQbNDX40ZeK9Abs2Qet6wcw==",
"cpu": [
"arm"
],
@@ -1334,9 +2112,9 @@
}
},
"node_modules/@oxc-parser/binding-linux-arm64-gnu": {
- "version": "0.137.0",
- "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.137.0.tgz",
- "integrity": "sha512-GdEtiG89yMr7XkUGxifgodXEEm2f+xW2f9CpDjlgAnBOwhTmrpQMvhOGobLVKUyzf/qHBXW16smk5zbF3nZU6w==",
+ "version": "0.140.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.140.0.tgz",
+ "integrity": "sha512-yrjmLj8ixPB25yqvPGr28meGjb+keed7m1GqqY/0uqkhZIoT4t9zmfwUgFEtC33C7dtE+UQ7TU0IaVxf97SWJg==",
"cpu": [
"arm64"
],
@@ -1351,9 +2129,9 @@
}
},
"node_modules/@oxc-parser/binding-linux-arm64-musl": {
- "version": "0.137.0",
- "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.137.0.tgz",
- "integrity": "sha512-EGJ+Bs8iXx8KBH8DQ5BLoEm5lnHaYjlh4/8j8vFhrr/6z4tqONy5BZDzLpKmmNWlN6Hlc5r8YOuBVHqZ9vRFEQ==",
+ "version": "0.140.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.140.0.tgz",
+ "integrity": "sha512-ggGMQTN8Agwxp2WiLMpdY671dt0qTDJWiWlJeig3HnUwTnerRl0J2JdGVghWBeDcss2D9S2V2Js6dZHEiVabVA==",
"cpu": [
"arm64"
],
@@ -1368,9 +2146,9 @@
}
},
"node_modules/@oxc-parser/binding-linux-ppc64-gnu": {
- "version": "0.137.0",
- "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.137.0.tgz",
- "integrity": "sha512-vzFUQENy/fnbSe5DZWovq6tIBc1uhuMztanSW6rz1e9WdQE4gHwYuD7ZII6JnrJifd1R3RSoqiZbgRFlVL2tYQ==",
+ "version": "0.140.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.140.0.tgz",
+ "integrity": "sha512-IgTs8xYAFgAUGNmR65tIqjlJ8vKgrfXzC515e9goSdfMyKQV4aJpd2pUUudU4u51G64H0/DSEJEXKOraxm9ZCA==",
"cpu": [
"ppc64"
],
@@ -1385,9 +2163,9 @@
}
},
"node_modules/@oxc-parser/binding-linux-riscv64-gnu": {
- "version": "0.137.0",
- "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.137.0.tgz",
- "integrity": "sha512-SfVI14HBQs9gtLcUD5hTt5hsNbdrqSUNg9S8muN+LhVQ5nf1WwH3hAoK6B9NKgdYgWAQSXFXGiiBedQ4r/BKuw==",
+ "version": "0.140.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.140.0.tgz",
+ "integrity": "sha512-A1x+PMWZmSGaFVOx2YeNTFau8uD+QO14/vLP4GrcuvUPs3+nBkUOjy9Lus86ftHsDojjYMbvBelmKc3F7Rv08g==",
"cpu": [
"riscv64"
],
@@ -1402,9 +2180,9 @@
}
},
"node_modules/@oxc-parser/binding-linux-riscv64-musl": {
- "version": "0.137.0",
- "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.137.0.tgz",
- "integrity": "sha512-e7Ppy4FCIFNQxT/ikSeIWFoQ0l+N9vgtRBtLcyZXeolTzApyVoPqEXsYPrcdM/9i0Bwk8knvYd37vaEMxHyi6g==",
+ "version": "0.140.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.140.0.tgz",
+ "integrity": "sha512-zBqpfRo2myWPrPo5xUjeZqlnPXPXsX8BcWtWff66/eGRQdbPjhzPgXa/F+AtxT2afUViPxbuDlwscMKzQ5tg+g==",
"cpu": [
"riscv64"
],
@@ -1419,9 +2197,9 @@
}
},
"node_modules/@oxc-parser/binding-linux-s390x-gnu": {
- "version": "0.137.0",
- "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.137.0.tgz",
- "integrity": "sha512-Bho5qFwdhqsIFR7gipYEUlqvi3SRrY8sugxXig380MIaakBB1PyU9+7dBiBVScfImTNWhijUxdBwqrprGdq5WA==",
+ "version": "0.140.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.140.0.tgz",
+ "integrity": "sha512-2M1DPm/8w9I//YzFlFC9qXw+r2tJFh5CYwRlYTq2vUJQS7qoQftEDeCZ8EnN7KHtvSiXvYj8mZI5pR7DpXmcEw==",
"cpu": [
"s390x"
],
@@ -1436,9 +2214,9 @@
}
},
"node_modules/@oxc-parser/binding-linux-x64-gnu": {
- "version": "0.137.0",
- "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.137.0.tgz",
- "integrity": "sha512-36mGWtg7PyFzjJwGDkH6/F4o2nIDEoKXLPr/X/lwqklkomQwJJt1I5GJVmGhovUEmgPK5WAeAZMqlFCehwiy9Q==",
+ "version": "0.140.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.140.0.tgz",
+ "integrity": "sha512-8aRDbZ/U/jO8N7go1MO72jtbpb4uswV8d7vOkMvt/BPgZiyEYvl1VIWK4ESxZZhnJ4tqwVldgX7dNiP/eB1Jdg==",
"cpu": [
"x64"
],
@@ -1453,9 +2231,9 @@
}
},
"node_modules/@oxc-parser/binding-linux-x64-musl": {
- "version": "0.137.0",
- "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.137.0.tgz",
- "integrity": "sha512-/Jqx6+N7A44n2BdvUr7pXhVr2vFjs6WGH3unZRczwrfiH0H1zY0QwKQMG/dtRiTlKGDKGukznPT8lx84/oEsZg==",
+ "version": "0.140.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.140.0.tgz",
+ "integrity": "sha512-xRqpeI8U2sQQS1W5BMWRyMTxtagkuLG2dEWruet5lFsWHTvBth11/TpSaJatHdqVVwHN0q3uuoS9zRsGinq8hg==",
"cpu": [
"x64"
],
@@ -1470,9 +2248,9 @@
}
},
"node_modules/@oxc-parser/binding-openharmony-arm64": {
- "version": "0.137.0",
- "resolved": "https://registry.npmjs.org/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.137.0.tgz",
- "integrity": "sha512-9Uj0qHNNl+OgT1UTGwF7ixIXU6T1u2SbMidmgPy/h1h/fl2gRS6YpAxxY1gwHofcWjoTwkoMFd8xs5Vuj6GOFA==",
+ "version": "0.140.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.140.0.tgz",
+ "integrity": "sha512-GbGRe26MqAKciFRvXeHNQJ6VAHYs9R4miP89sEAncysM3n+f4lnyLWgsa9kklJNpfnxdq2yRoNYHFqwBckVimw==",
"cpu": [
"arm64"
],
@@ -1487,9 +2265,9 @@
}
},
"node_modules/@oxc-parser/binding-wasm32-wasi": {
- "version": "0.137.0",
- "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.137.0.tgz",
- "integrity": "sha512-gW2vfkytNGgMVADiuzdvOfw0mWG9za20F/1fCJsif5aBMAvWJTSbpIXbIe0XkOe0VENk+PadpQ7cZgUy2sUJcA==",
+ "version": "0.140.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.140.0.tgz",
+ "integrity": "sha512-vFiC1hqys+hkX1GnQkIoiTQJNiUm43Z0lO35ETKXTw0YtpW7+cN58YRRXFAQQ+TgpkIi3lrhcxdlnqz+Oi3ptQ==",
"cpu": [
"wasm32"
],
@@ -1497,18 +2275,18 @@
"license": "MIT",
"optional": true,
"dependencies": {
- "@emnapi/core": "1.11.1",
- "@emnapi/runtime": "1.11.1",
- "@napi-rs/wasm-runtime": "^1.1.5"
+ "@emnapi/core": "1.11.2",
+ "@emnapi/runtime": "1.11.2",
+ "@napi-rs/wasm-runtime": "^1.1.6"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@oxc-parser/binding-win32-arm64-msvc": {
- "version": "0.137.0",
- "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.137.0.tgz",
- "integrity": "sha512-x+pFANF0yL5uK/6T7lu6SlR5qid6sp//eZXKLq5iNsIE+EQg6EaS8/wsW7E91nXXjpnPhSoMOHXShSVhGRdn8w==",
+ "version": "0.140.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.140.0.tgz",
+ "integrity": "sha512-fGSQldwEYKhM+H8uLt76Op8hh5+FYaR6lvvQ1Txw3Mhn86DyQXLcI0fi1EkFlTK7F+46OCk/j0AJMzZQm6g5Xg==",
"cpu": [
"arm64"
],
@@ -1523,9 +2301,9 @@
}
},
"node_modules/@oxc-parser/binding-win32-ia32-msvc": {
- "version": "0.137.0",
- "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.137.0.tgz",
- "integrity": "sha512-sQUqym80PFi6McRsIqfJrSu2JrSClEZIXXD+/FjAFoULEKzOPsldIdFBG96xdX8aVMzCNQ9792FPx3MfkEIrFA==",
+ "version": "0.140.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.140.0.tgz",
+ "integrity": "sha512-sDS2Bai+g3ZWYwfZqmosiSuFDBcVnZ3Ta6pszzsiJoLMqsJEWKcxXXbGa7b7yXr++W2lQNPb3ZRJ8czseqL7RA==",
"cpu": [
"ia32"
],
@@ -1540,9 +2318,9 @@
}
},
"node_modules/@oxc-parser/binding-win32-x64-msvc": {
- "version": "0.137.0",
- "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.137.0.tgz",
- "integrity": "sha512-2AsevxlvNN4WKxpEn3RtqD5zbqMaXF+T7JXblsP4gVuY+vC9dXS4ED/PwfRCliFqoeisYS3Iro4DHzxr0TEvVA==",
+ "version": "0.140.0",
+ "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.140.0.tgz",
+ "integrity": "sha512-kHbE1zWyb5OQgJA6/5P4WjiuB01sYdQwtZnSSyE58FQEXDAMnyeeq4vj7KgN75i5SlBzOs8A5MrtlD3gOlDKqQ==",
"cpu": [
"x64"
],
@@ -1557,9 +2335,9 @@
}
},
"node_modules/@oxc-project/types": {
- "version": "0.137.0",
- "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.137.0.tgz",
- "integrity": "sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==",
+ "version": "0.140.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.140.0.tgz",
+ "integrity": "sha512-h5LUOzGArYemnW1NMz/DuuQhBi96J6JL2Bk8zE4kvqxB5Sg3jxmCiH4uyOWHDkiKSt5vWlG4FIwCR/DbstcNRQ==",
"dev": true,
"license": "MIT",
"funding": {
@@ -1567,9 +2345,9 @@
}
},
"node_modules/@oxc-resolver/binding-android-arm-eabi": {
- "version": "11.21.3",
- "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.21.3.tgz",
- "integrity": "sha512-eNU11A2WNizh04v3uyaJCootrHIaS0B9aHYXvAvVnPNk4xYSjMUjHnhQ6dewPN2MRYDskV85d1N0Aw0WNWhcyg==",
+ "version": "11.24.2",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.24.2.tgz",
+ "integrity": "sha512-y09e0L0SRI2OA2tUIrjBgoV3eH5hvUKXNkJqXmNo5V2WxIjyC7I7aJfRLMEVpA8yi95f90gFDvO0VMgrDw+vwA==",
"cpu": [
"arm"
],
@@ -1581,9 +2359,9 @@
]
},
"node_modules/@oxc-resolver/binding-android-arm64": {
- "version": "11.21.3",
- "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.21.3.tgz",
- "integrity": "sha512-8Q+ZjTLvn2dIcWsrmhdrEihm7q+ag/k+mkry7Z+t0QbbHaVxXQfvH9AewyVMh/WrpEKhQ3DDgx9fYbqeCpeOEw==",
+ "version": "11.24.2",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.24.2.tgz",
+ "integrity": "sha512-cl4icWaZFnLdg8m6qtnh5rBMuGbxc/ptStFHLeCNwr+2cZjkjNwQu/jYRS0CHlnPecOJMpuS5M6/BH+0J/YkEg==",
"cpu": [
"arm64"
],
@@ -1595,9 +2373,9 @@
]
},
"node_modules/@oxc-resolver/binding-darwin-arm64": {
- "version": "11.21.3",
- "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.21.3.tgz",
- "integrity": "sha512-wkh0qKZGHXVUDxFw3oA1TXnU2BDYY/r775oJflGeIr8uDPPoN2pk8gijQIzYRT6hoql/lg3+Tx/SaTn9e2/aGg==",
+ "version": "11.24.2",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.24.2.tgz",
+ "integrity": "sha512-At29QEMF6HajbQvgY8K6OXnHD1x9rad74xBEfmCB6ZqCGsdq75aK7tOYcTbOanMy8qdIBrfL3SMr3p/lfSlb9w==",
"cpu": [
"arm64"
],
@@ -1609,9 +2387,9 @@
]
},
"node_modules/@oxc-resolver/binding-darwin-x64": {
- "version": "11.21.3",
- "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.21.3.tgz",
- "integrity": "sha512-HbNc23FAQYbuyDV2vBWMez4u4mrsm5RAkniGZAWqr6lYZ3N4beeqIb776jzwRl8qL2zRhHVXpUj97X0QgogVzg==",
+ "version": "11.24.2",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.24.2.tgz",
+ "integrity": "sha512-A5Kqr1EUj4oIL5CF4WRssq/o5P0Y11cwoFouMRmQ7YnC/A8V93nv1nb7aSU8HwcgmXropjLNkVTl4MN87cu28Q==",
"cpu": [
"x64"
],
@@ -1623,9 +2401,9 @@
]
},
"node_modules/@oxc-resolver/binding-freebsd-x64": {
- "version": "11.21.3",
- "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.21.3.tgz",
- "integrity": "sha512-K6xNsTUPEUdfrn0+kbMq5nOUB5w1C5pavPQngt4TM2FpN91lP0PBe2srSpamb4d69O7h86oAi/qWX/kZNRSjkw==",
+ "version": "11.24.2",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.24.2.tgz",
+ "integrity": "sha512-R5xkRBRRz7ceH/P5Jrc6G7FmdUdgpLYyESFAUDVTNQ9K0sGPxcp4ljiwEwEqsvNcQ4sYbMRrWcHHBCu7ksAJVw==",
"cpu": [
"x64"
],
@@ -1637,9 +2415,9 @@
]
},
"node_modules/@oxc-resolver/binding-linux-arm-gnueabihf": {
- "version": "11.21.3",
- "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.21.3.tgz",
- "integrity": "sha512-VcFmOpcpWX1zoEy8M58tR2M9YxM+Z9RuQhqAx5q0CTmrruaP7Gveejg75hzd/5sg5nk9G3aLALEa3hE2FsmmTQ==",
+ "version": "11.24.2",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.24.2.tgz",
+ "integrity": "sha512-k/RuYL4L/R58IBn3wT5ma3Wh4k62bp1eYCFRWCmMsasUOqL+H6sW0VGFadEzKWXFFlz+2uIMoeMk9ySSZJHgbg==",
"cpu": [
"arm"
],
@@ -1651,9 +2429,9 @@
]
},
"node_modules/@oxc-resolver/binding-linux-arm-musleabihf": {
- "version": "11.21.3",
- "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.21.3.tgz",
- "integrity": "sha512-quVoxFLBy43hWaQbbDtQNRwAX5vX76mv7n64icAtQcJ3eNgVeblqmkupF/hAneNthdqSlnd1sTjb3aQSaDPaCQ==",
+ "version": "11.24.2",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.24.2.tgz",
+ "integrity": "sha512-bnHAak3ujYfH5pKk4NieFNbvYvernfoQDgwLddbZ3OtMYrem87/qjlA+u+aKG0oZcqSLGCful/6/CEA+aeAgaA==",
"cpu": [
"arm"
],
@@ -1665,9 +2443,9 @@
]
},
"node_modules/@oxc-resolver/binding-linux-arm64-gnu": {
- "version": "11.21.3",
- "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.21.3.tgz",
- "integrity": "sha512-X0AqNZgcD07Q4V3RDK18/vYOj/HQT/FnmEFGYS2jTWqY7JO13ryE3TEs3eAIgUJhBnNkpEaiXqz3VK8M7qQhWQ==",
+ "version": "11.24.2",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.24.2.tgz",
+ "integrity": "sha512-vDT3KHgzYp47gmtNOqL2VNhCyl5Zv643eyxm//A68J8DeUGXrvD1pZFiaT4jSfe+RInfnn1R2yVHye4enx6RnA==",
"cpu": [
"arm64"
],
@@ -1679,9 +2457,9 @@
]
},
"node_modules/@oxc-resolver/binding-linux-arm64-musl": {
- "version": "11.21.3",
- "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.21.3.tgz",
- "integrity": "sha512-YkaQnaKYdbuaXvRt5Qd0GpbihzVnyfR6z1SpYfIUC6RTu4NF7lDKPjVkYb+jRI2gedVO2rVpN35Y6akG6ud4Lw==",
+ "version": "11.24.2",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.24.2.tgz",
+ "integrity": "sha512-+kMlQvbzfyEYtu5FcjE4p+ttBLpKW4d/AsAsuE69BxV6V4twZJeIQZFfD8gh/wqglY0MkPSezWXQH0jBV13MUw==",
"cpu": [
"arm64"
],
@@ -1693,9 +2471,9 @@
]
},
"node_modules/@oxc-resolver/binding-linux-ppc64-gnu": {
- "version": "11.21.3",
- "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.21.3.tgz",
- "integrity": "sha512-gB9HwhrPiFqUzDeEq+y/CgAijz1YdI6BnXz5GaH2Pa9cWdutchlkGFAiAuGb/PjVQpiK6NFKzFuztxrweoit7A==",
+ "version": "11.24.2",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.24.2.tgz",
+ "integrity": "sha512-shjfMhmZ3gq9fv/w7bi3PnZlgOPG+2QAOFf0BJF0EgBSIGZ6PMLN2zbGEblTUYB/NKVDRyYhE2ff3dJ1QqNPkA==",
"cpu": [
"ppc64"
],
@@ -1707,9 +2485,9 @@
]
},
"node_modules/@oxc-resolver/binding-linux-riscv64-gnu": {
- "version": "11.21.3",
- "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.21.3.tgz",
- "integrity": "sha512-zjDWBlYk8QGv0H8dsPUWqkfjYIIjG2TvspGkzXL0eImbgxtZorA/klKeHyolevoT3Kvbi+1iMr9Lhrh7jf54Og==",
+ "version": "11.24.2",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.24.2.tgz",
+ "integrity": "sha512-zGelwFR5oRo+b69k8Lrzun86DyUHzfKN6cnjbR9l7Z7NIRznOE/2ZvPa1IUKqAL2PzAXOdwkfVqNvO1H2RlpAw==",
"cpu": [
"riscv64"
],
@@ -1721,9 +2499,9 @@
]
},
"node_modules/@oxc-resolver/binding-linux-riscv64-musl": {
- "version": "11.21.3",
- "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.21.3.tgz",
- "integrity": "sha512-4UfsQvacV388y1zpXL7C1x1FNYaV52JtuNRiuzrfQA2z1z6ElVrsidkGsrvQ5EgeSq1Pj7kaKqrgGkvFuxJ/tw==",
+ "version": "11.24.2",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.24.2.tgz",
+ "integrity": "sha512-qxZ1SWCXJY0eyhAlP6Lmo9F2Nrtx7EkYj9oCgL8apDPCwXwCEDA2U697bbT81JIc2IrVjxO4KX6WU2N+oN9Z4w==",
"cpu": [
"riscv64"
],
@@ -1735,9 +2513,9 @@
]
},
"node_modules/@oxc-resolver/binding-linux-s390x-gnu": {
- "version": "11.21.3",
- "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.21.3.tgz",
- "integrity": "sha512-b5uH+HKH0MP5mNBYaK75SKsJbw52URqrx2LavYdq6wb0l3ExAG5niYRP9DWUNHdKilpaBVM2bXk9HNWrH3ew7Q==",
+ "version": "11.24.2",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.24.2.tgz",
+ "integrity": "sha512-sGCecF3cx2DFlH4t/z7ApnOnXqN48p5p5mlHDEnHTAukQa2P+qMVE4CwyWE9W+q/m3QJ7kKfGrIjax31f44oFQ==",
"cpu": [
"s390x"
],
@@ -1749,9 +2527,9 @@
]
},
"node_modules/@oxc-resolver/binding-linux-x64-gnu": {
- "version": "11.21.3",
- "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.21.3.tgz",
- "integrity": "sha512-PjYlmilBpNRh2ntXNYAK3Am5w/nPfEpnU/96iNx7CI8EzAn12J4JRiec63wHJTH31nLoCNxBg/829pN+3CfG3Q==",
+ "version": "11.24.2",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.24.2.tgz",
+ "integrity": "sha512-k/VlMMcSzMlahb3/fENM4rTlsJ0s3fFROA0KXPBmKggqmTSaE383sl8F3KCOXPLmVsYfW6hCitMhXCEtNeZxxg==",
"cpu": [
"x64"
],
@@ -1763,9 +2541,9 @@
]
},
"node_modules/@oxc-resolver/binding-linux-x64-musl": {
- "version": "11.21.3",
- "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.21.3.tgz",
- "integrity": "sha512-QTBAb7JuHlZ7JUEyM8UiQi2f7m/L4swBhP2TNpYIDc9Wp/wRw1G/8sl6i13aIzQAXH7LKIm294LeOHd0lQR8zA==",
+ "version": "11.24.2",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.24.2.tgz",
+ "integrity": "sha512-8hbnZyNi97b/8wapYaIF9+t9GmZKBW2vunaOc3h9HGJptH7b7XpvZqOTBSm/MpTjr7H497BlgOaSfLUdhmy2bw==",
"cpu": [
"x64"
],
@@ -1777,9 +2555,9 @@
]
},
"node_modules/@oxc-resolver/binding-openharmony-arm64": {
- "version": "11.21.3",
- "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-openharmony-arm64/-/binding-openharmony-arm64-11.21.3.tgz",
- "integrity": "sha512-4j1DFwjwv36ec9kds0jU/ucQ5Ha4ERO/H95BxR5JFf0kqUUAJ1kwII7XhTc1vZrkdJkvLGC9Q2MbpObpum8RBg==",
+ "version": "11.24.2",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-openharmony-arm64/-/binding-openharmony-arm64-11.24.2.tgz",
+ "integrity": "sha512-MvyGik3a6pVgZ0t/kWlbmFxFLmXQJwgLsY2eYFHLpy0wGwRbfzeIGgDwQ3kXqE30z+kSXennRkCrT7TUvkptNg==",
"cpu": [
"arm64"
],
@@ -1791,9 +2569,9 @@
]
},
"node_modules/@oxc-resolver/binding-wasm32-wasi": {
- "version": "11.21.3",
- "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.21.3.tgz",
- "integrity": "sha512-i8oluoel5kru/j1WNrjmQSiA3GQ7wvIYVR1IwIoZtKogAhya2iub+ZKIeSIkcJOrnzQ18Tzl/F+kL3fYOxZLvA==",
+ "version": "11.24.2",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.24.2.tgz",
+ "integrity": "sha512-vHcssMPwO08RTvj/c0iOBz90attxyG3wQJ0dTcyEQK43LRpcdLWZlV5feBhv6Isn6ahbQIzHbCgfa81+RiML0Q==",
"cpu": [
"wasm32"
],
@@ -1801,55 +2579,195 @@
"license": "MIT",
"optional": true,
"dependencies": {
- "@emnapi/core": "1.11.0",
- "@emnapi/runtime": "1.11.0",
- "@napi-rs/wasm-runtime": "^1.1.5"
+ "@emnapi/core": "1.11.2",
+ "@emnapi/runtime": "1.11.2",
+ "@napi-rs/wasm-runtime": "^1.1.6"
},
"engines": {
"node": ">=14.0.0"
}
},
- "node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@emnapi/core": {
- "version": "1.11.0",
- "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.0.tgz",
- "integrity": "sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q==",
+ "node_modules/@oxc-resolver/binding-win32-arm64-msvc": {
+ "version": "11.24.2",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.24.2.tgz",
+ "integrity": "sha512-uokJqro2iBqkFvJdKQLP7d8/BUmFwESQFVmIJUQKj1Xn1a/LysJoe1vmeECLF5b3jsV8CAL5sEMJXX6SdK9Nhg==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
"optional": true,
- "dependencies": {
- "@emnapi/wasi-threads": "1.2.2",
- "tslib": "^2.4.0"
- }
+ "os": [
+ "win32"
+ ]
},
- "node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@emnapi/runtime": {
- "version": "1.11.0",
- "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.0.tgz",
- "integrity": "sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg==",
+ "node_modules/@oxc-resolver/binding-win32-x64-msvc": {
+ "version": "11.24.2",
+ "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.24.2.tgz",
+ "integrity": "sha512-UqGPmo56KDfLlfXFAFIrNflHT8tFxWGEivWg3Zeyp4Uy2NlKN1FGPr6/BxcLGG3+kZ6Wp14g5Uj+n71boqZfiw==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
"optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@pact-foundation/pact": {
+ "version": "17.0.1",
+ "resolved": "https://registry.npmjs.org/@pact-foundation/pact/-/pact-17.0.1.tgz",
+ "integrity": "sha512-Gnzy9fMK0Wu6UB8u0fYcso5fiEZk+Tu2lG9zR5DoJiwWBanb/9/pOmRrBWtOHn5EdgvEvzIF1huB39sZIc27Rw==",
+ "dev": true,
+ "license": "MIT",
"dependencies": {
- "tslib": "^2.4.0"
+ "@pact-foundation/pact-core": "^19.2.0",
+ "@scarf/scarf": "^1.4.0",
+ "axios": "^1.12.2",
+ "body-parser": "^2.2.0",
+ "chalk": "4.1.2",
+ "express": "^5.1.0",
+ "graphql": "^16.11.0",
+ "graphql-tag": "^2.12.6",
+ "http-proxy": "^1.18.1",
+ "https-proxy-agent": "^7.0.6",
+ "js-base64": "^3.7.8",
+ "lodash": "^4.17.21",
+ "ramda": "^0.32.0",
+ "randexp": "^0.5.3",
+ "router": "^2.2.0",
+ "stack-utils": "^2.0.6"
+ },
+ "engines": {
+ "node": ">=22"
}
},
- "node_modules/@oxc-resolver/binding-win32-arm64-msvc": {
- "version": "11.21.3",
- "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.21.3.tgz",
- "integrity": "sha512-M/8dw8dD6aOs+NlPJax401CZB9I7Aut84isQLgALGGwke4Afvw+/7yYhZb94yXf6t2sPLhQLmSmtSV+2FhsOWg==",
+ "node_modules/@pact-foundation/pact-core": {
+ "version": "19.2.0",
+ "resolved": "https://registry.npmjs.org/@pact-foundation/pact-core/-/pact-core-19.2.0.tgz",
+ "integrity": "sha512-7EB2e850hmBzGnwOYTRj4TCFCJQa9zaOy53bK+ybzEDx801olf0gVlYqMYHt1EsHUZzmtS5uDybpr9UMcZQxgg==",
"cpu": [
+ "x64",
+ "ia32",
"arm64"
],
"dev": true,
"license": "MIT",
- "optional": true,
"os": [
+ "darwin",
+ "linux",
"win32"
+ ],
+ "dependencies": {
+ "check-types": "11.2.3",
+ "detect-libc": "^2.0.3",
+ "node-gyp-build": "^4.6.0",
+ "pino": "^10.0.0",
+ "pino-pretty": "^13.1.1",
+ "underscore": "1.13.8"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "optionalDependencies": {
+ "@pact-foundation/pact-core-darwin-arm64": "19.2.0",
+ "@pact-foundation/pact-core-darwin-x64": "19.2.0",
+ "@pact-foundation/pact-core-linux-arm64-glibc": "19.2.0",
+ "@pact-foundation/pact-core-linux-arm64-musl": "19.2.0",
+ "@pact-foundation/pact-core-linux-x64-glibc": "19.2.0",
+ "@pact-foundation/pact-core-linux-x64-musl": "19.2.0",
+ "@pact-foundation/pact-core-windows-x64": "19.2.0"
+ }
+ },
+ "node_modules/@pact-foundation/pact-core-darwin-arm64": {
+ "version": "19.2.0",
+ "resolved": "https://registry.npmjs.org/@pact-foundation/pact-core-darwin-arm64/-/pact-core-darwin-arm64-19.2.0.tgz",
+ "integrity": "sha512-cQvvWfJKS7iMzkunzh/kdgmyVLhAxyr/BQ8fOnMco2M/1+GHR+sOXvhrR1tes+NAYd1xjE3fbg1K2Flno8aDBw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
]
},
- "node_modules/@oxc-resolver/binding-win32-x64-msvc": {
- "version": "11.21.3",
- "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.21.3.tgz",
- "integrity": "sha512-H7BCt/VnS9hnmMp42eGhZ99izSCRvlnWwy/N71K1/J8QoExwY4262Z8QiEkMDtduRJrztayDxETTckmUuAVL9Q==",
+ "node_modules/@pact-foundation/pact-core-darwin-x64": {
+ "version": "19.2.0",
+ "resolved": "https://registry.npmjs.org/@pact-foundation/pact-core-darwin-x64/-/pact-core-darwin-x64-19.2.0.tgz",
+ "integrity": "sha512-UltPXDZ+h1r3JqgIpEBP16bQzB90otd1QbcoeM3XakF9khCrYhW4y9llSuaosyqPFYwCk+mLaZTl/4iitJJaow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@pact-foundation/pact-core-linux-arm64-glibc": {
+ "version": "19.2.0",
+ "resolved": "https://registry.npmjs.org/@pact-foundation/pact-core-linux-arm64-glibc/-/pact-core-linux-arm64-glibc-19.2.0.tgz",
+ "integrity": "sha512-amiv4COurH1FRa7DSH3ks4UPQCb5J3x4GKrArf8UsTOkNhVGFUWVl83LOELj9Gtk67GwJ96iF7/fQps8I/iFIA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@pact-foundation/pact-core-linux-arm64-musl": {
+ "version": "19.2.0",
+ "resolved": "https://registry.npmjs.org/@pact-foundation/pact-core-linux-arm64-musl/-/pact-core-linux-arm64-musl-19.2.0.tgz",
+ "integrity": "sha512-tGWF7dP72Ho1A1PApyUqUMRI/e+gfbPxfaXxwWMXNh8JLK2jQQnP4bWymGwHQ4vxL7wn8ayx8I5y6x1vk/2+rA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@pact-foundation/pact-core-linux-x64-glibc": {
+ "version": "19.2.0",
+ "resolved": "https://registry.npmjs.org/@pact-foundation/pact-core-linux-x64-glibc/-/pact-core-linux-x64-glibc-19.2.0.tgz",
+ "integrity": "sha512-/ZNuWKuFJSBRZH09t7FXqKLy5koCaMIZ4RdbockLYlNo8uWF2ALfRCllj+SbXPLK+T+lVnBrs5s94X2cu4Rc7A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@pact-foundation/pact-core-linux-x64-musl": {
+ "version": "19.2.0",
+ "resolved": "https://registry.npmjs.org/@pact-foundation/pact-core-linux-x64-musl/-/pact-core-linux-x64-musl-19.2.0.tgz",
+ "integrity": "sha512-n0m39CzVkq8J2alir1redm0rAdUVQzkKJqYBQdRmhEIa+QeGjUrJq+gmakUysoJxfSY4UthnJ9jS8SoyiZ+k6A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@pact-foundation/pact-core-windows-x64": {
+ "version": "19.2.0",
+ "resolved": "https://registry.npmjs.org/@pact-foundation/pact-core-windows-x64/-/pact-core-windows-x64-19.2.0.tgz",
+ "integrity": "sha512-WZSDBL1Sn8y5+YJTK+HDiS/Fn9th1M4QJW2dflQ8UFwoaIh6yb0TrJjtuWbDGIwNeD4Sv8CJ5jJOf1nyNjFLfg==",
"cpu": [
"x64"
],
@@ -1860,6 +2778,48 @@
"win32"
]
},
+ "node_modules/@pact-foundation/pact/node_modules/agent-base": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/@pact-foundation/pact/node_modules/https-proxy-agent": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/@paulirish/trace_engine": {
+ "version": "0.0.65",
+ "resolved": "https://registry.npmjs.org/@paulirish/trace_engine/-/trace_engine-0.0.65.tgz",
+ "integrity": "sha512-Qsm6F5C8xf6ZzQXbQc2+wcpe6sggfs/gvc/ytqSurdvYg3kyW0ECHCqE0CWBKZpqgjVfPNX9c7SCS3r2nEIRGg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "legacy-javascript": "latest",
+ "third-party-web": "latest"
+ }
+ },
+ "node_modules/@pinojs/redact": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz",
+ "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@playwright/test": {
"version": "1.62.0",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.62.0.tgz",
@@ -1876,57 +2836,249 @@
"node": ">=20"
}
},
- "node_modules/@rc-component/async-validator": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-6.0.0.tgz",
- "integrity": "sha512-D3AGQwdyE58gmvx6waVSXJ80JGO+IY5L2O8HDnSOex7JNlzB3GuN/4hyHNTdhy2qtOhkpbIjmeAN3tL993wKbA==",
- "license": "MIT",
+ "node_modules/@puppeteer/browsers": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-3.0.6.tgz",
+ "integrity": "sha512-B/gKoqlFkzhvzsI6jo9K1cZz9o5ypviVv/xu8CwA4grZzyVwN+XfkT+tu8T1zrauuEXv6VhS2oGX+6NL95WcKA==",
+ "dev": true,
+ "license": "Apache-2.0",
"dependencies": {
- "@babel/runtime": "^7.24.4"
+ "modern-tar": "^0.7.6",
+ "yargs": "^18.0.0"
+ },
+ "bin": {
+ "browsers": "lib/main-cli.js"
},
"engines": {
- "node": ">=14.x"
+ "node": ">=22.12.0"
+ },
+ "peerDependencies": {
+ "proxy-agent": ">=8.0.1",
+ "yauzl": "^2.10.0 || ^3.4.0"
+ },
+ "peerDependenciesMeta": {
+ "proxy-agent": {
+ "optional": true
+ },
+ "yauzl": {
+ "optional": true
+ }
}
},
- "node_modules/@rc-component/cascader": {
- "version": "1.17.0",
- "resolved": "https://registry.npmjs.org/@rc-component/cascader/-/cascader-1.17.0.tgz",
- "integrity": "sha512-3cVNG0zrQF1PoXq262L3wGCU+/YLEC1mGSVHDl577dQmA0ZKkXFbY6nwyXo+beCcM7buo49t24jkr+QZdL7O8w==",
+ "node_modules/@puppeteer/browsers/node_modules/ansi-regex": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "dev": true,
"license": "MIT",
- "dependencies": {
- "@rc-component/select": "~1.8.0",
- "@rc-component/tree": "~1.3.2",
- "@rc-component/util": "^1.11.1",
- "clsx": "^2.1.1"
+ "engines": {
+ "node": ">=12"
},
- "peerDependencies": {
- "react": ">=18.0.0",
- "react-dom": ">=18.0.0"
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
- "node_modules/@rc-component/checkbox": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/@rc-component/checkbox/-/checkbox-2.0.0.tgz",
- "integrity": "sha512-3CXGPpAR9gsPKeO2N78HAPOzU30UdemD6HGJoWVJOpa6WleaGB5kzZj3v6bdTZab31YuWgY/RxV3VKPctn0DwQ==",
+ "node_modules/@puppeteer/browsers/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/@puppeteer/browsers/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": {
- "@rc-component/util": "^1.3.0",
- "clsx": "^2.1.1"
+ "string-width": "^7.2.0",
+ "strip-ansi": "^7.1.0",
+ "wrap-ansi": "^9.0.0"
},
- "peerDependencies": {
- "react": ">=16.9.0",
- "react-dom": ">=16.9.0"
+ "engines": {
+ "node": ">=20"
}
},
- "node_modules/@rc-component/collapse": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@rc-component/collapse/-/collapse-1.2.0.tgz",
- "integrity": "sha512-ZRYSKSS39qsFx93p26bde7JUZJshsUBEQRlRXPuJYlAiNX0vyYlF5TsAm8JZN3LcF8XvKikdzPbgAtXSbkLUkw==",
+ "node_modules/@puppeteer/browsers/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": {
- "@babel/runtime": "^7.10.1",
- "@rc-component/motion": "^1.1.4",
- "@rc-component/util": "^1.3.0",
+ "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/@puppeteer/browsers/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/@puppeteer/browsers/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/@puppeteer/browsers/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/@puppeteer/browsers/node_modules/wrap-ansi": {
+ "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": "^6.2.1",
+ "string-width": "^7.0.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/@puppeteer/browsers/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": {
+ "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/@puppeteer/browsers/node_modules/yargs": {
+ "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": "^9.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "string-width": "^8.2.1",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^22.0.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=23"
+ }
+ },
+ "node_modules/@puppeteer/browsers/node_modules/yargs-parser": {
+ "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": "^20.19.0 || ^22.12.0 || >=23"
+ }
+ },
+ "node_modules/@rc-component/async-validator": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-6.0.0.tgz",
+ "integrity": "sha512-D3AGQwdyE58gmvx6waVSXJ80JGO+IY5L2O8HDnSOex7JNlzB3GuN/4hyHNTdhy2qtOhkpbIjmeAN3tL993wKbA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.24.4"
+ },
+ "engines": {
+ "node": ">=14.x"
+ }
+ },
+ "node_modules/@rc-component/cascader": {
+ "version": "1.17.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/cascader/-/cascader-1.17.0.tgz",
+ "integrity": "sha512-3cVNG0zrQF1PoXq262L3wGCU+/YLEC1mGSVHDl577dQmA0ZKkXFbY6nwyXo+beCcM7buo49t24jkr+QZdL7O8w==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/select": "~1.8.0",
+ "@rc-component/tree": "~1.3.2",
+ "@rc-component/util": "^1.11.1",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=18.0.0",
+ "react-dom": ">=18.0.0"
+ }
+ },
+ "node_modules/@rc-component/checkbox": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/checkbox/-/checkbox-2.0.0.tgz",
+ "integrity": "sha512-3CXGPpAR9gsPKeO2N78HAPOzU30UdemD6HGJoWVJOpa6WleaGB5kzZj3v6bdTZab31YuWgY/RxV3VKPctn0DwQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/util": "^1.3.0",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/collapse": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/collapse/-/collapse-1.2.0.tgz",
+ "integrity": "sha512-ZRYSKSS39qsFx93p26bde7JUZJshsUBEQRlRXPuJYlAiNX0vyYlF5TsAm8JZN3LcF8XvKikdzPbgAtXSbkLUkw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "@rc-component/motion": "^1.1.4",
+ "@rc-component/util": "^1.3.0",
"clsx": "^2.1.1"
},
"peerDependencies": {
@@ -1995,13 +3147,13 @@
}
},
"node_modules/@rc-component/dropdown": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@rc-component/dropdown/-/dropdown-1.0.2.tgz",
- "integrity": "sha512-6PY2ecUSYhDPhkNHHb4wfeAya04WhpmUSKzdR60G+kMNVUCX2vjT/AgTS0Lz0I/K6xrPMJ3enQbwVpeN3sHCgg==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rc-component/dropdown/-/dropdown-1.0.3.tgz",
+ "integrity": "sha512-YTST/N6kpqpDz3IMuM/PSSZnrDpSOA6dgHv12gPA90ZTSLv2CoqkZ0+9NtwTY6BeO7dstPblSic2QJg7dSFy/g==",
"license": "MIT",
"dependencies": {
"@rc-component/trigger": "^3.0.0",
- "@rc-component/util": "^1.2.1",
+ "@rc-component/util": "^1.11.1",
"clsx": "^2.1.1"
},
"peerDependencies": {
@@ -2397,9 +3549,9 @@
}
},
"node_modules/@rc-component/table": {
- "version": "1.10.2",
- "resolved": "https://registry.npmjs.org/@rc-component/table/-/table-1.10.2.tgz",
- "integrity": "sha512-b3PjqB9Gp25p5t/zq+9QrbXbodkptT8/zvLmwgd2FNPUUtaYyDnQqfxeD5a7ao8E8lpinLHsi2u2vdfPhyNvAw==",
+ "version": "1.10.4",
+ "resolved": "https://registry.npmjs.org/@rc-component/table/-/table-1.10.4.tgz",
+ "integrity": "sha512-HwoTnrwc29zeoXkXGhWqzJh8FIibGUxi1jM4LtoSzmR9d5Vv5osUQpZxnXKBP8iOCvyD6BQzZm1nXJRcnrxpAg==",
"license": "MIT",
"dependencies": {
"@rc-component/context": "^2.0.1",
@@ -2507,15 +3659,15 @@
}
},
"node_modules/@rc-component/trigger": {
- "version": "3.9.1",
- "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-3.9.1.tgz",
- "integrity": "sha512-LNsYvz60mrLJ/kRvKcHE7boUvcQfVMCfRqZ71x3Fo9AOiZ1KKIEqkzMA8DNvz2V3Bcvir/vwQNn7JF1NPODQ7Q==",
+ "version": "3.10.1",
+ "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-3.10.1.tgz",
+ "integrity": "sha512-mXlDN0IXdtV8Yqqm8195ECCyrbmfvvfKvwVvSlH0+qvKD6BUF8gRhEjSy0FOcD1+CcDRHgTiX99LoxfQrmh3Cw==",
"license": "MIT",
"dependencies": {
- "@rc-component/motion": "^1.1.4",
- "@rc-component/portal": "^2.2.0",
- "@rc-component/resize-observer": "^1.1.1",
- "@rc-component/util": "^1.2.1",
+ "@rc-component/motion": "^1.3.3",
+ "@rc-component/portal": "^2.2.1",
+ "@rc-component/resize-observer": "^1.1.2",
+ "@rc-component/util": "^1.11.1",
"clsx": "^2.1.1"
},
"engines": {
@@ -2541,13 +3693,13 @@
}
},
"node_modules/@rc-component/util": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@rc-component/util/-/util-1.11.1.tgz",
- "integrity": "sha512-awVlI3ub2vqfqkYxOBc/uQ0efm3jw0wcrhtO/YWLyZfxiKXczKwNbVuhlnyxytDt7H9pbbVQiqr+O6MLATtRYg==",
+ "version": "1.12.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/util/-/util-1.12.0.tgz",
+ "integrity": "sha512-AEjPL8JVdohIITaiXokyjL9WQ6tKWWjAYK9QU16tGNE9JaQABBQy+hA4H2Lup5MgXy9yY3iLrbZJheuU13hTdQ==",
"license": "MIT",
"dependencies": {
"is-mobile": "^5.0.0",
- "react-is": "^18.2.0"
+ "react-is": "^19.2.7"
},
"peerDependencies": {
"react": ">=18.0.0",
@@ -2574,9 +3726,9 @@
}
},
"node_modules/@rolldown/binding-android-arm64": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.3.tgz",
- "integrity": "sha512-DT6Z3PhvioeHMvxo+xHc3KtqggrI7CCTXCmC2h/5zUlp5jVitv7XEy+9q5/7v8IolhlioawpMo8Kg0EEBy7J0g==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.1.tgz",
+ "integrity": "sha512-02hOeOSryYxVrOIphmLAsqnCJWxwlzFk+pEt/N/i6OgT3lShHO7xGCU5cpgchRDHboAEbSjzgGh+O/u1GswQmA==",
"cpu": [
"arm64"
],
@@ -2591,9 +3743,9 @@
}
},
"node_modules/@rolldown/binding-darwin-arm64": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.3.tgz",
- "integrity": "sha512-0NwgwsjM7LrsuVnXMK3koTpagBNOhloc/BNjKqZjv4V5zI5r13qx69uVhRx+o5Z0yy4Hzq+lpy7TAgUG/ocvrw==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.1.tgz",
+ "integrity": "sha512-fMsTOnN0OjFm3CyppWPitKnc8UlliVARUULW6cfU6AIqjdtgmSFWSk9vecHzZduv/yMWIHDlRhM1e8Iff9uAfA==",
"cpu": [
"arm64"
],
@@ -2608,9 +3760,9 @@
}
},
"node_modules/@rolldown/binding-darwin-x64": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.3.tgz",
- "integrity": "sha512-YtiBp4disu6V560loT6PjMdiRaWmVvDNrUunAalbiFx2ggeJwxdAsgZMcoGP17uyAsTwAj5V1niksxlHnVQ1Sw==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.1.tgz",
+ "integrity": "sha512-1wjKdz/XLGKHaTNHjQveQ/B23TKx4ItAqm1JbyVuvNPc4Ze0Fb48s49TAd/2zcplPl8okE/UbTgmlVfwT7eFeQ==",
"cpu": [
"x64"
],
@@ -2625,9 +3777,9 @@
}
},
"node_modules/@rolldown/binding-freebsd-x64": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.3.tgz",
- "integrity": "sha512-yD3EkEdXk2LypPxnf/kSZHirarsI8gcPzc62SukhR9VJTyvV+F9Q/GxWNuCojc7sXyuVC4DxRGhdDK4X8VSsbw==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.1.tgz",
+ "integrity": "sha512-Fa0jHR07E7YBN4vOEsbVf2briYNsuOowfLJaXULZM0ldMlaCaj2LJgLMbMe4iacRyZmvR8efFhgR9wKuGclQUg==",
"cpu": [
"x64"
],
@@ -2642,9 +3794,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.3.tgz",
- "integrity": "sha512-c+8vieQbsD7HNAHKIA34w0GJ9FedFFuJGD+7E6vz7Q3uqAIugL5p45fhlsj4UaAsHpcmlqugBWMhA0/j7o0sIg==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.1.tgz",
+ "integrity": "sha512-pzkgu1SSHGgRRyRZ4fbmSgmajbVt+epaLP99NDjFft69v/ypfTi6swBMiVdh2EkQ0OSnHE1lZDM7DRGkyAzUpA==",
"cpu": [
"arm"
],
@@ -2659,9 +3811,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-gnu": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.3.tgz",
- "integrity": "sha512-50jD0uUwLvur7Zz9LHz17kaAdTPjn5wN93hEgjvmYFRZwiR7ZJYovTd5ipyWJDAnXKvZ+wgc+/Ika6dwSF5OcA==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.1.tgz",
+ "integrity": "sha512-QI5SEDY8cbiYWHx0VO4vIc3UlS6a32vXHjU8Qy/17adEmZIPuByJg13UEvo9c/UCiUkdcVWY83C+b+JrwnNyUg==",
"cpu": [
"arm64"
],
@@ -2676,9 +3828,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-musl": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.3.tgz",
- "integrity": "sha512-BO9+oPL8K9poZJBfYPsXNtYjPE5uM3qeehT3aFcW4LITOl+iSqhp0abzjR2nWBUNjIZeKXjAEWBZ64WjNoHd6w==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.1.tgz",
+ "integrity": "sha512-Sm41FyCeXqmYcERoYOCbGIL5hNfd8w9LQ7Y61Bev48HkcjaJqV/iiVOaiDxjVTRMS+QKrZmD8cfPt4uMVnvM+A==",
"cpu": [
"arm64"
],
@@ -2693,9 +3845,9 @@
}
},
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.3.tgz",
- "integrity": "sha512-f3VpLB1vQ0Eo6ecr/6cekLnvYMFF4YBFoVGkfkvPLq1bAkbAwHYQPZKoAmG6OJyTcxxoC+AvezGx/S1obNC0Mw==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.1.tgz",
+ "integrity": "sha512-2x+WhXTGl9yJYPbltW/BSEPTVz9OIWQyER4N+gJEDWkkn904eRcBzELqh/Hf7K0w/ubGbKNMv0ZC+94QK/IFEg==",
"cpu": [
"ppc64"
],
@@ -2710,9 +3862,9 @@
}
},
"node_modules/@rolldown/binding-linux-s390x-gnu": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.3.tgz",
- "integrity": "sha512-AmurZ26Pqx/RI9N1gzEOCklkKXl927yjfXWUUS0O7Puh8ARM/Ob8qfrD3qnWksScdw6cSrW5PSHE9DyLu7+PtA==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.1.tgz",
+ "integrity": "sha512-eEjmQpuRQayHPWWnywaWHkFT3ToPbP3RYy42VVd/B9aBGDA+Ol25EIWHxKQST3IiWJjikCWUF7KtbfqwZrzVwQ==",
"cpu": [
"s390x"
],
@@ -2727,9 +3879,9 @@
}
},
"node_modules/@rolldown/binding-linux-x64-gnu": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.3.tgz",
- "integrity": "sha512-JJpqs8bRGITDOdbkNKnlojzBabbOHrqjSvDr0IVsZObE1lBcPjxItUEY9eWIDbxaJ3cGrXPWGfGkIxFijg/URg==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.1.tgz",
+ "integrity": "sha512-/Orga1fZYkLc/56jBICcHrKchl8Z2UKdDSr3LG9ToWO1lQ6a4Livk9Xz+9WN91zsz5QR3XQz2NNoSDEvP6qadw==",
"cpu": [
"x64"
],
@@ -2744,9 +3896,9 @@
}
},
"node_modules/@rolldown/binding-linux-x64-musl": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.3.tgz",
- "integrity": "sha512-rSJcdjPxzA/by/6/rYs+v+bXU7UjvnbUWz8MJb6kh6+knqB1dCrtHg0uu7C/4haqJvqdkYHQ5IGn+tCH9GLW/g==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.1.tgz",
+ "integrity": "sha512-xxBJRL+0q0Kce7orznGWLuylHDY65vuARXZRpX+hPdv+DqK2c3NlCsVA98tlWzWNEE7yPqA/1NQ5nnCrj49Y5A==",
"cpu": [
"x64"
],
@@ -2761,9 +3913,9 @@
}
},
"node_modules/@rolldown/binding-openharmony-arm64": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.3.tgz",
- "integrity": "sha512-hQ3/PYkDJICgevvyNcVrihVeqq7k1Pp3VZ9lY+dauAYUJKO+auqApvANhvR1An9BhmqYKvW2Mu1F9u4DXSMLxQ==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.1.tgz",
+ "integrity": "sha512-M6AdXIXw3s+/8XpKMzdGDEXGS1S7kwUsy+rcTIUIOx5Ge4nXKCtAFHFV9YKkXvGcC5WMoTjAteLzlsQROVI0Yw==",
"cpu": [
"arm64"
],
@@ -2778,28 +3930,59 @@
}
},
"node_modules/@rolldown/binding-wasm32-wasi": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.3.tgz",
- "integrity": "sha512-Elcv/BtML9lXrV6JuKITc/grN2kYV9gjsQpW8Jfw4ioK0TOkjBjye0nnyqQNy9STNaI20lXNaQBRrD5gSgR0Yg==",
- "cpu": [
- "wasm32"
- ],
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.2.1.tgz",
+ "integrity": "sha512-/TX0SoRGojHzSAHpfVBbavRVSazg5U3h3Y3VXfcc0cdugq6kxdqw8LPGFiPr+/7gE/60zRcsOY2Vi9b9eT0jww==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
- "@emnapi/core": "1.11.1",
- "@emnapi/runtime": "1.11.1",
- "@napi-rs/wasm-runtime": "^1.1.6"
+ "@emnapi/core": "2.0.0-alpha.3",
+ "@emnapi/runtime": "2.0.0-alpha.3",
+ "@napi-rs/wasm-runtime": "^1.2.0"
},
"engines": {
- "node": "^20.19.0 || >=22.12.0"
+ "node": "^20.19.0 || ^22.13.0 || >=23.5.0"
+ }
+ },
+ "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/core": {
+ "version": "2.0.0-alpha.3",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-2.0.0-alpha.3.tgz",
+ "integrity": "sha512-AZypUeJ/yByuxyS7BlSNRDOMLMlROYtjYdIAuBmJssVz1UJDSeYxLrdizhXCFYhedC5bqd/ASy8EuNXbVVXp9g==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/wasi-threads": "2.0.1",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/runtime": {
+ "version": "2.0.0-alpha.3",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-2.0.0-alpha.3.tgz",
+ "integrity": "sha512-hFPAhMUjJD9BSyCANEISPOogeXC9Zo9ZQl7L6vKnaVsMkCtzznaW/naYypeyl0Gv5rYfWYsZbpixTMpjDJzQeA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-2.0.1.tgz",
+ "integrity": "sha512-9DsSk+o5NBX0CCJT8s0EROGSGxjR/tKu6aBTaVyq+SjAEQH4XcdcRxPBRzsBLizTTJ49MJjF+jgu3qnO9GLQcQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
}
},
"node_modules/@rolldown/binding-win32-arm64-msvc": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.3.tgz",
- "integrity": "sha512-2DrEfhluH9yhiaFApmsjsjwrSYbNcY1oFTzYSP1a535jDbV98zCFanA/96TBUd0iDFcxGmw9QRExwGCXz3U+/g==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.1.tgz",
+ "integrity": "sha512-EvRrivJieyHG+AO9lleZWgq+g0+S7oV2C51yuqlcyU/R9net+sI4Pj0F+lUoP2bEr6TWX3SqFaaS0SzfLxSzkw==",
"cpu": [
"arm64"
],
@@ -2814,9 +3997,9 @@
}
},
"node_modules/@rolldown/binding-win32-x64-msvc": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.3.tgz",
- "integrity": "sha512-OL4OMk7UPXOeVGGd3qo5zJyPIljf4AFgk5QAkPPS+OoLuOOozhuaQGC18MxVTnw/06q93gShAJzlwnSCY9YtqA==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.1.tgz",
+ "integrity": "sha512-Z4eCmn5QJ/5+azF9knpLWKfVd9aidn0mAe9TpJgvBLId9Ax3t0+JVxBmT25Bv7NBbVW1TZyKjQjQReouMeH5UQ==",
"cpu": [
"x64"
],
@@ -2837,100 +4020,449 @@
"dev": true,
"license": "MIT"
},
- "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==",
+ "node_modules/@scarf/scarf": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz",
+ "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/@sec-ant/readable-stream": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz",
+ "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==",
"dev": true,
"license": "MIT"
},
- "node_modules/@testing-library/dom": {
- "version": "10.4.1",
- "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
- "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
+ "node_modules/@sentry/conventions": {
+ "version": "0.16.0",
+ "resolved": "https://registry.npmjs.org/@sentry/conventions/-/conventions-0.16.0.tgz",
+ "integrity": "sha512-fO9PLmHdVURcSPUpWCItWAtgKiMwGdJHbovoSEyLplX5sxs2ugvI4CBPTrkkgqhObnZOD0CnWBKDzSVQYBKEyQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@sentry/core": {
+ "version": "10.69.0",
+ "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.69.0.tgz",
+ "integrity": "sha512-+uuqVEeiDzYuAKjZLqsROKXvRTbl/QeH0gfGRtpYib1cud4rAFWRIkFmcR7Jb7JGFYwmReyQotiTj/hcDszTZg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/code-frame": "^7.10.4",
- "@babel/runtime": "^7.12.5",
- "@types/aria-query": "^5.0.1",
- "aria-query": "5.3.0",
- "dom-accessibility-api": "^0.5.9",
- "lz-string": "^1.5.0",
- "picocolors": "1.1.1",
- "pretty-format": "^27.0.2"
+ "@sentry/conventions": "^0.16.0"
},
"engines": {
"node": ">=18"
}
},
- "node_modules/@testing-library/jest-dom": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-7.0.0.tgz",
- "integrity": "sha512-HKAH9C6mBo5yBG6yRO5i43L2iisencAo5z+o5P/saHUoY+miC5ivXRxHBJcFyB5ypPNxHJdK3BoF/3O4DIptMg==",
+ "node_modules/@sentry/node": {
+ "version": "10.69.0",
+ "resolved": "https://registry.npmjs.org/@sentry/node/-/node-10.69.0.tgz",
+ "integrity": "sha512-xEXA1YGIiTZbrW6MWV34uS6JGQuQg2ijTI0zed+FsJb9JZKPYel/GZK8Km26vfTVb+yCXFmWZNBesKegNcVdzg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@adobe/css-tools": "^4.4.0",
- "aria-query": "^5.0.0",
- "css.escape": "^1.5.1",
- "dom-accessibility-api": "^0.6.3",
- "picocolors": "^1.1.1",
- "redent": "^3.0.0"
+ "@opentelemetry/api": "^1.9.1",
+ "@opentelemetry/instrumentation": "^0.220.0",
+ "@opentelemetry/sdk-trace-base": "^2.9.0",
+ "@sentry/conventions": "^0.16.0",
+ "@sentry/core": "10.69.0",
+ "@sentry/node-core": "10.69.0",
+ "@sentry/opentelemetry": "10.69.0",
+ "@sentry/server-utils": "10.69.0",
+ "import-in-the-middle": "^3.0.0"
},
"engines": {
- "node": ">=22",
- "npm": ">=6",
- "yarn": ">=1"
- },
- "peerDependencies": {
- "@testing-library/dom": ">=10 <11"
+ "node": ">=18"
}
},
- "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz",
- "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@testing-library/react": {
- "version": "16.3.2",
- "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz",
- "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==",
+ "node_modules/@sentry/node-core": {
+ "version": "10.69.0",
+ "resolved": "https://registry.npmjs.org/@sentry/node-core/-/node-core-10.69.0.tgz",
+ "integrity": "sha512-IgArHczrZJxkgxoffHscj0NxQrG6kCazgmGQnlf3j58J1ec21YaUu8Tu+7G4Lo5tCiW3teQnwlKW1ttMXSqWRw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/runtime": "^7.12.5"
+ "@sentry/conventions": "^0.16.0",
+ "@sentry/core": "10.69.0",
+ "@sentry/opentelemetry": "10.69.0",
+ "import-in-the-middle": "^3.0.0"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
- "@testing-library/dom": "^10.0.0",
- "@types/react": "^18.0.0 || ^19.0.0",
- "@types/react-dom": "^18.0.0 || ^19.0.0",
- "react": "^18.0.0 || ^19.0.0",
- "react-dom": "^18.0.0 || ^19.0.0"
+ "@opentelemetry/api": "^1.9.0",
+ "@opentelemetry/core": "^1.30.1 || ^2.1.0",
+ "@opentelemetry/exporter-trace-otlp-http": ">=0.57.0 <1",
+ "@opentelemetry/instrumentation": ">=0.57.1 <1",
+ "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0"
},
"peerDependenciesMeta": {
- "@types/react": {
+ "@opentelemetry/api": {
"optional": true
},
- "@types/react-dom": {
+ "@opentelemetry/core": {
+ "optional": true
+ },
+ "@opentelemetry/exporter-trace-otlp-http": {
+ "optional": true
+ },
+ "@opentelemetry/instrumentation": {
+ "optional": true
+ },
+ "@opentelemetry/sdk-trace-base": {
"optional": true
}
}
},
- "node_modules/@testing-library/user-event": {
- "version": "14.6.1",
- "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz",
- "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==",
+ "node_modules/@sentry/opentelemetry": {
+ "version": "10.69.0",
+ "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-10.69.0.tgz",
+ "integrity": "sha512-3FyWV6YcEJuvLrlaKGE1dHXCI+1YO0a62w7PkwlRg8yp6K6YXkmdwu9GjqaYD+Ju4tm7uC7mHIsGFQMm0M7pqQ==",
"dev": true,
"license": "MIT",
+ "dependencies": {
+ "@sentry/conventions": "^0.16.0",
+ "@sentry/core": "10.69.0"
+ },
"engines": {
- "node": ">=12",
- "npm": ">=6"
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@opentelemetry/api": "^1.9.0",
+ "@opentelemetry/core": "^1.30.1 || ^2.1.0",
+ "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0"
+ }
+ },
+ "node_modules/@sentry/server-utils": {
+ "version": "10.69.0",
+ "resolved": "https://registry.npmjs.org/@sentry/server-utils/-/server-utils-10.69.0.tgz",
+ "integrity": "sha512-0MwHrA8+nNvMIsqf8m3cXwCBlUjr6AS7N6CZvHJtY1DkqEvQqEbD5VIrhzEyHN/KMZIgQ8XeDCQRhjnXFQGRhg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@apm-js-collab/code-transformer-bundler-plugins": "^0.7.3",
+ "@apm-js-collab/tracing-hooks": "^0.13.0",
+ "@sentry/conventions": "^0.16.0",
+ "@sentry/core": "10.69.0",
+ "meriyah": "^6.1.4"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@sindresorhus/merge-streams": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz",
+ "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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/@stryker-mutator/api": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/@stryker-mutator/api/-/api-9.6.1.tgz",
+ "integrity": "sha512-g8VNoFWQWbx0pdal3Vt8jVCZW+v3sc3gi94iI0GVtVgUGTqphAjJF6EAruPTx0lqvtonsaAxn5TD36hcG1d6Wg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "mutation-testing-metrics": "3.7.3",
+ "mutation-testing-report-schema": "3.7.3",
+ "tslib": "~2.8.0",
+ "typed-inject": "~5.0.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@stryker-mutator/core": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/@stryker-mutator/core/-/core-9.6.1.tgz",
+ "integrity": "sha512-WMgnvf+Wyh/yiruhNZwc8w8DlzmmjXhPjSn5MR8RhAXzlnWji8TQrUYgBUkHk9bEgSaIlB3KZHm37iiU5Q2cLQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@inquirer/prompts": "^8.0.0",
+ "@stryker-mutator/api": "9.6.1",
+ "@stryker-mutator/instrumenter": "9.6.1",
+ "@stryker-mutator/util": "9.6.1",
+ "ajv": "~8.18.0",
+ "chalk": "~5.6.0",
+ "commander": "~14.0.0",
+ "diff-match-patch": "1.0.5",
+ "emoji-regex": "~10.6.0",
+ "execa": "~9.6.0",
+ "json-rpc-2.0": "^1.7.0",
+ "lodash.groupby": "~4.6.0",
+ "minimatch": "~10.2.4",
+ "mutation-server-protocol": "~0.4.0",
+ "mutation-testing-elements": "3.7.3",
+ "mutation-testing-metrics": "3.7.3",
+ "mutation-testing-report-schema": "3.7.3",
+ "npm-run-path": "~6.0.0",
+ "progress": "~2.0.3",
+ "rxjs": "~7.8.1",
+ "semver": "^7.6.3",
+ "source-map": "~0.7.4",
+ "tree-kill": "~1.2.2",
+ "tslib": "2.8.1",
+ "typed-inject": "~5.0.0",
+ "typed-rest-client": "~2.3.0"
+ },
+ "bin": {
+ "stryker": "bin/stryker.js"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@stryker-mutator/core/node_modules/ajv": {
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
+ "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "fast-uri": "^3.0.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/@stryker-mutator/core/node_modules/chalk": {
+ "version": "5.6.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
+ "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.17.0 || ^14.13 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/@stryker-mutator/core/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/@stryker-mutator/core/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/@stryker-mutator/core/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/@stryker-mutator/core/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/@stryker-mutator/instrumenter": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/@stryker-mutator/instrumenter/-/instrumenter-9.6.1.tgz",
+ "integrity": "sha512-5K8wH4Pthly25c2uKKik4Dfcoeou7sbJdFS6u3QIYHlulgFVDJwtEMWTZGkZfs7IiUEXIDNa0keRACq5jn5AvA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@babel/core": "~7.29.0",
+ "@babel/generator": "~7.29.0",
+ "@babel/parser": "~7.29.0",
+ "@babel/plugin-proposal-decorators": "~7.29.0",
+ "@babel/plugin-transform-explicit-resource-management": "^7.28.0",
+ "@babel/preset-typescript": "~7.28.0",
+ "@stryker-mutator/api": "9.6.1",
+ "@stryker-mutator/util": "9.6.1",
+ "angular-html-parser": "~10.4.0",
+ "semver": "~7.7.0",
+ "tslib": "2.8.1",
+ "weapon-regex": "~1.3.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@stryker-mutator/instrumenter/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@stryker-mutator/util": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/@stryker-mutator/util/-/util-9.6.1.tgz",
+ "integrity": "sha512-Lk/ALVctJjFv1vvwR+CFoKzDCWvsBlq7flDUnmnpuwTrGbm156EdZD1Jjq4o8KdOap0ezUZqQNE9OAI1m2+pUQ==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/@stryker-mutator/vitest-runner": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/@stryker-mutator/vitest-runner/-/vitest-runner-9.6.1.tgz",
+ "integrity": "sha512-eyUHTCf3Ui+SUn/tpFJwzw6MV391kyBLZk/cDHFUfKFELqKMLbvd7e81axArlApKqO6cOnLfrxlwED+2SRN0ow==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@stryker-mutator/api": "9.6.1",
+ "@stryker-mutator/util": "9.6.1",
+ "semver": "^7.7.4",
+ "tslib": "~2.8.0"
+ },
+ "engines": {
+ "node": ">=14.18.0"
+ },
+ "peerDependencies": {
+ "@stryker-mutator/core": "9.6.1",
+ "vitest": ">=2.0.0"
+ }
+ },
+ "node_modules/@stryker-mutator/vitest-runner/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/@testing-library/dom": {
+ "version": "10.4.1",
+ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
+ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.10.4",
+ "@babel/runtime": "^7.12.5",
+ "@types/aria-query": "^5.0.1",
+ "aria-query": "5.3.0",
+ "dom-accessibility-api": "^0.5.9",
+ "lz-string": "^1.5.0",
+ "picocolors": "1.1.1",
+ "pretty-format": "^27.0.2"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@testing-library/jest-dom": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-7.0.0.tgz",
+ "integrity": "sha512-HKAH9C6mBo5yBG6yRO5i43L2iisencAo5z+o5P/saHUoY+miC5ivXRxHBJcFyB5ypPNxHJdK3BoF/3O4DIptMg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@adobe/css-tools": "^4.4.0",
+ "aria-query": "^5.0.0",
+ "css.escape": "^1.5.1",
+ "dom-accessibility-api": "^0.6.3",
+ "picocolors": "^1.1.1",
+ "redent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=22",
+ "npm": ">=6",
+ "yarn": ">=1"
+ },
+ "peerDependencies": {
+ "@testing-library/dom": ">=10 <11"
+ }
+ },
+ "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz",
+ "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@testing-library/react": {
+ "version": "16.3.2",
+ "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz",
+ "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@testing-library/dom": "^10.0.0",
+ "@types/react": "^18.0.0 || ^19.0.0",
+ "@types/react-dom": "^18.0.0 || ^19.0.0",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@testing-library/user-event": {
+ "version": "14.6.1",
+ "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz",
+ "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12",
+ "npm": ">=6"
},
"peerDependencies": {
"@testing-library/dom": ">=7.21.4"
@@ -3001,13 +4533,13 @@
"license": "MIT"
},
"node_modules/@types/node": {
- "version": "26.0.1",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.1.tgz",
- "integrity": "sha512-fc3KiUoBt6kie0N9bIW3E47vZsuaMf0PM2AaUpLCLT0s/LvX1nxAim6Fc049cNxODPpGm6qRAuUOB86SkRuPQw==",
+ "version": "24.13.3",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz",
+ "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "undici-types": "~8.3.0"
+ "undici-types": "~7.18.0"
}
},
"node_modules/@types/parse-json": {
@@ -3054,17 +4586,17 @@
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.62.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.62.0.tgz",
- "integrity": "sha512-o+mpz7EYiMzXoySXiKmzlabIvTVqUuK5yLrAedRPRDA0IpPFMUV1IXt6OqljIxX/kumN6EjUYp41Hqelh6p/Dw==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.65.0.tgz",
+ "integrity": "sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.12.2",
- "@typescript-eslint/scope-manager": "8.62.0",
- "@typescript-eslint/type-utils": "8.62.0",
- "@typescript-eslint/utils": "8.62.0",
- "@typescript-eslint/visitor-keys": "8.62.0",
+ "@typescript-eslint/scope-manager": "8.65.0",
+ "@typescript-eslint/type-utils": "8.65.0",
+ "@typescript-eslint/utils": "8.65.0",
+ "@typescript-eslint/visitor-keys": "8.65.0",
"ignore": "^7.0.5",
"natural-compare": "^1.4.0",
"ts-api-utils": "^2.5.0"
@@ -3077,15 +4609,15 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "@typescript-eslint/parser": "^8.62.0",
+ "@typescript-eslint/parser": "^8.65.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.5",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
- "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
+ "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": {
@@ -3093,16 +4625,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
- "version": "8.62.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.62.0.tgz",
- "integrity": "sha512-dzHeT2gySzZtLDsuqxU9AkYgIsQoHAHtRBpOqM+Ofzx1Bwrd2RcCjQJ+6iQbsHOIR6NS33bF2W1k3blN1zLDrA==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.65.0.tgz",
+ "integrity": "sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/scope-manager": "8.62.0",
- "@typescript-eslint/types": "8.62.0",
- "@typescript-eslint/typescript-estree": "8.62.0",
- "@typescript-eslint/visitor-keys": "8.62.0",
+ "@typescript-eslint/scope-manager": "8.65.0",
+ "@typescript-eslint/types": "8.65.0",
+ "@typescript-eslint/typescript-estree": "8.65.0",
+ "@typescript-eslint/visitor-keys": "8.65.0",
"debug": "^4.4.3"
},
"engines": {
@@ -3118,14 +4650,14 @@
}
},
"node_modules/@typescript-eslint/project-service": {
- "version": "8.62.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.62.0.tgz",
- "integrity": "sha512-wexnCqiTg7BOGtbLDftYpRWlmLq4xfoMd7BKFR6Y75sZS3QmRKLdN3yWLhmIYgqMmP/OXWpj3H8odkb5nGURCQ==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz",
+ "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/tsconfig-utils": "^8.62.0",
- "@typescript-eslint/types": "^8.62.0",
+ "@typescript-eslint/tsconfig-utils": "^8.65.0",
+ "@typescript-eslint/types": "^8.65.0",
"debug": "^4.4.3"
},
"engines": {
@@ -3140,14 +4672,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
- "version": "8.62.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.62.0.tgz",
- "integrity": "sha512-1lX38kNxXIRb8mEc3lbq5mdHq1Pf2+U0nFU65KfT18mtPxxl0fvjuEE92mHuXPuCtElJhOrddOpyMlM3Z0umEA==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz",
+ "integrity": "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.62.0",
- "@typescript-eslint/visitor-keys": "8.62.0"
+ "@typescript-eslint/types": "8.65.0",
+ "@typescript-eslint/visitor-keys": "8.65.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -3158,9 +4690,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
- "version": "8.62.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.62.0.tgz",
- "integrity": "sha512-y2GAdB6ykaXUvuspbYnizQc4oDDz0Tz/Yc7iWrXf9mx8vm/L/0vLHCe0tS2boG96Zy+DivnVDQ9ZUEWoHqqx1g==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz",
+ "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -3175,15 +4707,15 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
- "version": "8.62.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.62.0.tgz",
- "integrity": "sha512-+g5O3j0w2ldzC86Pv6fvbO/xhAonbJFIdf/MKQ1d30gndlsVzUOE83ldfSE15Qrl9fhFjK6AovHs5Wpp6vx86w==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.65.0.tgz",
+ "integrity": "sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.62.0",
- "@typescript-eslint/typescript-estree": "8.62.0",
- "@typescript-eslint/utils": "8.62.0",
+ "@typescript-eslint/types": "8.65.0",
+ "@typescript-eslint/typescript-estree": "8.65.0",
+ "@typescript-eslint/utils": "8.65.0",
"debug": "^4.4.3",
"ts-api-utils": "^2.5.0"
},
@@ -3200,9 +4732,9 @@
}
},
"node_modules/@typescript-eslint/types": {
- "version": "8.62.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.62.0.tgz",
- "integrity": "sha512-KvAclkktORPvM54TgLgA4z9HIV1M8zOgw9ZVNXl9f/8dLYfXYX1wkMXP7qmabpijQRV5bHJLOmoyGQbLMaUYeg==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz",
+ "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -3214,16 +4746,16 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.62.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.62.0.tgz",
- "integrity": "sha512-+hVbNxtW64pIcZWDPGbyaKF7vp2IBTVY5ma1blwwksrjdsbdqqEKvJWMGbBofei4F6Dovx1M0RJgoFeNu2279A==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz",
+ "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/project-service": "8.62.0",
- "@typescript-eslint/tsconfig-utils": "8.62.0",
- "@typescript-eslint/types": "8.62.0",
- "@typescript-eslint/visitor-keys": "8.62.0",
+ "@typescript-eslint/project-service": "8.65.0",
+ "@typescript-eslint/tsconfig-utils": "8.65.0",
+ "@typescript-eslint/types": "8.65.0",
+ "@typescript-eslint/visitor-keys": "8.65.0",
"debug": "^4.4.3",
"minimatch": "^10.2.2",
"semver": "^7.7.3",
@@ -3255,16 +4787,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
- "version": "8.62.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.62.0.tgz",
- "integrity": "sha512-82r66fi9zYwZ+mTq3vKgwjbZ1PVk/DJzrXFLpG6RnBbdvH8TEGVHIs9H4d2drhkOzf0syZuD/OZvvlu6GDbP4g==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.65.0.tgz",
+ "integrity": "sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.9.1",
- "@typescript-eslint/scope-manager": "8.62.0",
- "@typescript-eslint/types": "8.62.0",
- "@typescript-eslint/typescript-estree": "8.62.0"
+ "@typescript-eslint/scope-manager": "8.65.0",
+ "@typescript-eslint/types": "8.65.0",
+ "@typescript-eslint/typescript-estree": "8.65.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -3279,13 +4811,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.62.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.62.0.tgz",
- "integrity": "sha512-CY3uyFSRbcQv3nnSv8S0+lDftMVz6P963PoRlxrV7ew/Md564g9ut60PYzdLM5qW4jFn93GBF+Soi90ISAN+GQ==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.65.0.tgz",
+ "integrity": "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.62.0",
+ "@typescript-eslint/types": "8.65.0",
"eslint-visitor-keys": "^5.0.0"
},
"engines": {
@@ -3310,9 +4842,9 @@
}
},
"node_modules/@vitejs/plugin-react": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.3.tgz",
- "integrity": "sha512-vmFvco5/QuC2f9Oj+wTk0+9XeDFkHxSamwZKYc7MxYwKICfvUvlMhqKI0VuICPltGqh1neqBKDvO4kes1ya8vg==",
+ "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": {
@@ -3479,6 +5011,47 @@
"url": "https://opencollective.com/vitest"
}
},
+ "node_modules/accepts": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "^3.0.0",
+ "negotiator": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/accepts/node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/accepts/node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
"node_modules/acorn": {
"version": "8.17.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz",
@@ -3531,18 +5104,38 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
- "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/angular-html-parser": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/angular-html-parser/-/angular-html-parser-10.4.0.tgz",
+ "integrity": "sha512-++nLNyZwRfHqFh7akH5Gw/JYizoFlMRz0KRigfwfsLqV8ZqlcVRb1LkPEWdYvEKDnbktknM2J4BXaYUGrQZPww==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=8"
+ "node": ">= 14"
}
},
- "node_modules/ansi-styles": {
- "version": "5.2.0",
+ "node_modules/ansi-colors": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
+ "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "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",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "5.2.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
"dev": true,
@@ -3555,16 +5148,16 @@
}
},
"node_modules/antd": {
- "version": "6.5.0",
- "resolved": "https://registry.npmjs.org/antd/-/antd-6.5.0.tgz",
- "integrity": "sha512-9zbVc9UukfGuqCvIAov01nlpDQWfARNmZQyt21ZhqLX7ilXmi4cdkp12xA48WEmXRXwZvno8A03qQuGE9JG8fg==",
+ "version": "6.5.2",
+ "resolved": "https://registry.npmjs.org/antd/-/antd-6.5.2.tgz",
+ "integrity": "sha512-ntYx0lr4Jq192QnBkDWkDqEeoberXZ34vSE9SgiP/0J6DY8O0pzR3bVZLBsdpCSguVkwjtEAP+QNeMN7LNAvgw==",
"license": "MIT",
"dependencies": {
"@ant-design/colors": "^8.0.1",
"@ant-design/cssinjs": "^2.1.2",
"@ant-design/cssinjs-utils": "^2.1.2",
"@ant-design/fast-color": "^3.0.1",
- "@ant-design/icons": "^6.3.1",
+ "@ant-design/icons": "^6.3.2",
"@ant-design/react-slick": "~2.0.0",
"@babel/runtime": "^7.29.2",
"@rc-component/cascader": "~1.17.0",
@@ -3573,7 +5166,7 @@
"@rc-component/color-picker": "~3.1.1",
"@rc-component/dialog": "~1.10.0",
"@rc-component/drawer": "~1.4.2",
- "@rc-component/dropdown": "~1.0.2",
+ "@rc-component/dropdown": "~1.0.3",
"@rc-component/form": "~1.8.5",
"@rc-component/image": "~1.9.0",
"@rc-component/input": "~1.3.1",
@@ -3594,15 +5187,15 @@
"@rc-component/slider": "~1.1.1",
"@rc-component/steps": "~1.2.2",
"@rc-component/switch": "~1.0.3",
- "@rc-component/table": "~1.10.2",
+ "@rc-component/table": "~1.10.4",
"@rc-component/tabs": "~1.11.0",
"@rc-component/tooltip": "~1.4.0",
"@rc-component/tour": "~2.4.0",
"@rc-component/tree": "~1.3.2",
"@rc-component/tree-select": "~1.11.0",
- "@rc-component/trigger": "^3.9.1",
+ "@rc-component/trigger": "^3.10.1",
"@rc-component/upload": "~1.1.1",
- "@rc-component/util": "^1.11.1",
+ "@rc-component/util": "^1.12.0",
"clsx": "^2.1.1",
"dayjs": "^1.11.11",
"scroll-into-view-if-needed": "^3.1.0",
@@ -3676,20 +5269,61 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/astring": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz",
+ "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "astring": "bin/astring"
+ }
+ },
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
+ "node_modules/atomic-sleep": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
+ "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/atomically": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/atomically/-/atomically-2.1.1.tgz",
+ "integrity": "sha512-P4w9o2dqARji6P7MHprklbfiArZAWvo07yW7qs3pdljb3BWr12FIB7W+p0zJiuiVsUpRO0iZn1kFFcpPegg0tQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "stubborn-fs": "^2.0.0",
+ "when-exit": "^2.1.4"
+ }
+ },
+ "node_modules/axe-core": {
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.12.1.tgz",
+ "integrity": "sha512-s7iGf5GaVMxEG0ENN9x+xTr7GFZCb1ZP/1uATUpCEK2X78nDB3RwbtFCo9pGAf9ru+VwoQ464DkaLEeRM08wJA==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/axios": {
- "version": "1.18.1",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz",
- "integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==",
+ "version": "1.19.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.19.0.tgz",
+ "integrity": "sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.16.0",
- "form-data": "^4.0.5",
+ "form-data": "^4.0.6",
"https-proxy-agent": "^5.0.1",
"proxy-from-env": "^2.1.0"
}
@@ -3742,17 +5376,42 @@
"require-from-string": "^2.0.2"
}
},
+ "node_modules/body-parser": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz",
+ "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "^3.1.2",
+ "content-type": "^2.0.0",
+ "debug": "^4.4.3",
+ "http-errors": "^2.0.1",
+ "iconv-lite": "^0.7.2",
+ "on-finished": "^2.4.1",
+ "qs": "^6.15.2",
+ "raw-body": "^3.0.2",
+ "type-is": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
"node_modules/brace-expansion": {
- "version": "5.0.6",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
- "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
+ "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": "18 || 20 || >=22"
+ "node": "20 || >=22"
}
},
"node_modules/browserslist": {
@@ -3789,6 +5448,16 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
@@ -3802,6 +5471,23 @@
"node": ">= 0.4"
}
},
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -3842,6 +5528,106 @@
"node": ">=18"
}
},
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/chalk/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": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/chardet": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.2.0.tgz",
+ "integrity": "sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/check-types": {
+ "version": "11.2.3",
+ "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.2.3.tgz",
+ "integrity": "sha512-+67P1GkJRaxQD6PKK0Et9DhwQB+vGg3PM5+aavopCpZT1lj9jeqfvpgTLAWErNj8qApkkmXlu/Ug74kmhagkXg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/chrome-launcher": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.2.1.tgz",
+ "integrity": "sha512-qmFR5PLMzHyuNJHwOloHPAHhbaNglkfeV/xDtt5b7xiFFyU1I+AZZX0PYseMuhenJSSirgxELYIbswcoc+5H4A==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/node": "*",
+ "escape-string-regexp": "^4.0.0",
+ "is-wsl": "^2.2.0",
+ "lighthouse-logger": "^2.0.1"
+ },
+ "bin": {
+ "print-chrome-path": "bin/print-chrome-path.cjs"
+ },
+ "engines": {
+ "node": ">=12.13.0"
+ }
+ },
+ "node_modules/chromium-bidi": {
+ "version": "17.0.2",
+ "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-17.0.2.tgz",
+ "integrity": "sha512-5v9GQFhTktFvotn/OFNJBmKLKRAb6n9r0bVCwf7sHgWc3/JryK0bj1nn93L3pHFrfgcsu6Be6EWsDi+1XHTGDg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "mitt": "^3.0.1",
+ "zod": "^3.24.1"
+ },
+ "engines": {
+ "node": ">=20.19.0 <22.0.0 || >=22.12.0"
+ },
+ "peerDependencies": {
+ "devtools-protocol": "*"
+ }
+ },
+ "node_modules/chromium-bidi/node_modules/zod": {
+ "version": "3.25.76",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
+ "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "node_modules/cjs-module-lexer": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz",
+ "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/cli-width": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz",
@@ -3896,6 +5682,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/colorette": {
+ "version": "2.0.20",
+ "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
+ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -3908,12 +5701,69 @@
"node": ">= 0.8"
}
},
+ "node_modules/commander": {
+ "version": "14.0.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz",
+ "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=20"
+ }
+ },
"node_modules/compute-scroll-into-view": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz",
"integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==",
"license": "MIT"
},
+ "node_modules/configstore": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/configstore/-/configstore-7.1.0.tgz",
+ "integrity": "sha512-N4oog6YJWbR9kGyXvS7jEykLDXIE2C0ILYqNBZBp9iwiJpoCBWYsuAdW6PPFn6w06jjnC+3JstVvWHO4cZqvRg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "atomically": "^2.0.3",
+ "dot-prop": "^9.0.0",
+ "graceful-fs": "^4.2.11",
+ "xdg-basedir": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/content-disposition": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz",
+ "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz",
+ "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
"node_modules/convert-source-map": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
@@ -3925,6 +5775,7 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
"integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
@@ -3934,6 +5785,22 @@
"url": "https://opencollective.com/express"
}
},
+ "node_modules/cookie-es": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-3.1.1.tgz",
+ "integrity": "sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==",
+ "license": "MIT"
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.6.0"
+ }
+ },
"node_modules/cosmiconfig": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
@@ -3974,6 +5841,13 @@
"node": ">= 8"
}
},
+ "node_modules/csp_evaluator": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/csp_evaluator/-/csp_evaluator-1.1.8.tgz",
+ "integrity": "sha512-EwOnfYuNbTytvbMKsLixTrRgnjOa0WZCxGy8A9nnSYAicrdwn+T/epU/yjgymmOxlgKnvH+8wXt+7p/8ak5Feg==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
"node_modules/css-tree": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz",
@@ -4015,6 +5889,16 @@
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
}
},
+ "node_modules/dateformat": {
+ "version": "4.6.3",
+ "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz",
+ "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/dayjs": {
"version": "1.11.21",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz",
@@ -4052,6 +5936,16 @@
"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/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -4061,6 +5955,16 @@
"node": ">=0.4.0"
}
},
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/dequal": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
@@ -4071,6 +5975,17 @@
"node": ">=6"
}
},
+ "node_modules/des.js": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz",
+ "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "^2.0.1",
+ "minimalistic-assert": "^1.0.0"
+ }
+ },
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
@@ -4081,6 +5996,20 @@
"node": ">=8"
}
},
+ "node_modules/devtools-protocol": {
+ "version": "0.0.1663043",
+ "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1663043.tgz",
+ "integrity": "sha512-33aOY3ZnBP1dgZsshgaL+/XlsQleiFZgyUaDtdZkEa1nbZhVY1MoDeWjk+wxg25fU924l1ZJfoGNmjjeA/5s1w==",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/diff-match-patch": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz",
+ "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
"node_modules/dom-accessibility-api": {
"version": "0.5.16",
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
@@ -4088,6 +6017,45 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/dot-prop": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-9.0.0.tgz",
+ "integrity": "sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "type-fest": "^4.18.2"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/dot-prop/node_modules/type-fest": {
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
+ "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/drange": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/drange/-/drange-1.1.1.tgz",
+ "integrity": "sha512-pYxfDYpued//QpnLIm4Avk7rsNtAtQkUES2cwAYSvD/wd2pKD71gN2Ebj3e7klzXwjocvE8c5vx/1fxwpqmSxA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -4102,6 +6070,13 @@
"node": ">= 0.4"
}
},
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/electron-to-chromium": {
"version": "1.5.380",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.380.tgz",
@@ -4116,6 +6091,40 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/end-of-stream": {
+ "version": "1.4.5",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
+ "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "once": "^1.4.0"
+ }
+ },
+ "node_modules/enquirer": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz",
+ "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-colors": "^4.1.1",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
"node_modules/entities": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz",
@@ -4200,6 +6209,13 @@
"node": ">=6"
}
},
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -4213,9 +6229,9 @@
}
},
"node_modules/eslint": {
- "version": "10.6.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.6.0.tgz",
- "integrity": "sha512-6lVbcqSodALYo+4ELD0heG6lFiFxnLMuLkiMi2qV8LMp54N8tE8FT1GMH+ev4Ti00nFjNze2+Su6DsV5OQW3Dg==",
+ "version": "10.8.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.8.0.tgz",
+ "integrity": "sha512-nuKKvN+oIBO0koN7Tm7dlkmnkc21mtt0QJLwAKzjLq14y6lRTdVG36MZHJ8eQHwdJMwZbQNMlPOYedMq/oVJvQ==",
"dev": true,
"license": "MIT",
"workspaces": [
@@ -4225,7 +6241,7 @@
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.2",
"@eslint/config-array": "^0.23.5",
- "@eslint/config-helpers": "^0.6.0",
+ "@eslint/config-helpers": "^0.7.0",
"@eslint/core": "^1.2.1",
"@eslint/plugin-kit": "^0.7.2",
"@humanfs/node": "^0.16.6",
@@ -4249,7 +6265,7 @@
"imurmurhash": "^0.1.4",
"is-glob": "^4.0.0",
"json-stable-stringify-without-jsonify": "^1.0.1",
- "minimatch": "^10.2.4",
+ "minimatch": "^10.2.5",
"natural-compare": "^1.4.0",
"optionator": "^0.9.3"
},
@@ -4302,9 +6318,9 @@
}
},
"node_modules/eslint-plugin-simple-import-sort": {
- "version": "13.0.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-13.0.0.tgz",
- "integrity": "sha512-McAc+/Nlvcg4byY/CABGH8kqnefWBj8s3JA2okEtz8ixbECQgU46p0HkTUKa4YS7wvgGceimlc34p1nXqbWqtA==",
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-14.0.0.tgz",
+ "integrity": "sha512-NUJO0+XFCkk+o5EsAJruTgnfMEpeWrPWeJS15UVF60GgXmqz1BJ9/3hzlvG7lkL8Bubzos5cCLptThbFfPnSMQ==",
"dev": true,
"license": "MIT",
"peerDependencies": {
@@ -4417,6 +6433,50 @@
"node": ">=0.10.0"
}
},
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/eventemitter3": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
+ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/execa": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz",
+ "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@sindresorhus/merge-streams": "^4.0.0",
+ "cross-spawn": "^7.0.6",
+ "figures": "^6.1.0",
+ "get-stream": "^9.0.0",
+ "human-signals": "^8.0.1",
+ "is-plain-obj": "^4.1.0",
+ "is-stream": "^4.0.1",
+ "npm-run-path": "^6.0.0",
+ "pretty-ms": "^9.2.0",
+ "signal-exit": "^4.1.0",
+ "strip-final-newline": "^4.0.0",
+ "yoctocolors": "^2.1.1"
+ },
+ "engines": {
+ "node": "^18.19.0 || >=20.5.0"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
"node_modules/expect-type": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz",
@@ -4427,6 +6487,104 @@
"node": ">=12.0.0"
}
},
+ "node_modules/express": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
+ "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "^2.0.0",
+ "body-parser": "^2.2.1",
+ "content-disposition": "^1.0.0",
+ "content-type": "^1.0.5",
+ "cookie": "^0.7.1",
+ "cookie-signature": "^1.2.1",
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "finalhandler": "^2.1.0",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.0",
+ "merge-descriptors": "^2.0.0",
+ "mime-types": "^3.0.0",
+ "on-finished": "^2.4.1",
+ "once": "^1.4.0",
+ "parseurl": "^1.3.3",
+ "proxy-addr": "^2.0.7",
+ "qs": "^6.14.0",
+ "range-parser": "^1.2.1",
+ "router": "^2.2.0",
+ "send": "^1.1.0",
+ "serve-static": "^2.2.0",
+ "statuses": "^2.0.1",
+ "type-is": "^2.0.1",
+ "vary": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/express/node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/express/node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/express/node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/express/node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/fast-copy": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-4.0.4.tgz",
+ "integrity": "sha512-eVAiWVNPSEGIzDl5yPuLrx8fNMogScXvD9xp1Kzd41FjRIz2I3sSIcxsFeM5EzFfHAfobdvs8ZySffUopljvIA==",
+ "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",
@@ -4448,6 +6606,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/fast-safe-stringify": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
+ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/fast-string-truncated-width": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz",
@@ -4465,6 +6630,23 @@
"fast-string-truncated-width": "^3.0.2"
}
},
+ "node_modules/fast-uri": {
+ "version": "3.1.4",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz",
+ "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
"node_modules/fast-wrap-ansi": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz",
@@ -4503,6 +6685,22 @@
}
}
},
+ "node_modules/figures": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz",
+ "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-unicode-supported": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/file-entry-cache": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -4516,6 +6714,28 @@
"node": ">=16.0.0"
}
},
+ "node_modules/finalhandler": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
+ "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "on-finished": "^2.4.1",
+ "parseurl": "^1.3.3",
+ "statuses": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
"node_modules/find-root": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
@@ -4612,10 +6832,30 @@
"node": ">=18.3.0"
}
},
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/fsevents": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@@ -4656,6 +6896,19 @@
"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-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
@@ -4693,6 +6946,23 @@
"node": ">= 0.4"
}
},
+ "node_modules/get-stream": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz",
+ "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@sec-ant/readable-stream": "^0.4.1",
+ "is-stream": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/get-tsconfig": {
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz",
@@ -4720,9 +6990,9 @@
}
},
"node_modules/globals": {
- "version": "17.7.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-17.7.0.tgz",
- "integrity": "sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==",
+ "version": "17.8.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-17.8.0.tgz",
+ "integrity": "sha512-Zz/LMDZScFmkakeL2cTHzf+PbWKdpU3uclqkZT7TjDG58j5WPt0PpA+n9uPI24fZtlw07q0OtEi84K+umsRzqQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -4744,6 +7014,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "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/graphql": {
"version": "16.14.2",
"resolved": "https://registry.npmjs.org/graphql/-/graphql-16.14.2.tgz",
@@ -4754,6 +7031,22 @@
"node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0"
}
},
+ "node_modules/graphql-tag": {
+ "version": "2.12.7",
+ "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.7.tgz",
+ "integrity": "sha512-xnE/NFzy+0eIesvAsREJZ284zTl/wYuBAvpsFSDhRGRdRHdnE90M21Q3xAWyYInb0J756c6x0pIQ62+vtvOs1Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "graphql": "^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
+ }
+ },
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -4821,6 +7114,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/help-me": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz",
+ "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/hermes-estree": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
@@ -4874,12 +7174,58 @@
"license": "MIT"
},
"node_modules/html-parse-stringify": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
- "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-4.0.1.tgz",
+ "integrity": "sha512-0zHsZJrK7S3K2aucXWL6ycoYJ/iNtIcFHC/nYQgFklPtrv5LpJctIiSCroWZWeuoXvuyFdzp6KzjJQ+OT5MfFw==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://locize.com"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/http-link-header": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/http-link-header/-/http-link-header-1.1.4.tgz",
+ "integrity": "sha512-xT3GPW6/ZbGuw4UvwHqErSCEjNUlwbQJuZn9/q5U4WEKfp2kENVCAlousG1zLxHeaQ/ffOHUNpWamvkbBW0eNw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/http-proxy": {
+ "version": "1.18.1",
+ "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
+ "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "void-elements": "3.1.0"
+ "eventemitter3": "^4.0.0",
+ "follow-redirects": "^1.0.0",
+ "requires-port": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=8.0.0"
}
},
"node_modules/https-proxy-agent": {
@@ -4895,10 +7241,20 @@
"node": ">= 6"
}
},
+ "node_modules/human-signals": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz",
+ "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
"node_modules/i18next": {
- "version": "26.3.3",
- "resolved": "https://registry.npmjs.org/i18next/-/i18next-26.3.3.tgz",
- "integrity": "sha512-aYVegyBdXSO93CMMihvr47jI7GHSOcIahMpJX+qzUXDzW4xDJf2uenIA+45vDU+YhiVdcfsql70AC9RVdMNrHg==",
+ "version": "26.3.6",
+ "resolved": "https://registry.npmjs.org/i18next/-/i18next-26.3.6.tgz",
+ "integrity": "sha512-Bu5Z2nAXgfVyM8xvW3jk9EKRIuX37PudsrBViThNFx7CR7aaYTpP01cxNB/E4c4UUzTDiAZRstEhsRfPOL/8xA==",
"funding": [
{
"type": "individual",
@@ -4915,7 +7271,7 @@
],
"license": "MIT",
"peerDependencies": {
- "typescript": "^5 || ^6"
+ "typescript": "^5 || ^6 || ^7"
},
"peerDependenciesMeta": {
"typescript": {
@@ -4923,6 +7279,23 @@
}
}
},
+ "node_modules/iconv-lite": {
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz",
+ "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -4933,6 +7306,13 @@
"node": ">= 4"
}
},
+ "node_modules/image-ssim": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/image-ssim/-/image-ssim-0.2.0.tgz",
+ "integrity": "sha512-W7+sO6/yhxy83L0G7xR8YAc5Z5QFtYEXXRV6EaE8tuYBZJnA3gVgp3q7X7muhLZVodeb9UfvjSbwt9VJwjIYAg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/import-fresh": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
@@ -4949,6 +7329,21 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/import-in-the-middle": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-3.3.2.tgz",
+ "integrity": "sha512-jTd2FfOgOWOdgjkHuk/1Ms8VKFXkPs15ymYBETw1sAOrO/dY3XeGVRWir9qBbw7pXr0T2eTFwfCZ+N02HmiNGA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "cjs-module-lexer": "^2.2.0",
+ "es-module-lexer": "^2.2.0",
+ "module-details-from-path": "^1.0.4"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
@@ -4969,9 +7364,39 @@
"node": ">=8"
}
},
- "node_modules/is-arrayish": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/intl-messageformat": {
+ "version": "10.7.18",
+ "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.18.tgz",
+ "integrity": "sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@formatjs/ecma402-abstract": "2.3.6",
+ "@formatjs/fast-memoize": "2.2.7",
+ "@formatjs/icu-messageformat-parser": "2.11.4",
+ "tslib": "^2.8.0"
+ }
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "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==",
"license": "MIT"
},
@@ -4990,6 +7415,22 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "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",
@@ -5036,6 +7477,19 @@
"dev": true,
"license": "MIT"
},
+ "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",
@@ -5043,6 +7497,52 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/is-promise": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/is-stream": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz",
+ "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-unicode-supported": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz",
+ "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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",
@@ -5108,6 +7608,47 @@
"jiti": "lib/jiti-cli.mjs"
}
},
+ "node_modules/joycon": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz",
+ "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/jpeg-js": {
+ "version": "0.4.4",
+ "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz",
+ "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/js-base64": {
+ "version": "3.9.2",
+ "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.9.2.tgz",
+ "integrity": "sha512-6zayE8QlUdiweYI6cETD/XBSqFcoCUlufn/29PJR99r82x1yDnIprRca0YvAYpAW+ez0GuQkVBC6xG5QkD7OjA==",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/js-library-detector": {
+ "version": "6.7.0",
+ "resolved": "https://registry.npmjs.org/js-library-detector/-/js-library-detector-6.7.0.tgz",
+ "integrity": "sha512-c80Qupofp43y4cJ7+8TTDN/AsDwLi5oOm/plBrWI+iQt485vKXCco+yVmOwEgdo9VOdsYTuV0UlTeetVPTriXA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/js-md4": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/js-md4/-/js-md4-0.3.2.tgz",
+ "integrity": "sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -5115,39 +7656,39 @@
"license": "MIT"
},
"node_modules/jsdom": {
- "version": "29.1.1",
- "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz",
- "integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==",
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.0.1.tgz",
+ "integrity": "sha512-52v7mUVUfNQVYYqE1lcdaymWL0njO7lTLUog6ZvW2U5KsbiLk/GnZlVJ+qx0xfNJZ6Gn+KSpPNE52vurbxZwrA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@asamuzakjp/css-color": "^5.1.11",
- "@asamuzakjp/dom-selector": "^7.1.1",
+ "@asamuzakjp/css-color": "^6.0.5",
+ "@asamuzakjp/dom-selector": "^8.3.0",
"@bramus/specificity": "^2.4.2",
- "@csstools/css-syntax-patches-for-csstree": "^1.1.3",
- "@exodus/bytes": "^1.15.0",
+ "@csstools/css-syntax-patches-for-csstree": "^1.1.7",
+ "@exodus/bytes": "^1.15.1",
"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",
+ "lru-cache": "^11.5.2",
"parse5": "^8.0.1",
"saxes": "^6.0.0",
"symbol-tree": "^3.2.4",
- "tough-cookie": "^6.0.1",
- "undici": "^7.25.0",
+ "tough-cookie": "^6.0.2",
+ "undici": "^8.9.0",
"w3c-xmlserializer": "^5.0.0",
"webidl-conversions": "^8.0.1",
"whatwg-mimetype": "^5.0.0",
- "whatwg-url": "^16.0.1",
+ "whatwg-url": "^17.1.0",
"xml-name-validator": "^5.0.0"
},
"engines": {
- "node": "^20.19.0 || ^22.13.0 || >=24.0.0"
+ "node": "^22.22.2 || ^24.15.0 || >=26.0.0"
},
"peerDependencies": {
- "canvas": "^3.0.0"
+ "canvas": "^3.2.3"
},
"peerDependenciesMeta": {
"canvas": {
@@ -5165,6 +7706,21 @@
"node": "20 || >=22"
}
},
+ "node_modules/jsdom/node_modules/whatwg-url": {
+ "version": "17.1.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-17.1.0.tgz",
+ "integrity": "sha512-3GeworPmc2ZfEEHP7lEbUfBX/L75wdEsi0rLNhXcXxnoN5jyq0SL5gCy06SGW2cyTIZdTvWIDQNQoza++vKeaw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@exodus/bytes": "^1.15.1",
+ "tr46": "^6.0.0",
+ "webidl-conversions": "^8.0.1"
+ },
+ "engines": {
+ "node": "^22.14.0 || >=24.0.0"
+ }
+ },
"node_modules/jsesc": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
@@ -5190,6 +7746,13 @@
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
"license": "MIT"
},
+ "node_modules/json-rpc-2.0": {
+ "version": "1.7.1",
+ "resolved": "https://registry.npmjs.org/json-rpc-2.0/-/json-rpc-2.0-1.7.1.tgz",
+ "integrity": "sha512-JqZjhjAanbpkXIzFE7u8mE/iFblawwlXtONaCvRqI+pyABVz7B4M1EUNpyVW+dZjqgQ2L5HFmZCmOCgUKm00hg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@@ -5237,9 +7800,9 @@
}
},
"node_modules/knip": {
- "version": "6.22.0",
- "resolved": "https://registry.npmjs.org/knip/-/knip-6.22.0.tgz",
- "integrity": "sha512-HTqtalzPNERFiyTj/3pERGss8oBzgxL9ZWiEQ29N9/+cTMZ1guIIEtcczrYpaoE76//XSHO11uuc66O9feY8wA==",
+ "version": "6.29.0",
+ "resolved": "https://registry.npmjs.org/knip/-/knip-6.29.0.tgz",
+ "integrity": "sha512-A3kXqSBky1tWBAqiU9srdtu0Larhzkuyor0aD/gg+ToiqyBncCCs2Q60sLsxmcKhV0OsKss9LV0hMPpwLv711Q==",
"dev": true,
"funding": [
{
@@ -5257,15 +7820,15 @@
"formatly": "^0.3.0",
"get-tsconfig": "4.14.0",
"jiti": "^2.7.0",
- "oxc-parser": "^0.137.0",
- "oxc-resolver": "11.21.3",
- "picomatch": "^4.0.4",
- "smol-toml": "^1.6.1",
+ "oxc-parser": "^0.140.0",
+ "oxc-resolver": "11.24.2",
+ "picomatch": "^4.0.5",
+ "smol-toml": "^1.7.0",
"strip-json-comments": "5.0.3",
"tinyglobby": "^0.2.17",
- "unbash": "^4.0.1",
+ "unbash": "^4.0.3",
"yaml": "^2.9.0",
- "zod": "^4.1.11"
+ "zod": "^4.4.3"
},
"bin": {
"knip": "bin/knip.js",
@@ -5275,6 +7838,13 @@
"node": "^20.19.0 || >=22.12.0"
}
},
+ "node_modules/legacy-javascript": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/legacy-javascript/-/legacy-javascript-0.0.1.tgz",
+ "integrity": "sha512-lPyntS4/aS7jpuvOlitZDFifBCb4W8L/3QU0PLbUTUj+zYah8rfVjYic88yG7ZKTxhS5h9iz7duT8oUXKszLhg==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
"node_modules/levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@@ -5289,10 +7859,71 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/lighthouse": {
+ "version": "13.4.1",
+ "resolved": "https://registry.npmjs.org/lighthouse/-/lighthouse-13.4.1.tgz",
+ "integrity": "sha512-fDu8lt3QLK/lTqIxtp1HkzQNJ32rsFHhbadYOepcMZFLgA8oINhxutMbMv8XXnpTOvZ0TXCo4JCk1LDTWaRLnA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@paulirish/trace_engine": "0.0.65",
+ "@sentry/node": "^10.0.0",
+ "axe-core": "^4.12.1",
+ "chrome-launcher": "^1.2.1",
+ "configstore": "^7.0.0",
+ "csp_evaluator": "1.1.8",
+ "devtools-protocol": "0.0.1663043",
+ "enquirer": "^2.3.6",
+ "http-link-header": "^1.1.1",
+ "intl-messageformat": "^10.5.3",
+ "jpeg-js": "^0.4.4",
+ "js-library-detector": "^6.7.0",
+ "lighthouse-logger": "^2.0.2",
+ "lighthouse-stack-packs": "1.12.3",
+ "lodash-es": "^4.17.21",
+ "lookup-closest-locale": "6.2.0",
+ "open": "^8.4.0",
+ "puppeteer-core": "^25.3.0",
+ "robots-parser": "^3.0.1",
+ "speedline-core": "^1.4.3",
+ "third-party-web": "^0.29.2",
+ "tldts-icann": "^7.4.9",
+ "web-features": "^3.34.0",
+ "ws": "^7.0.0",
+ "yargs": "^17.3.1",
+ "yargs-parser": "^21.0.0"
+ },
+ "bin": {
+ "chrome-debug": "core/scripts/manual-chrome-launcher.js",
+ "lighthouse": "cli/index.js",
+ "smokehouse": "cli/test/smokehouse/frontends/smokehouse-bin.js"
+ },
+ "engines": {
+ "node": ">=22.19"
+ }
+ },
+ "node_modules/lighthouse-logger": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.2.tgz",
+ "integrity": "sha512-vWl2+u5jgOQuZR55Z1WM0XDdrJT6mzMP8zHUct7xTlWhuQs+eV0g+QL0RQdFjT54zVmbhLCP8vIVpy1wGn/gCg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "debug": "^4.4.1",
+ "marky": "^1.2.2"
+ }
+ },
+ "node_modules/lighthouse-stack-packs": {
+ "version": "1.12.3",
+ "resolved": "https://registry.npmjs.org/lighthouse-stack-packs/-/lighthouse-stack-packs-1.12.3.tgz",
+ "integrity": "sha512-d8IsOpE83kbANgnM+Tp8+x6HcMpX9o2ITBiUERssgzAIFdZCQzs/f4k6D0DLQTE59enml9mbAOU52Wu35exWtg==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
"node_modules/lightningcss": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
- "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
+ "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": {
@@ -5306,23 +7937,23 @@
"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"
+ "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/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==",
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz",
+ "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==",
"cpu": [
"arm64"
],
@@ -5341,9 +7972,9 @@
}
},
"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==",
+ "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"
],
@@ -5362,9 +7993,9 @@
}
},
"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==",
+ "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"
],
@@ -5383,9 +8014,9 @@
}
},
"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==",
+ "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"
],
@@ -5404,9 +8035,9 @@
}
},
"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==",
+ "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"
],
@@ -5425,9 +8056,9 @@
}
},
"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==",
+ "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"
],
@@ -5446,9 +8077,9 @@
}
},
"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==",
+ "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"
],
@@ -5467,9 +8098,9 @@
}
},
"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==",
+ "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"
],
@@ -5488,9 +8119,9 @@
}
},
"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==",
+ "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"
],
@@ -5509,9 +8140,9 @@
}
},
"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==",
+ "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"
],
@@ -5530,9 +8161,9 @@
}
},
"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==",
+ "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"
],
@@ -5578,6 +8209,27 @@
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
"license": "MIT"
},
+ "node_modules/lodash-es": {
+ "version": "4.18.1",
+ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz",
+ "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lodash.groupby": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz",
+ "integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lookup-closest-locale": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/lookup-closest-locale/-/lookup-closest-locale-6.2.0.tgz",
+ "integrity": "sha512-/c2kL+Vnp1jnV6K6RpDTHK3dgg0Tu2VVp+elEiJpjfS1UyY7AjOYHohRug6wT0OpoX2qFgNORndE9RqesfVxWQ==",
+ "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",
@@ -5649,6 +8301,13 @@
"node": ">=10"
}
},
+ "node_modules/marky": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/marky/-/marky-1.3.0.tgz",
+ "integrity": "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -5665,6 +8324,43 @@
"dev": true,
"license": "CC0-1.0"
},
+ "node_modules/media-typer": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz",
+ "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/meriyah": {
+ "version": "6.1.4",
+ "resolved": "https://registry.npmjs.org/meriyah/-/meriyah-6.1.4.tgz",
+ "integrity": "sha512-Sz8FzjzI0kN13GK/6MVEsVzMZEPvOhnmmI1lU5+/1cGOiK3QUahntrNNtdVeihrO7t9JpoH75iMNXg6R6uWflQ==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
@@ -5696,6 +8392,13 @@
"node": ">=4"
}
},
+ "node_modules/minimalistic-assert": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
+ "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/minimatch": {
"version": "10.2.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
@@ -5712,6 +8415,40 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/mitt": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
+ "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/modern-tar": {
+ "version": "0.7.7",
+ "resolved": "https://registry.npmjs.org/modern-tar/-/modern-tar-0.7.7.tgz",
+ "integrity": "sha512-t9VmxaqrmANnEOBhpSDI6HD192Ge48k8vmWqQQL7hSFEqHEYwZbbsu49+aKLWZeRvFs3j1pMhXOqqF4kPlvjkQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/module-details-from-path": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz",
+ "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -5763,6 +8500,43 @@
}
}
},
+ "node_modules/mutation-server-protocol": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/mutation-server-protocol/-/mutation-server-protocol-0.4.1.tgz",
+ "integrity": "sha512-SBGK0j8hLDne7bktgThKI8kGvGTx3rY3LAeQTmOKZ5bVnL/7TorLMvcVF7dIPJCu5RNUWhkkuF53kurygYVt3g==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "zod": "^4.1.12"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/mutation-testing-elements": {
+ "version": "3.7.3",
+ "resolved": "https://registry.npmjs.org/mutation-testing-elements/-/mutation-testing-elements-3.7.3.tgz",
+ "integrity": "sha512-SMeIPxngJpfjfNYctFpYQQtlBlZaVO0aoB3FKdwrI8Ee/2bkyUuCZzAOCLv1U9fnmfA37dPFq0Owduoxs2XgGQ==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/mutation-testing-metrics": {
+ "version": "3.7.3",
+ "resolved": "https://registry.npmjs.org/mutation-testing-metrics/-/mutation-testing-metrics-3.7.3.tgz",
+ "integrity": "sha512-B8QrP0ZomErzTPNlhrzKWPNBln+3afwBZPHv0Q7N8wZZTYxMptzb/Gdm3ExXVmioVYrtZAtsDs7W/T/b2AixOQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "mutation-testing-report-schema": "3.7.3"
+ }
+ },
+ "node_modules/mutation-testing-report-schema": {
+ "version": "3.7.3",
+ "resolved": "https://registry.npmjs.org/mutation-testing-report-schema/-/mutation-testing-report-schema-3.7.3.tgz",
+ "integrity": "sha512-BHm3MYq+ckO+t5CtlG8zpqxc75rdJCkxVlE+fGuGJM3F7tNCQ/OW2N+TQVHN3BHsYa84+BFc6g3AwDYkUsw2MA==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
"node_modules/mute-stream": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-3.0.0.tgz",
@@ -5774,9 +8548,9 @@
}
},
"node_modules/nanoid": {
- "version": "3.3.15",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz",
- "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==",
+ "version": "3.3.16",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
+ "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
"dev": true,
"funding": [
{
@@ -5799,6 +8573,28 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/negotiator": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/node-gyp-build": {
+ "version": "4.8.4",
+ "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
+ "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "node-gyp-build": "bin.js",
+ "node-gyp-build-optional": "optional.js",
+ "node-gyp-build-test": "build-test.js"
+ }
+ },
"node_modules/node-releases": {
"version": "2.0.50",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.50.tgz",
@@ -5809,6 +8605,49 @@
"node": ">=18"
}
},
+ "node_modules/npm-run-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz",
+ "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^4.0.0",
+ "unicorn-magic": "^0.3.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/npm-run-path/node_modules/path-key": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
+ "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/obug": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz",
@@ -5823,15 +8662,66 @@
"node": ">=12.20.0"
}
},
- "node_modules/optionator": {
- "version": "0.9.4",
- "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
- "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "node_modules/on-exit-leak-free": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz",
+ "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "deep-is": "^0.1.3",
- "fast-levenshtein": "^2.0.6",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "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==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "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",
@@ -5849,13 +8739,13 @@
"license": "MIT"
},
"node_modules/oxc-parser": {
- "version": "0.137.0",
- "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.137.0.tgz",
- "integrity": "sha512-yFImD+WLElJpLKy8llG1qe4DCmMsL18peRp8XP1JKfig/gISbJkglnpDtX2aTmAn10kZF7164HbN2H8QPsXxGg==",
+ "version": "0.140.0",
+ "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.140.0.tgz",
+ "integrity": "sha512-h6QFWd6lBMfjESqgQ27GjzrSDb0qbznp7VDQqp2zvgsrWut4vcchyMIzOVXvGQ2GMZgKw9RWrFNWv9WqGL0p7Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@oxc-project/types": "^0.137.0"
+ "@oxc-project/types": "^0.140.0"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
@@ -5864,57 +8754,57 @@
"url": "https://github.com/sponsors/Boshen"
},
"optionalDependencies": {
- "@oxc-parser/binding-android-arm-eabi": "0.137.0",
- "@oxc-parser/binding-android-arm64": "0.137.0",
- "@oxc-parser/binding-darwin-arm64": "0.137.0",
- "@oxc-parser/binding-darwin-x64": "0.137.0",
- "@oxc-parser/binding-freebsd-x64": "0.137.0",
- "@oxc-parser/binding-linux-arm-gnueabihf": "0.137.0",
- "@oxc-parser/binding-linux-arm-musleabihf": "0.137.0",
- "@oxc-parser/binding-linux-arm64-gnu": "0.137.0",
- "@oxc-parser/binding-linux-arm64-musl": "0.137.0",
- "@oxc-parser/binding-linux-ppc64-gnu": "0.137.0",
- "@oxc-parser/binding-linux-riscv64-gnu": "0.137.0",
- "@oxc-parser/binding-linux-riscv64-musl": "0.137.0",
- "@oxc-parser/binding-linux-s390x-gnu": "0.137.0",
- "@oxc-parser/binding-linux-x64-gnu": "0.137.0",
- "@oxc-parser/binding-linux-x64-musl": "0.137.0",
- "@oxc-parser/binding-openharmony-arm64": "0.137.0",
- "@oxc-parser/binding-wasm32-wasi": "0.137.0",
- "@oxc-parser/binding-win32-arm64-msvc": "0.137.0",
- "@oxc-parser/binding-win32-ia32-msvc": "0.137.0",
- "@oxc-parser/binding-win32-x64-msvc": "0.137.0"
+ "@oxc-parser/binding-android-arm-eabi": "0.140.0",
+ "@oxc-parser/binding-android-arm64": "0.140.0",
+ "@oxc-parser/binding-darwin-arm64": "0.140.0",
+ "@oxc-parser/binding-darwin-x64": "0.140.0",
+ "@oxc-parser/binding-freebsd-x64": "0.140.0",
+ "@oxc-parser/binding-linux-arm-gnueabihf": "0.140.0",
+ "@oxc-parser/binding-linux-arm-musleabihf": "0.140.0",
+ "@oxc-parser/binding-linux-arm64-gnu": "0.140.0",
+ "@oxc-parser/binding-linux-arm64-musl": "0.140.0",
+ "@oxc-parser/binding-linux-ppc64-gnu": "0.140.0",
+ "@oxc-parser/binding-linux-riscv64-gnu": "0.140.0",
+ "@oxc-parser/binding-linux-riscv64-musl": "0.140.0",
+ "@oxc-parser/binding-linux-s390x-gnu": "0.140.0",
+ "@oxc-parser/binding-linux-x64-gnu": "0.140.0",
+ "@oxc-parser/binding-linux-x64-musl": "0.140.0",
+ "@oxc-parser/binding-openharmony-arm64": "0.140.0",
+ "@oxc-parser/binding-wasm32-wasi": "0.140.0",
+ "@oxc-parser/binding-win32-arm64-msvc": "0.140.0",
+ "@oxc-parser/binding-win32-ia32-msvc": "0.140.0",
+ "@oxc-parser/binding-win32-x64-msvc": "0.140.0"
}
},
"node_modules/oxc-resolver": {
- "version": "11.21.3",
- "resolved": "https://registry.npmjs.org/oxc-resolver/-/oxc-resolver-11.21.3.tgz",
- "integrity": "sha512-2Mx3fKQz7+xgrBONjsxOgCGtMHOn38/HxMzW1I5efwXB5a4lRN0Vp40gYUJFBWJslcrvwoofTrqoTnLbwTd3pA==",
+ "version": "11.24.2",
+ "resolved": "https://registry.npmjs.org/oxc-resolver/-/oxc-resolver-11.24.2.tgz",
+ "integrity": "sha512-FY91FiDBj7ls5MsFS9jN3tjz2o0/zsdSsymlakySaBwVJZorHhkWyICLZMKxlu1R9vYo+sd3z1jwb4J8x7bNDw==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/Boshen"
},
"optionalDependencies": {
- "@oxc-resolver/binding-android-arm-eabi": "11.21.3",
- "@oxc-resolver/binding-android-arm64": "11.21.3",
- "@oxc-resolver/binding-darwin-arm64": "11.21.3",
- "@oxc-resolver/binding-darwin-x64": "11.21.3",
- "@oxc-resolver/binding-freebsd-x64": "11.21.3",
- "@oxc-resolver/binding-linux-arm-gnueabihf": "11.21.3",
- "@oxc-resolver/binding-linux-arm-musleabihf": "11.21.3",
- "@oxc-resolver/binding-linux-arm64-gnu": "11.21.3",
- "@oxc-resolver/binding-linux-arm64-musl": "11.21.3",
- "@oxc-resolver/binding-linux-ppc64-gnu": "11.21.3",
- "@oxc-resolver/binding-linux-riscv64-gnu": "11.21.3",
- "@oxc-resolver/binding-linux-riscv64-musl": "11.21.3",
- "@oxc-resolver/binding-linux-s390x-gnu": "11.21.3",
- "@oxc-resolver/binding-linux-x64-gnu": "11.21.3",
- "@oxc-resolver/binding-linux-x64-musl": "11.21.3",
- "@oxc-resolver/binding-openharmony-arm64": "11.21.3",
- "@oxc-resolver/binding-wasm32-wasi": "11.21.3",
- "@oxc-resolver/binding-win32-arm64-msvc": "11.21.3",
- "@oxc-resolver/binding-win32-x64-msvc": "11.21.3"
+ "@oxc-resolver/binding-android-arm-eabi": "11.24.2",
+ "@oxc-resolver/binding-android-arm64": "11.24.2",
+ "@oxc-resolver/binding-darwin-arm64": "11.24.2",
+ "@oxc-resolver/binding-darwin-x64": "11.24.2",
+ "@oxc-resolver/binding-freebsd-x64": "11.24.2",
+ "@oxc-resolver/binding-linux-arm-gnueabihf": "11.24.2",
+ "@oxc-resolver/binding-linux-arm-musleabihf": "11.24.2",
+ "@oxc-resolver/binding-linux-arm64-gnu": "11.24.2",
+ "@oxc-resolver/binding-linux-arm64-musl": "11.24.2",
+ "@oxc-resolver/binding-linux-ppc64-gnu": "11.24.2",
+ "@oxc-resolver/binding-linux-riscv64-gnu": "11.24.2",
+ "@oxc-resolver/binding-linux-riscv64-musl": "11.24.2",
+ "@oxc-resolver/binding-linux-s390x-gnu": "11.24.2",
+ "@oxc-resolver/binding-linux-x64-gnu": "11.24.2",
+ "@oxc-resolver/binding-linux-x64-musl": "11.24.2",
+ "@oxc-resolver/binding-openharmony-arm64": "11.24.2",
+ "@oxc-resolver/binding-wasm32-wasi": "11.24.2",
+ "@oxc-resolver/binding-win32-arm64-msvc": "11.24.2",
+ "@oxc-resolver/binding-win32-x64-msvc": "11.24.2"
}
},
"node_modules/p-limit": {
@@ -5979,6 +8869,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/parse-ms": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz",
+ "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "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",
@@ -5992,6 +8895,16 @@
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -6048,9 +8961,9 @@
"license": "ISC"
},
"node_modules/picomatch": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
- "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "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": {
@@ -6060,6 +8973,71 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/pino": {
+ "version": "10.3.1",
+ "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz",
+ "integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@pinojs/redact": "^0.4.0",
+ "atomic-sleep": "^1.0.0",
+ "on-exit-leak-free": "^2.1.0",
+ "pino-abstract-transport": "^3.0.0",
+ "pino-std-serializers": "^7.0.0",
+ "process-warning": "^5.0.0",
+ "quick-format-unescaped": "^4.0.3",
+ "real-require": "^0.2.0",
+ "safe-stable-stringify": "^2.3.1",
+ "sonic-boom": "^4.0.1",
+ "thread-stream": "^4.0.0"
+ },
+ "bin": {
+ "pino": "bin.js"
+ }
+ },
+ "node_modules/pino-abstract-transport": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz",
+ "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "split2": "^4.0.0"
+ }
+ },
+ "node_modules/pino-pretty": {
+ "version": "13.1.3",
+ "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-13.1.3.tgz",
+ "integrity": "sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "colorette": "^2.0.7",
+ "dateformat": "^4.6.3",
+ "fast-copy": "^4.0.0",
+ "fast-safe-stringify": "^2.1.1",
+ "help-me": "^5.0.0",
+ "joycon": "^3.1.1",
+ "minimist": "^1.2.6",
+ "on-exit-leak-free": "^2.1.0",
+ "pino-abstract-transport": "^3.0.0",
+ "pump": "^3.0.0",
+ "secure-json-parse": "^4.0.0",
+ "sonic-boom": "^4.0.1",
+ "strip-json-comments": "^5.0.2"
+ },
+ "bin": {
+ "pino-pretty": "bin.js"
+ }
+ },
+ "node_modules/pino-std-serializers": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz",
+ "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/playwright": {
"version": "1.62.0",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.62.0.tgz",
@@ -6092,25 +9070,10 @@
"node": ">=20"
}
},
- "node_modules/playwright/node_modules/fsevents": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
- "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
"node_modules/postcss": {
- "version": "8.5.15",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
- "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
+ "version": "8.5.25",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz",
+ "integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==",
"dev": true,
"funding": [
{
@@ -6128,7 +9091,7 @@
],
"license": "MIT",
"dependencies": {
- "nanoid": "^3.3.12",
+ "nanoid": "^3.3.16",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
@@ -6184,6 +9147,63 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/pretty-ms": {
+ "version": "9.3.0",
+ "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz",
+ "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parse-ms": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/process-warning": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.1.0.tgz",
+ "integrity": "sha512-jQSaVHsPgtyw60e1rQ/A+/ArPEj/S8pS/vFnyGa/gYFXrKk/6RuDkoqVDQ5NI5MmS01698ltlAk0NoDBNLujRw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/progress": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
+ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
"node_modules/proxy-from-env": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
@@ -6193,6 +9213,17 @@
"node": ">=10"
}
},
+ "node_modules/pump": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
+ "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -6203,41 +9234,167 @@
"node": ">=6"
}
},
+ "node_modules/puppeteer-core": {
+ "version": "25.4.0",
+ "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-25.4.0.tgz",
+ "integrity": "sha512-K1plkLOdeoUnGeT1OvdqF3qxl33v+Ra/uH5VyPEhXdMcpvGiEskHzxxEU3fgpccJpJLIipB/rPUsvkZRWeKqOA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@puppeteer/browsers": "3.0.6",
+ "chromium-bidi": "17.0.2",
+ "devtools-protocol": "0.0.1653615",
+ "typed-query-selector": "^2.12.2",
+ "webdriver-bidi-protocol": "0.4.2",
+ "ws": "^8.21.1"
+ },
+ "engines": {
+ "node": ">=22.12.0"
+ }
+ },
+ "node_modules/puppeteer-core/node_modules/devtools-protocol": {
+ "version": "0.0.1653615",
+ "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1653615.tgz",
+ "integrity": "sha512-pGVkY3T/qXxAp2nFPodwYqOevk6ncNMSmvL8QfRCx5ZWGd6Vor7AFNmyaA8Zs6uJyP1QAfjuLandCgvSix1BNA==",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/puppeteer-core/node_modules/ws": {
+ "version": "8.21.1",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz",
+ "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.15.3",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz",
+ "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "es-define-property": "^1.0.1",
+ "side-channel": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/quick-format-unescaped": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
+ "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/ramda": {
+ "version": "0.32.0",
+ "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.32.0.tgz",
+ "integrity": "sha512-GQWAHhxhxWBWA8oIBr1XahFVjQ9Fic6MK9ikijfd4TZHfE2+urfk+irVlR5VOn48uwMgM+loRRBJd6Yjsbc0zQ==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/ramda"
+ }
+ },
+ "node_modules/randexp": {
+ "version": "0.5.3",
+ "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.5.3.tgz",
+ "integrity": "sha512-U+5l2KrcMNOUPYvazA3h5ekF80FHTUG+87SEAmHZmolh1M+i/WyTCxVzmi+tidIa1tM4BSe8g2Y/D3loWDjj+w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "drange": "^1.0.2",
+ "ret": "^0.2.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz",
+ "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
+ "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.7.0",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
"node_modules/react": {
- "version": "19.2.7",
- "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz",
- "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==",
+ "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.7",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz",
- "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==",
+ "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.7"
+ "react": "^19.2.8"
}
},
"node_modules/react-i18next": {
- "version": "17.0.8",
- "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-17.0.8.tgz",
- "integrity": "sha512-0ooKbGLU8JXhe1zwpQUWIeXSgLPOfwJmgheWRIUpcoA0CpyabpGhayjdG+/eA5esC1AQ8h2jWpXjJfzQzeDOCw==",
+ "version": "17.0.11",
+ "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-17.0.11.tgz",
+ "integrity": "sha512-cDtkXgxjuFTWUH6V+aQn1Ve5vDiUztCNPWW5GtSHDccsgRXO1nE6QFWCEmc1KAutrb3OUv87wFShJL5RhUwPXg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.29.2",
- "html-parse-stringify": "^3.0.1",
+ "html-parse-stringify": "^4.0.1",
"use-sync-external-store": "^1.6.0"
},
"peerDependencies": {
"i18next": ">= 26.2.0",
"react": ">= 16.8.0",
- "typescript": "^5 || ^6"
+ "typescript": "^5 || ^6 || ^7"
},
"peerDependenciesMeta": {
"react-dom": {
@@ -6252,26 +9409,25 @@
}
},
"node_modules/react-is": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
- "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "version": "19.2.8",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.8.tgz",
+ "integrity": "sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==",
"license": "MIT"
},
"node_modules/react-router": {
- "version": "7.18.0",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.0.tgz",
- "integrity": "sha512-pTTGt8J+ji1NOmYnjzT+bAJy/1zD+Jp4ziO6cL7T3ZLvXKtusO7BpFqlRXitqpcPVqllsIXFHRMt+2/k3Xn6HQ==",
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-8.3.0.tgz",
+ "integrity": "sha512-qyPMvW83jGIct3yiieisxdk9M745anqhpIMKN5m1t6yBMfgVPpt77aHOqs5fUlEJRMCGffg9BaQLH9oPVOL7xQ==",
"license": "MIT",
"dependencies": {
- "cookie": "^1.0.1",
- "set-cookie-parser": "^2.6.0"
+ "cookie-es": "^3.1.1"
},
"engines": {
- "node": ">=20.0.0"
+ "node": ">=22.22.0"
},
"peerDependencies": {
- "react": ">=18",
- "react-dom": ">=18"
+ "react": ">=19.2.7",
+ "react-dom": ">=19.2.7"
},
"peerDependenciesMeta": {
"react-dom": {
@@ -6279,20 +9435,14 @@
}
}
},
- "node_modules/react-router-dom": {
- "version": "7.18.0",
- "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.0.tgz",
- "integrity": "sha512-Fi0yY6kgtKae/Th2xibdWK0KSdYZ4B53Gyf6wRtomOKWgpNm7H7+DyfDhncdz9FKbpS+1jmDhg3F4WoGJ+yFOA==",
+ "node_modules/real-require": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz",
+ "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==",
+ "dev": true,
"license": "MIT",
- "dependencies": {
- "react-router": "7.18.0"
- },
"engines": {
- "node": ">=20.0.0"
- },
- "peerDependencies": {
- "react": ">=18",
- "react-dom": ">=18"
+ "node": ">= 12.13.0"
}
},
"node_modules/redent": {
@@ -6329,6 +9479,27 @@
"node": ">=0.10.0"
}
},
+ "node_modules/require-in-the-middle": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-8.0.1.tgz",
+ "integrity": "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.3.5",
+ "module-details-from-path": "^1.0.3"
+ },
+ "engines": {
+ "node": ">=9.3.0 || >=8.10.0 <9.0.0"
+ }
+ },
+ "node_modules/requires-port": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
+ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/resolve": {
"version": "1.22.12",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz",
@@ -6369,6 +9540,16 @@
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
}
},
+ "node_modules/ret": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz",
+ "integrity": "sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/rettime": {
"version": "0.11.11",
"resolved": "https://registry.npmjs.org/rettime/-/rettime-0.11.11.tgz",
@@ -6376,14 +9557,24 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/robots-parser": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/robots-parser/-/robots-parser-3.0.1.tgz",
+ "integrity": "sha512-s+pyvQeIKIZ0dx5iJiQk1tPLJAWln39+MI5jtM8wnyws+G5azk+dMnMX0qfbqNetKKNgcWWOdi0sfm+FbQbgdQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
"node_modules/rolldown": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.3.tgz",
- "integrity": "sha512-1F1eEtUBtFvcGm1HQ9TiUIUHPQG7mSAODrhIzjxoUEFuo8OcbrGLiVLkevNgj84TE4lnHvnumwFjhJO5Eu135g==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.1.tgz",
+ "integrity": "sha512-4FKJhg8d3OiyQOA6Q1Q0hoFFpW9/OoX+VsHzpECsdsIZoOArrAK90gl59YK/Z+gnDel45bgJZK03ozH/9bCqEw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@oxc-project/types": "=0.137.0",
+ "@oxc-project/types": "=0.142.0",
"@rolldown/pluginutils": "^1.0.0"
},
"bin": {
@@ -6393,23 +9584,88 @@
"node": "^20.19.0 || >=22.12.0"
},
"optionalDependencies": {
- "@rolldown/binding-android-arm64": "1.1.3",
- "@rolldown/binding-darwin-arm64": "1.1.3",
- "@rolldown/binding-darwin-x64": "1.1.3",
- "@rolldown/binding-freebsd-x64": "1.1.3",
- "@rolldown/binding-linux-arm-gnueabihf": "1.1.3",
- "@rolldown/binding-linux-arm64-gnu": "1.1.3",
- "@rolldown/binding-linux-arm64-musl": "1.1.3",
- "@rolldown/binding-linux-ppc64-gnu": "1.1.3",
- "@rolldown/binding-linux-s390x-gnu": "1.1.3",
- "@rolldown/binding-linux-x64-gnu": "1.1.3",
- "@rolldown/binding-linux-x64-musl": "1.1.3",
- "@rolldown/binding-openharmony-arm64": "1.1.3",
- "@rolldown/binding-wasm32-wasi": "1.1.3",
- "@rolldown/binding-win32-arm64-msvc": "1.1.3",
- "@rolldown/binding-win32-x64-msvc": "1.1.3"
+ "@rolldown/binding-android-arm64": "1.2.1",
+ "@rolldown/binding-darwin-arm64": "1.2.1",
+ "@rolldown/binding-darwin-x64": "1.2.1",
+ "@rolldown/binding-freebsd-x64": "1.2.1",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.2.1",
+ "@rolldown/binding-linux-arm64-gnu": "1.2.1",
+ "@rolldown/binding-linux-arm64-musl": "1.2.1",
+ "@rolldown/binding-linux-ppc64-gnu": "1.2.1",
+ "@rolldown/binding-linux-s390x-gnu": "1.2.1",
+ "@rolldown/binding-linux-x64-gnu": "1.2.1",
+ "@rolldown/binding-linux-x64-musl": "1.2.1",
+ "@rolldown/binding-openharmony-arm64": "1.2.1",
+ "@rolldown/binding-wasm32-wasi": "1.2.1",
+ "@rolldown/binding-win32-arm64-msvc": "1.2.1",
+ "@rolldown/binding-win32-x64-msvc": "1.2.1"
+ }
+ },
+ "node_modules/rolldown/node_modules/@oxc-project/types": {
+ "version": "0.142.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.142.0.tgz",
+ "integrity": "sha512-7W+2q5AKQVU36fkaryontrHn3YDt1RyUYXatw9i5H8ocYe2sPKSFB6eS8WNPeRKiN1qAWWZUPm7gwFzJGrccqQ==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/Boshen"
}
},
+ "node_modules/router": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
+ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "is-promise": "^4.0.0",
+ "parseurl": "^1.3.3",
+ "path-to-regexp": "^8.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/router/node_modules/path-to-regexp": {
+ "version": "8.4.2",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
+ "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/rxjs": {
+ "version": "7.8.2",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
+ "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/safe-stable-stringify": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",
+ "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/saxes": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
@@ -6438,6 +9694,30 @@
"compute-scroll-into-view": "^3.0.2"
}
},
+ "node_modules/secure-json-parse": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz",
+ "integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/semifies": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/semifies/-/semifies-1.0.0.tgz",
+ "integrity": "sha512-xXR3KGeoxTNWPD4aBvL5NUpMTT7WMANr3EWnaS190QVkY52lqqcVRD7Q05UVbBhiWDGWMlJEUam9m7uFFGVScw==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
"node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
@@ -6448,11 +9728,86 @@
"semver": "bin/semver.js"
}
},
- "node_modules/set-cookie-parser": {
- "version": "2.7.2",
- "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
- "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
- "license": "MIT"
+ "node_modules/send": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
+ "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.3",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.1",
+ "mime-types": "^3.0.2",
+ "ms": "^2.1.3",
+ "on-finished": "^2.4.1",
+ "range-parser": "^1.2.1",
+ "statuses": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/send/node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/send/node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/serve-static": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
+ "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "parseurl": "^1.3.3",
+ "send": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "dev": true,
+ "license": "ISC"
},
"node_modules/shebang-command": {
"version": "2.0.0",
@@ -6467,14 +9822,90 @@
"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==",
+ "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/side-channel": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
+ "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.4",
+ "side-channel-list": "^1.0.1",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
+ "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
"dev": true,
"license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
"engines": {
- "node": ">=8"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/siginfo": {
@@ -6510,6 +9941,16 @@
"url": "https://github.com/sponsors/cyyynthia"
}
},
+ "node_modules/sonic-boom": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz",
+ "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "atomic-sleep": "^1.0.0"
+ }
+ },
"node_modules/source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
@@ -6529,6 +9970,54 @@
"node": ">=0.10.0"
}
},
+ "node_modules/speedline-core": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/speedline-core/-/speedline-core-1.4.3.tgz",
+ "integrity": "sha512-DI7/OuAUD+GMpR6dmu8lliO2Wg5zfeh+/xsdyJZCzd8o5JgFUjCeLsBDuZjIQJdwXS3J0L/uZYrELKYqx+PXog==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "image-ssim": "^0.2.0",
+ "jpeg-js": "^0.4.1"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/split2": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
+ "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">= 10.x"
+ }
+ },
+ "node_modules/stack-utils": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
+ "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "escape-string-regexp": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/stack-utils/node_modules/escape-string-regexp": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
+ "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/stackback": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
@@ -6594,6 +10083,19 @@
"node": ">=8"
}
},
+ "node_modules/strip-final-newline": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz",
+ "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/strip-indent": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
@@ -6620,6 +10122,23 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/stubborn-fs": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/stubborn-fs/-/stubborn-fs-2.0.0.tgz",
+ "integrity": "sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "stubborn-utils": "^1.0.1"
+ }
+ },
+ "node_modules/stubborn-utils": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/stubborn-utils/-/stubborn-utils-1.0.2.tgz",
+ "integrity": "sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/stylis": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.4.0.tgz",
@@ -6671,6 +10190,33 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/third-party-web": {
+ "version": "0.29.2",
+ "resolved": "https://registry.npmjs.org/third-party-web/-/third-party-web-0.29.2.tgz",
+ "integrity": "sha512-fegtha91tq2DHphyoiBXVHjVi2YG9zFaRnboT9C28tO1en9Y3wJsfspuy40F+u5wl3hHVbw7cnd1b67kEGHb8g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/thread-stream": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.2.0.tgz",
+ "integrity": "sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "real-require": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/thread-stream/node_modules/real-require": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/real-require/-/real-require-1.0.0.tgz",
+ "integrity": "sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/throttle-debounce": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz",
@@ -6744,6 +10290,26 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/tldts-icann": {
+ "version": "7.4.9",
+ "resolved": "https://registry.npmjs.org/tldts-icann/-/tldts-icann-7.4.9.tgz",
+ "integrity": "sha512-q6gyxCkcFPu5OZd2hi2quysxCG3ejIi53EACesbCEZHhH7FO3HnliqwO5zUs0TV6dA54SxhCrTGAc4ugl7rTiw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tldts-core": "^7.4.9"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
"node_modules/tough-cookie": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz",
@@ -6770,6 +10336,16 @@
"node": ">=20"
}
},
+ "node_modules/tree-kill": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
+ "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "tree-kill": "cli.js"
+ }
+ },
"node_modules/ts-api-utils": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz",
@@ -6788,8 +10364,17 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"dev": true,
- "license": "0BSD",
- "optional": true
+ "license": "0BSD"
+ },
+ "node_modules/tunnel": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
+ "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6.11 <=0.7.0 || >=0.7.3"
+ }
},
"node_modules/type-check": {
"version": "0.4.0",
@@ -6820,6 +10405,86 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/type-is": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz",
+ "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "content-type": "^2.0.0",
+ "media-typer": "^1.1.0",
+ "mime-types": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/type-is/node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/type-is/node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/typed-inject": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/typed-inject/-/typed-inject-5.0.0.tgz",
+ "integrity": "sha512-0Ql2ORqBORLMdAW89TQKZsb1PQkFGImFfVmncXWe7a+AA3+7dh7Se9exxZowH4kbnlvKEFkMxUYdHUpjYWFJaA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/typed-query-selector": {
+ "version": "2.12.2",
+ "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.2.tgz",
+ "integrity": "sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/typed-rest-client": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-2.3.1.tgz",
+ "integrity": "sha512-k4kX5Up6qA68D0Cby2AK+6+vM5k3qTxe+/3FqhnHRExjY5cfbOnzjQZbP/LXleF8hVoDvDqxlgk9KK83HoBZlQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "des.js": "^1.1.0",
+ "js-md4": "^0.3.2",
+ "qs": "6.15.1",
+ "tunnel": "0.0.6",
+ "underscore": "^1.13.8"
+ },
+ "engines": {
+ "node": ">= 16.0.0"
+ }
+ },
"node_modules/typescript": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
@@ -6835,16 +10500,16 @@
}
},
"node_modules/typescript-eslint": {
- "version": "8.62.0",
- "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.62.0.tgz",
- "integrity": "sha512-8QxXi+ZACKX0kaqO4gY8kn0RSD9gFfaHDWwjqtEN48aWCBkX4MJaufWN+c3BzlrXLOxfywDL8CaoqUwcRq4j4Q==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.65.0.tgz",
+ "integrity": "sha512-/ggrHAwyjENDusvyxbuqxAC2dTnZg/Z8F+fgQtYIz+L6n/9HfSlEZcFGV/NsMNa6CkGk0xUjUAFwC0vHOflvIA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/eslint-plugin": "8.62.0",
- "@typescript-eslint/parser": "8.62.0",
- "@typescript-eslint/typescript-estree": "8.62.0",
- "@typescript-eslint/utils": "8.62.0"
+ "@typescript-eslint/eslint-plugin": "8.65.0",
+ "@typescript-eslint/parser": "8.65.0",
+ "@typescript-eslint/typescript-estree": "8.65.0",
+ "@typescript-eslint/utils": "8.65.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -6859,32 +10524,62 @@
}
},
"node_modules/unbash": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/unbash/-/unbash-4.0.1.tgz",
- "integrity": "sha512-1ajSo3813sDoVIHx4inJdUS4l5L2ic5cFiddemPiyjb/PZEoBAhFwHtbaEdRDFxbAKy7FCG7s5ww3/uCFawuIA==",
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/unbash/-/unbash-4.0.4.tgz",
+ "integrity": "sha512-60m9IVGbavD6jholbxt0jVBXZkEB/HsMZq7Tyaghseve2/Sf0zQRAIfWsD34sde+DKP2tBxJS2wP88ZM0D1FhA==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=14"
}
},
+ "node_modules/underscore": {
+ "version": "1.13.8",
+ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz",
+ "integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/undici": {
- "version": "7.29.0",
- "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz",
- "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==",
+ "version": "8.9.0",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-8.9.0.tgz",
+ "integrity": "sha512-aWZpUj7XoGonMClx4gdDRfgBjqeA+F473aDmROQQbM9n6PRfK/u1q/a0X4wMTgcHfT8H6fpbt98PFuDUwFg2YA==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=20.18.1"
+ "node": ">=22.19.0"
}
},
"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==",
+ "version": "7.18.2",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
+ "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
"dev": true,
"license": "MIT"
},
+ "node_modules/unicorn-magic": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz",
+ "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/until-async": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/until-async/-/until-async-3.0.2.tgz",
@@ -6954,17 +10649,27 @@
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/vite": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.0.tgz",
- "integrity": "sha512-BuJcQK/56NQTWDGn4ABea3q4SSBdNPWwNZKTkkUpcMPnLoquSYH8llRtSUIgoL1KSCpHt5eghLShn50mH36y7Q==",
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.0.tgz",
+ "integrity": "sha512-pn+CFpM0lwDeKwmOq1ZaBK/9sjorZcgqxki6MbY/jPEVd9vichIlmlD4HmQ5wdP5EgqQCFRaACBxMC7uEGc6lQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "lightningcss": "^1.32.0",
- "picomatch": "^4.0.4",
- "postcss": "^8.5.15",
- "rolldown": "~1.1.2",
+ "lightningcss": "^1.33.0",
+ "picomatch": "^4.0.5",
+ "postcss": "^8.5.23",
+ "rolldown": "~1.2.0",
"tinyglobby": "^0.2.17"
},
"bin": {
@@ -6981,7 +10686,7 @@
},
"peerDependencies": {
"@types/node": "^20.19.0 || >=22.12.0",
- "@vitejs/devtools": "^0.3.0",
+ "@vitejs/devtools": "^0.4.0",
"esbuild": "^0.27.0 || ^0.28.0",
"jiti": ">=1.21.0",
"less": "^4.0.0",
@@ -7032,6 +10737,21 @@
}
}
},
+ "node_modules/vite/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/vitest": {
"version": "4.1.10",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz",
@@ -7122,15 +10842,6 @@
}
}
},
- "node_modules/void-elements": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
- "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/w3c-xmlserializer": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
@@ -7154,6 +10865,27 @@
"node": "20 || >=22"
}
},
+ "node_modules/weapon-regex": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/weapon-regex/-/weapon-regex-1.3.6.tgz",
+ "integrity": "sha512-wsf1m1jmMrso5nhwVFJJHSubEBf3+pereGd7+nBKtYJ18KoB/PWJOHS3WRkwS04VrOU0iJr2bZU+l1QaTJ+9nA==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/web-features": {
+ "version": "3.34.2",
+ "resolved": "https://registry.npmjs.org/web-features/-/web-features-3.34.2.tgz",
+ "integrity": "sha512-1/prthzNwl/ITxBgFuKUCJ1ezWxZDM8rhz/VIBCD6zMEv5STMDQxgf05IfYxmJvFBTTLONaTDUk7umPkhUHmpw==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/webdriver-bidi-protocol": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.4.2.tgz",
+ "integrity": "sha512-VSV+fzfChirL3e7jay2yUC7B4HQCGtEWEg/MSSQbK+qWbqeGlRLlXTzPpYr3XGUvbpDHumWZBJxgesg4N7dbtA==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
"node_modules/webidl-conversions": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz",
@@ -7189,6 +10921,13 @@
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
}
},
+ "node_modules/when-exit": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/when-exit/-/when-exit-2.1.5.tgz",
+ "integrity": "sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -7266,6 +11005,48 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
+ "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==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/ws": {
+ "version": "7.5.13",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.13.tgz",
+ "integrity": "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.3.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": "^5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/xdg-basedir": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz",
+ "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/xml-name-validator": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
@@ -7358,6 +11139,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/yoctocolors": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.2.0.tgz",
+ "integrity": "sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "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",
diff --git a/package.json b/package.json
index 272e478..58fd117 100644
--- a/package.json
+++ b/package.json
@@ -1,39 +1,58 @@
{
"name": "kernel",
"private": true,
- "version": "0.0.0",
+ "version": "1.0.0",
"type": "module",
+ "engines": {
+ "node": ">=24.15.0 <25"
+ },
+ "overrides": {
+ "qs": "^6.15.3"
+ },
"scripts": {
+ "audit": "npm audit --audit-level=high",
+ "bundle:size": "node scripts/check-bundle-size.mjs",
"dev": "vite --host",
- "build": "tsc -b && vite build",
+ "build": "node scripts/validate-production-env.mjs && tsc -b && vite build",
"format": "prettier . --write",
"format:check": "prettier . --check",
"lint": "eslint . --ext .ts,.tsx",
"lint:fix": "eslint . --ext .ts,.tsx --fix",
+ "lighthouse": "node scripts/run-lighthouse.mjs",
"knip": "knip",
+ "performance": "npm run build && npm run bundle:size && npm run lighthouse",
"preview": "vite preview",
"test": "vitest run",
+ "test:contract": "vitest run --config=vitest.contract.config.ts",
"test:coverage": "vitest run --coverage",
"test:e2e": "playwright test",
"test:e2e:report": "playwright show-report",
"test:e2e:ui": "playwright test --ui",
+ "test:e2e:update-snapshots": "playwright test --project=visual-chromium --update-snapshots",
+ "test:smoke": "playwright test --config=smoke.config.ts",
+ "test:staging": "playwright test --config=staging.config.ts",
+ "test:mutation": "stryker run",
"test:watch": "vitest",
"typecheck": "tsc --noEmit"
},
"dependencies": {
- "antd": "^6.5.0",
+ "antd": "^6.5.2",
"antd-style": "^4.1.0",
- "axios": "^1.18.1",
+ "axios": "^1.19.0",
"dayjs": "^1.11.21",
- "i18next": "^26.3.3",
+ "i18next": "^26.3.6",
"jalaliday": "^3.1.1",
"lodash": "^4.18.1",
- "react": "^19.2.7",
- "react-dom": "^19.2.7",
- "react-i18next": "^17.0.8",
- "react-router-dom": "^7.18.0"
+ "react": "^19.2.8",
+ "react-dom": "^19.2.8",
+ "react-i18next": "^17.0.11",
+ "react-router": "^8.3.0"
},
"devDependencies": {
+ "@axe-core/playwright": "4.12.1",
+ "@pact-foundation/pact": "^17.0.1",
+ "@stryker-mutator/core": "^9.6.1",
+ "@stryker-mutator/vitest-runner": "^9.6.1",
"@eslint/js": "^10.0.1",
"@playwright/test": "^1.62.0",
"@testing-library/dom": "^10.4.1",
@@ -41,25 +60,27 @@
"@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1",
"@types/lodash": "^4.17.24",
- "@types/node": "^26.0.1",
+ "@types/node": "^24.13.3",
"@types/react": "^19.2.17",
"@types/react-dom": "^19.2.3",
- "@typescript-eslint/types": "^8.62.0",
+ "@typescript-eslint/types": "^8.65.0",
"@vitejs/plugin-basic-ssl": "^2.3.0",
- "@vitejs/plugin-react": "^6.0.3",
+ "@vitejs/plugin-react": "^6.0.5",
"@vitest/coverage-v8": "^4.1.10",
- "eslint": "^10.6.0",
+ "chrome-launcher": "^1.2.1",
+ "eslint": "^10.8.0",
"eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-react-refresh": "^0.5.3",
- "eslint-plugin-simple-import-sort": "^13.0.0",
- "globals": "^17.7.0",
- "jsdom": "^29.1.1",
- "knip": "^6.22.0",
+ "eslint-plugin-simple-import-sort": "^14.0.0",
+ "globals": "^17.8.0",
+ "jsdom": "^30.0.1",
+ "knip": "^6.29.0",
+ "lighthouse": "^13.4.1",
"msw": "^2.15.0",
"prettier": "^3.9.6",
"typescript": "~6.0.3",
- "typescript-eslint": "^8.62.0",
- "vite": "^8.1.0",
+ "typescript-eslint": "^8.65.0",
+ "vite": "^8.2.0",
"vitest": "^4.1.10"
}
}
diff --git a/playwright.config.ts b/playwright.config.ts
index 5a76495..7fc754a 100644
--- a/playwright.config.ts
+++ b/playwright.config.ts
@@ -4,6 +4,11 @@ const baseURL = "https://127.0.0.1:4173";
export default defineConfig({
expect: {
+ toHaveScreenshot: {
+ animations: "disabled",
+ caret: "hide",
+ maxDiffPixelRatio: 0.01,
+ },
timeout: 5_000,
},
forbidOnly: !!process.env.CI,
@@ -12,6 +17,33 @@ export default defineConfig({
projects: [
{
name: "chromium",
+ testIgnore: /visual\.spec\.ts/,
+ use: {
+ ...devices["Desktop Chrome"],
+ ...(process.env.CI ? {} : { channel: "chrome" as const }),
+ },
+ },
+ {
+ name: "firefox",
+ testIgnore: /visual\.spec\.ts/,
+ use: devices["Desktop Firefox"],
+ },
+ {
+ name: "webkit",
+ testIgnore: /visual\.spec\.ts/,
+ use: devices["Desktop Safari"],
+ },
+ {
+ name: "mobile-chromium",
+ testIgnore: /visual\.spec\.ts/,
+ use: {
+ ...devices["Pixel 7"],
+ ...(process.env.CI ? {} : { channel: "chrome" as const }),
+ },
+ },
+ {
+ name: "visual-chromium",
+ testMatch: /visual\.spec\.ts/,
use: {
...devices["Desktop Chrome"],
...(process.env.CI ? {} : { channel: "chrome" as const }),
@@ -22,6 +54,8 @@ export default defineConfig({
? [["line"], ["html", { open: "never" }]]
: [["list"], ["html", { open: "never" }]],
retries: process.env.CI ? 2 : 0,
+ snapshotPathTemplate:
+ "{testDir}/{testFilePath}-snapshots/{arg}-{projectName}{ext}",
testDir: "./e2e",
use: {
baseURL,
diff --git a/public/fonts/iransans/README.md b/public/fonts/iransans/README.md
deleted file mode 100644
index 422932c..0000000
--- a/public/fonts/iransans/README.md
+++ /dev/null
@@ -1,11 +0,0 @@
-# Local IRANSansX fonts
-
-IRANSansX font binaries are not committed because their public redistribution license is unclear.
-
-To enable the font locally:
-
-1. Obtain a valid IRANSansX license.
-2. Copy the licensed `.woff2` or `.woff` files into this directory.
-3. Add matching `@font-face` rules in `src/styles/iransans.css`.
-
-The app already keeps `"IRANSansX"` first in the global font stack, so the UI falls back safely until local font files are configured.
diff --git a/release-please-config.json b/release-please-config.json
new file mode 100644
index 0000000..5fab763
--- /dev/null
+++ b/release-please-config.json
@@ -0,0 +1,10 @@
+{
+ "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
+ "include-v-in-tag": true,
+ "packages": {
+ ".": {
+ "package-name": "kernel",
+ "release-type": "node"
+ }
+ }
+}
diff --git a/scripts/check-bundle-size.mjs b/scripts/check-bundle-size.mjs
new file mode 100644
index 0000000..9faa794
--- /dev/null
+++ b/scripts/check-bundle-size.mjs
@@ -0,0 +1,45 @@
+import console from "node:console";
+import { readdir, stat } from "node:fs/promises";
+import path from "node:path";
+
+const assetsDirectory = path.resolve("dist/assets");
+const maximumChunkBytes = 450_000;
+const maximumTotalBytes = 1_600_000;
+
+const assetNames = await readdir(assetsDirectory);
+const javascriptAssets = assetNames.filter((name) => name.endsWith(".js"));
+
+if (javascriptAssets.length === 0) {
+ throw new Error(`No JavaScript assets found in ${assetsDirectory}`);
+}
+
+const assets = await Promise.all(
+ javascriptAssets.map(async (name) => ({
+ bytes: (await stat(path.join(assetsDirectory, name))).size,
+ name,
+ })),
+);
+const oversizedAssets = assets.filter(({ bytes }) => bytes > maximumChunkBytes);
+const totalBytes = assets.reduce((total, { bytes }) => total + bytes, 0);
+
+if (oversizedAssets.length > 0 || totalBytes > maximumTotalBytes) {
+ const failures = oversizedAssets.map(
+ ({ bytes, name }) =>
+ `${name} is ${bytes.toLocaleString()} bytes (limit ${maximumChunkBytes.toLocaleString()})`,
+ );
+
+ if (totalBytes > maximumTotalBytes) {
+ failures.push(
+ `Total JavaScript is ${totalBytes.toLocaleString()} bytes (limit ${maximumTotalBytes.toLocaleString()})`,
+ );
+ }
+
+ throw new Error(`Bundle size budget exceeded:\n${failures.join("\n")}`);
+}
+
+const largestAsset = assets.toSorted((a, b) => b.bytes - a.bytes)[0];
+
+console.log(
+ `Bundle budgets passed: ${assets.length} chunks, ${totalBytes.toLocaleString()} bytes total, ` +
+ `${largestAsset.name} largest at ${largestAsset.bytes.toLocaleString()} bytes.`,
+);
diff --git a/scripts/run-lighthouse.mjs b/scripts/run-lighthouse.mjs
new file mode 100644
index 0000000..eebe1b7
--- /dev/null
+++ b/scripts/run-lighthouse.mjs
@@ -0,0 +1,97 @@
+import console from "node:console";
+import { mkdir, writeFile } from "node:fs/promises";
+import path from "node:path";
+import process from "node:process";
+import { URL } from "node:url";
+
+import * as chromeLauncher from "chrome-launcher";
+import lighthouse from "lighthouse";
+import { preview } from "vite";
+
+const reportDirectory = path.resolve(".lighthouseci");
+const appBasePath = (process.env.VITE_APP_BASE_URL ?? "/").replace(
+ /^\/+|\/+$/g,
+ "",
+);
+const url = new URL(
+ `/${[appBasePath, "auth"].filter(Boolean).join("/")}`,
+ "https://127.0.0.1:4174",
+).href;
+const thresholds = {
+ "cumulative-layout-shift": 0.1,
+ "first-contentful-paint": 4_000,
+ interactive: 4_500,
+ "largest-contentful-paint": 4_000,
+ "total-blocking-time": 300,
+};
+
+const server = await preview({
+ preview: {
+ host: "127.0.0.1",
+ port: 4174,
+ strictPort: true,
+ },
+});
+const chrome = await chromeLauncher.launch({
+ chromeFlags: ["--headless=new", "--ignore-certificate-errors"],
+});
+
+try {
+ const result = await lighthouse(url, {
+ logLevel: "error",
+ onlyCategories: ["performance"],
+ output: ["html", "json"],
+ port: chrome.port,
+ });
+
+ if (!result) throw new Error("Lighthouse did not produce a result");
+
+ const reports = Array.isArray(result.report)
+ ? result.report
+ : [result.report];
+
+ await mkdir(reportDirectory, { recursive: true });
+ await Promise.all([
+ writeFile(path.join(reportDirectory, "report.html"), reports[0]),
+ writeFile(path.join(reportDirectory, "report.json"), reports[1]),
+ ]);
+
+ const failures = [];
+ const performanceScore = result.lhr.categories.performance.score ?? 0;
+
+ if (performanceScore < 0.75) {
+ failures.push(
+ `performance score ${Math.round(performanceScore * 100)} (minimum 75)`,
+ );
+ }
+
+ for (const [auditId, maximum] of Object.entries(thresholds)) {
+ const value = result.lhr.audits[auditId].numericValue;
+
+ if (value === undefined || value > maximum) {
+ failures.push(
+ `${auditId} ${value?.toFixed(2) ?? "unavailable"} (maximum ${maximum})`,
+ );
+ }
+ }
+
+ if (failures.length > 0) {
+ throw new Error(`Lighthouse budget exceeded:\n${failures.join("\n")}`);
+ }
+
+ console.log(
+ `Lighthouse budgets passed with a performance score of ${Math.round(performanceScore * 100)}.`,
+ );
+} finally {
+ try {
+ await chrome.kill();
+ } catch (error) {
+ console.warn(`Chrome cleanup warning: ${error.message}`);
+ }
+ await new Promise((resolve, reject) => {
+ server.httpServer.close((error) => {
+ if (error) reject(error);
+ else resolve();
+ });
+ });
+}
diff --git a/scripts/validate-production-env.mjs b/scripts/validate-production-env.mjs
new file mode 100644
index 0000000..956ec55
--- /dev/null
+++ b/scripts/validate-production-env.mjs
@@ -0,0 +1,43 @@
+import process from "node:process";
+import { URL } from "node:url";
+
+import { loadEnv } from "vite";
+
+const env = loadEnv("production", process.cwd(), "");
+const apiUrl = env.VITE_API_BASE_URL;
+const appBaseUrl = env.VITE_APP_BASE_URL;
+const observabilityUrl = env.VITE_OBSERVABILITY_URL;
+
+if (!apiUrl) {
+ throw new Error("VITE_API_BASE_URL is required for production builds");
+}
+
+let parsedApiUrl;
+
+try {
+ parsedApiUrl = new URL(apiUrl);
+} catch {
+ throw new Error("VITE_API_BASE_URL must be a valid absolute URL");
+}
+
+if (parsedApiUrl.protocol !== "https:") {
+ throw new Error("VITE_API_BASE_URL must use HTTPS in production");
+}
+
+if (!appBaseUrl || !appBaseUrl.startsWith("/") || !appBaseUrl.endsWith("/")) {
+ throw new Error("VITE_APP_BASE_URL must start and end with / in production");
+}
+
+if (observabilityUrl) {
+ let parsedObservabilityUrl;
+
+ try {
+ parsedObservabilityUrl = new URL(observabilityUrl);
+ } catch {
+ throw new Error("VITE_OBSERVABILITY_URL must be a valid absolute URL");
+ }
+
+ if (parsedObservabilityUrl.protocol !== "https:") {
+ throw new Error("VITE_OBSERVABILITY_URL must use HTTPS in production");
+ }
+}
diff --git a/smoke.config.ts b/smoke.config.ts
new file mode 100644
index 0000000..1629568
--- /dev/null
+++ b/smoke.config.ts
@@ -0,0 +1,40 @@
+import { defineConfig, devices } from "@playwright/test";
+
+const deploymentUrl = new URL(
+ process.env.SMOKE_BASE_URL ?? "https://smoke-target.invalid/",
+);
+
+if (!deploymentUrl.pathname.endsWith("/")) {
+ deploymentUrl.pathname += "/";
+}
+
+export default defineConfig({
+ expect: {
+ timeout: 10_000,
+ },
+ forbidOnly: true,
+ globalSetup: "./smoke/setup.ts",
+ outputDir: "test-results/smoke",
+ projects: [
+ {
+ name: "deployed-chromium",
+ use: {
+ ...devices["Desktop Chrome"],
+ ...(process.env.CI ? {} : { channel: "chrome" as const }),
+ },
+ },
+ ],
+ reporter: [
+ ["line"],
+ ["html", { open: "never", outputFolder: "playwright-report/smoke" }],
+ ],
+ retries: process.env.CI ? 2 : 0,
+ testDir: "./smoke",
+ use: {
+ baseURL: deploymentUrl.href,
+ ignoreHTTPSErrors: process.env.SMOKE_IGNORE_HTTPS_ERRORS === "true",
+ screenshot: "only-on-failure",
+ trace: "on-first-retry",
+ },
+ workers: 1,
+});
diff --git a/smoke/deployment.spec.ts b/smoke/deployment.spec.ts
new file mode 100644
index 0000000..9414e8e
--- /dev/null
+++ b/smoke/deployment.spec.ts
@@ -0,0 +1,46 @@
+import { expect, test } from "@playwright/test";
+
+const apiHealthUrl = process.env.SMOKE_API_HEALTH_URL;
+
+if (!apiHealthUrl) throw new Error("SMOKE_API_HEALTH_URL is required");
+
+test("loads the application and its static assets", async ({ page }) => {
+ const applicationOrigin = new URL(test.info().project.use.baseURL as string)
+ .origin;
+ const assetResponses: { ok: boolean; url: string }[] = [];
+
+ page.on("response", (response) => {
+ const resourceType = response.request().resourceType();
+
+ if (
+ ["font", "script", "stylesheet"].includes(resourceType) &&
+ new URL(response.url()).origin === applicationOrigin
+ ) {
+ assetResponses.push({ ok: response.ok(), url: response.url() });
+ }
+ });
+
+ const documentResponse = await page.goto("auth");
+
+ expect(documentResponse?.ok()).toBe(true);
+ await expect(page).toHaveTitle("kernel");
+ await expect(page.locator("#root > *").first()).toBeVisible();
+ expect(assetResponses.length).toBeGreaterThan(0);
+ expect(assetResponses.filter(({ ok }) => !ok)).toEqual([]);
+});
+
+test("supports direct navigation to a client-side route", async ({ page }) => {
+ const response = await page.goto("auth/register");
+
+ expect(response?.ok()).toBe(true);
+ await expect(page.locator("form")).toBeVisible();
+ await expect(page).toHaveURL(/\/auth\/register\/?$/);
+});
+
+test("reaches the deployed API health endpoint", async ({ request }) => {
+ const response = await request.get(apiHealthUrl, {
+ failOnStatusCode: false,
+ });
+
+ expect(response.ok()).toBe(true);
+});
diff --git a/smoke/setup.ts b/smoke/setup.ts
new file mode 100644
index 0000000..5abe994
--- /dev/null
+++ b/smoke/setup.ts
@@ -0,0 +1,16 @@
+const validateHttpsUrl = (name: string) => {
+ const value = process.env[name];
+
+ if (!value) throw new Error(`${name} is required`);
+
+ const url = new URL(value);
+
+ if (url.protocol !== "https:") {
+ throw new Error(`${name} must use HTTPS`);
+ }
+};
+
+export default () => {
+ validateHttpsUrl("SMOKE_API_HEALTH_URL");
+ validateHttpsUrl("SMOKE_BASE_URL");
+};
diff --git a/sonar-project.properties b/sonar-project.properties
new file mode 100644
index 0000000..bbee1db
--- /dev/null
+++ b/sonar-project.properties
@@ -0,0 +1,9 @@
+sonar.organization=sepaseh
+sonar.sources=src
+sonar.tests=src,e2e
+sonar.test.inclusions=src/**/*.test.ts,src/**/*.test.tsx,e2e/**/*.spec.ts
+sonar.exclusions=src/**/*.d.ts,src/**/*.test.ts,src/**/*.test.tsx,src/assets/fonts/**,src/test/**
+sonar.coverage.exclusions=src/**/*.d.ts,src/**/*.test.ts,src/**/*.test.tsx,src/**/index.ts,src/main.tsx,src/test/**,src/types/**
+sonar.javascript.lcov.reportPaths=coverage/lcov.info
+sonar.qualitygate.ignoreSmallChanges=false
+sonar.qualitygate.wait=true
diff --git a/src/App.tsx b/src/App.tsx
index f001e6f..f618d18 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,11 +1,14 @@
+import { ErrorBoundary } from "@/components/error-boundary";
import { AntdProvider, CoreProvider } from "@/providers";
import { Routes } from "./Routes";
export const App = () => (
-
-
-
-
-
+
+
+
+
+
+
+
);
diff --git a/src/Routes.test.tsx b/src/Routes.test.tsx
new file mode 100644
index 0000000..5e45806
--- /dev/null
+++ b/src/Routes.test.tsx
@@ -0,0 +1,79 @@
+import { render, screen } from "@testing-library/react";
+import { Outlet } from "react-router";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+
+const routeState = vi.hoisted(() => ({
+ allowedRoutes: new Set([
+ "account",
+ "auth",
+ "forgotPassword",
+ "register",
+ "roles",
+ "root",
+ "users",
+ ]),
+ setCurrentRoute: vi.fn(),
+}));
+
+vi.mock("@/hooks", () => ({
+ useAllowedRoutes: () => routeState.allowedRoutes,
+ useCore: () => ({ setCurrentRoute: routeState.setCurrentRoute }),
+}));
+vi.mock("@/layouts/auth", () => ({ AuthLayout: () => }));
+vi.mock("@/layouts/default", () => ({ DefaultLayout: () => }));
+vi.mock("@/pages/account", () => ({ AccountPage: () => "Account page" }));
+vi.mock("@/pages/dashboard", () => ({
+ DashboardPage: () => "Dashboard page",
+}));
+vi.mock("@/pages/forgot-pass", () => ({
+ ForgotPassPage: () => "Forgot password page",
+}));
+vi.mock("@/pages/login", () => ({ LoginPage: () => "Login page" }));
+vi.mock("@/pages/register", () => ({ RegisterPage: () => "Register page" }));
+vi.mock("@/pages/roles", () => ({ RolesPage: () => "Roles page" }));
+vi.mock("@/pages/users", () => ({ UsersPage: () => "Users page" }));
+
+beforeEach(() => {
+ routeState.setCurrentRoute.mockClear();
+ vi.resetModules();
+});
+
+describe("application routes", () => {
+ it("renders the not-found page for an unknown route", async () => {
+ window.history.replaceState({}, "", "/unknown-route");
+ const { Routes } = await import("./Routes");
+
+ render();
+
+ expect(await screen.findByText("404")).toBeInTheDocument();
+ });
+
+ it.each([
+ ["/", "Dashboard page", "root"],
+ ["/account", "Account page", "account"],
+ ["/users", "Users page", "users"],
+ ["/roles", "Roles page", "roles"],
+ ["/auth", "Login page", "auth"],
+ ["/auth/forgot-password", "Forgot password page", "forgotPassword"],
+ ["/auth/register", "Register page", "register"],
+ ])("renders the allowed route %s", async (path, content, route) => {
+ window.history.replaceState({}, "", path);
+ const { Routes } = await import("./Routes");
+
+ render();
+
+ expect(await screen.findByText(content)).toBeInTheDocument();
+ expect(routeState.setCurrentRoute).toHaveBeenCalledWith(route);
+ });
+
+ it("redirects a disallowed route to the dashboard", async () => {
+ routeState.allowedRoutes.delete("users");
+ window.history.replaceState({}, "", "/users");
+ const { Routes } = await import("./Routes");
+
+ render();
+
+ expect(await screen.findByText("Dashboard page")).toBeInTheDocument();
+ routeState.allowedRoutes.add("users");
+ });
+});
diff --git a/src/Routes.tsx b/src/Routes.tsx
index 0f6b648..81602ce 100644
--- a/src/Routes.tsx
+++ b/src/Routes.tsx
@@ -1,27 +1,60 @@
-import { FC, ReactNode, useEffect } from "react";
import {
- createBrowserRouter,
- Navigate,
- RouterProvider,
-} from "react-router-dom";
+ FC,
+ Fragment,
+ lazy,
+ ReactElement,
+ ReactNode,
+ Suspense,
+ useEffect,
+} from "react";
+import { createBrowserRouter, Navigate } from "react-router";
+import { RouterProvider } from "react-router/dom";
import { baseUrl, RouteKey, routeTree } from "@/config";
import { useAllowedRoutes, useCore } from "@/hooks";
-import { AuthLayout } from "@/layouts/Auth";
-import { DefaultLayout } from "@/layouts/Default";
-import { AccountPage } from "@/pages/Account";
-import { DashboardPage } from "@/pages/Dashboard";
-import { ForgotPassPage } from "@/pages/ForgotPass";
-import { LoginPage } from "@/pages/Login";
-import { NotFoundPage } from "@/pages/NotFound";
-import { RegisterPage } from "@/pages/Register";
-import { RolesPage } from "@/pages/Roles";
-import { UsersPage } from "@/pages/Users";
+import { NotFoundPage } from "@/pages/not-found";
+
+const AccountPage = lazy(async () => {
+ const { AccountPage } = await import("@/pages/account");
+ return { default: AccountPage };
+});
+const AuthLayout = lazy(async () => {
+ const { AuthLayout } = await import("@/layouts/auth");
+ return { default: AuthLayout };
+});
+const DashboardPage = lazy(async () => {
+ const { DashboardPage } = await import("@/pages/dashboard");
+ return { default: DashboardPage };
+});
+const DefaultLayout = lazy(async () => {
+ const { DefaultLayout } = await import("@/layouts/default");
+ return { default: DefaultLayout };
+});
+const ForgotPassPage = lazy(async () => {
+ const { ForgotPassPage } = await import("@/pages/forgot-pass");
+ return { default: ForgotPassPage };
+});
+const LoginPage = lazy(async () => {
+ const { LoginPage } = await import("@/pages/login");
+ return { default: LoginPage };
+});
+const RegisterPage = lazy(async () => {
+ const { RegisterPage } = await import("@/pages/register");
+ return { default: RegisterPage };
+});
+const RolesPage = lazy(async () => {
+ const { RolesPage } = await import("@/pages/roles");
+ return { default: RolesPage };
+});
+const UsersPage = lazy(async () => {
+ const { UsersPage } = await import("@/pages/users");
+ return { default: UsersPage };
+});
const RouteWrapper: FC<{ route: RouteKey; children: ReactNode }> = ({
route,
children,
-}) => {
+}): ReactElement => {
const { setCurrentRoute } = useCore();
const allowedRoutes = useAllowedRoutes();
@@ -29,20 +62,26 @@ const RouteWrapper: FC<{ route: RouteKey; children: ReactNode }> = ({
setCurrentRoute(route);
}, [route, setCurrentRoute]);
- if (allowedRoutes.has(route)) return children;
+ if (allowedRoutes.has(route)) return {children};
return ;
};
const wrapRoute = (route: RouteKey, element: ReactNode) => (
- {element}
+
+ {element}
+
);
const router = createBrowserRouter(
[
{
path: routeTree.auth.path,
- element: ,
+ element: (
+
+
+
+ ),
children: [
{
index: true,
@@ -60,7 +99,11 @@ const router = createBrowserRouter(
},
{
path: routeTree.root.path,
- element: ,
+ element: (
+
+
+
+ ),
children: [
{
index: true,
diff --git a/src/api/account.test.ts b/src/api/account.test.ts
new file mode 100644
index 0000000..bb13073
--- /dev/null
+++ b/src/api/account.test.ts
@@ -0,0 +1,60 @@
+import { beforeEach, describe, expect, it, vi } from "vitest";
+
+import {
+ getAccount,
+ requestEmailVerification,
+ updateProfile,
+ updateUsername,
+ verifyEmail,
+} from "./account";
+import { apiClient } from "./instance";
+
+vi.mock("./instance", () => ({
+ apiClient: {
+ get: vi.fn(),
+ patch: vi.fn(),
+ post: vi.fn(),
+ },
+}));
+
+beforeEach(() => {
+ vi.mocked(apiClient.get).mockResolvedValue({});
+ vi.mocked(apiClient.patch).mockResolvedValue({});
+ vi.mocked(apiClient.post).mockResolvedValue({});
+});
+
+describe("account API", () => {
+ it("uses the account endpoint", async () => {
+ await getAccount();
+
+ expect(apiClient.get).toHaveBeenCalledWith("/account/me");
+ });
+
+ it("requests and verifies email changes", async () => {
+ await requestEmailVerification({ email: "ada@example.com" });
+ await verifyEmail({ email: "ada@example.com", otp: "123456" });
+
+ expect(apiClient.post).toHaveBeenNthCalledWith(
+ 1,
+ "/account/request-email-verification",
+ { email: "ada@example.com" },
+ );
+ expect(apiClient.post).toHaveBeenNthCalledWith(2, "/account/verify-email", {
+ email: "ada@example.com",
+ otp: "123456",
+ });
+ });
+
+ it("updates profile and username fields", async () => {
+ await updateProfile({ firstName: "Ada", lastName: "Lovelace" });
+ await updateUsername({ username: "ada" });
+
+ expect(apiClient.patch).toHaveBeenCalledWith("/account/update-profile", {
+ firstName: "Ada",
+ lastName: "Lovelace",
+ });
+ expect(apiClient.post).toHaveBeenCalledWith("/account/update-username", {
+ username: "ada",
+ });
+ });
+});
diff --git a/src/api/auth.test.ts b/src/api/auth.test.ts
new file mode 100644
index 0000000..a23556c
--- /dev/null
+++ b/src/api/auth.test.ts
@@ -0,0 +1,93 @@
+import { beforeEach, describe, expect, it, vi } from "vitest";
+
+import {
+ changePassword,
+ forgotPassword,
+ login,
+ logout,
+ register,
+ requestOtp,
+} from "./auth";
+import { apiClient } from "./instance";
+import { clearAccessToken, setAccessToken } from "./token";
+
+vi.mock("./instance", () => ({
+ apiClient: {
+ post: vi.fn(),
+ },
+}));
+
+vi.mock("./token", () => ({
+ clearAccessToken: vi.fn(),
+ setAccessToken: vi.fn(),
+}));
+
+const currentCredential = "current-test-credential";
+const replacementCredential = "replacement-test-credential";
+const syntheticCredential = "synthetic-test-credential";
+
+beforeEach(() => {
+ vi.mocked(apiClient.post).mockResolvedValue({});
+});
+
+describe("authentication API", () => {
+ it("stores access tokens returned by login and registration", async () => {
+ vi.mocked(apiClient.post)
+ .mockResolvedValueOnce({ accessToken: "login-token" })
+ .mockResolvedValueOnce({ accessToken: "register-token" });
+
+ await expect(
+ login({ identifier: "ada", password: syntheticCredential }),
+ ).resolves.toEqual({ accessToken: "login-token" });
+ await expect(
+ register({
+ firstName: "Ada",
+ lastName: "Lovelace",
+ mobile: "09120000000",
+ otp: "123456",
+ password: syntheticCredential,
+ }),
+ ).resolves.toEqual({ accessToken: "register-token" });
+
+ expect(setAccessToken).toHaveBeenNthCalledWith(1, "login-token");
+ expect(setAccessToken).toHaveBeenNthCalledWith(2, "register-token");
+ });
+
+ it("clears the access token when logout succeeds or fails", async () => {
+ await logout();
+
+ vi.mocked(apiClient.post).mockRejectedValueOnce(new Error("offline"));
+ await expect(logout()).rejects.toThrow("offline");
+
+ expect(apiClient.post).toHaveBeenNthCalledWith(1, "/auth/logout");
+ expect(apiClient.post).toHaveBeenNthCalledWith(2, "/auth/logout");
+ expect(clearAccessToken).toHaveBeenCalledTimes(2);
+ });
+
+ it("submits password and OTP operations to their endpoints", async () => {
+ await changePassword({
+ currentPassword: currentCredential,
+ newPassword: replacementCredential,
+ });
+ await forgotPassword({
+ mobile: "09120000000",
+ otp: "123456",
+ password: replacementCredential,
+ });
+ await requestOtp({ mobile: "09120000000", purpose: "login" });
+
+ expect(apiClient.post).toHaveBeenNthCalledWith(1, "/auth/change-password", {
+ currentPassword: currentCredential,
+ newPassword: replacementCredential,
+ });
+ expect(apiClient.post).toHaveBeenNthCalledWith(2, "/auth/forgot-password", {
+ mobile: "09120000000",
+ otp: "123456",
+ password: replacementCredential,
+ });
+ expect(apiClient.post).toHaveBeenNthCalledWith(3, "/auth/otp-request", {
+ mobile: "09120000000",
+ purpose: "login",
+ });
+ });
+});
diff --git a/src/api/instance.test.ts b/src/api/instance.test.ts
new file mode 100644
index 0000000..c5cb244
--- /dev/null
+++ b/src/api/instance.test.ts
@@ -0,0 +1,288 @@
+import axios, { AxiosError } from "axios";
+import { delay, http, HttpResponse } from "msw";
+import { afterEach, describe, expect, it, vi } from "vitest";
+
+import { i18nInstance } from "@/i18n";
+import { server } from "@/test/mocks/server";
+
+import { apiClient, setUnauthorizedHandler } from "./instance";
+import { clearAccessToken, getAccessToken, setAccessToken } from "./token";
+
+const syntheticCredential = "synthetic-test-credential";
+
+afterEach(() => {
+ clearAccessToken();
+ setUnauthorizedHandler(null);
+});
+
+describe("API client authentication", () => {
+ it("adds the access token to protected requests", async () => {
+ setAccessToken("access-token");
+ server.use(
+ http.get("http://localhost/protected", ({ request }) => {
+ expect(request.headers.get("authorization")).toBe(
+ "Bearer access-token",
+ );
+
+ return HttpResponse.json({ user_id: "user-1" });
+ }),
+ );
+
+ await expect(
+ apiClient.get<{ userId: string }>("/protected"),
+ ).resolves.toEqual({ userId: "user-1" });
+ });
+
+ it("refreshes an expired token and retries the request once", async () => {
+ let protectedRequestCount = 0;
+ let refreshRequestCount = 0;
+
+ setAccessToken("expired-token");
+ server.use(
+ http.get("http://localhost/protected", ({ request }) => {
+ protectedRequestCount += 1;
+
+ if (request.headers.get("authorization") === "Bearer fresh-token") {
+ return HttpResponse.json({ status: "ready" });
+ }
+
+ return HttpResponse.json({ message: "Unauthorized" }, { status: 401 });
+ }),
+ http.post("http://localhost/auth/refresh-token", () => {
+ refreshRequestCount += 1;
+ return HttpResponse.json({ access_token: "fresh-token" });
+ }),
+ );
+
+ await expect(
+ apiClient.get<{ status: string }>("/protected"),
+ ).resolves.toEqual({ status: "ready" });
+ expect(getAccessToken()).toBe("fresh-token");
+ expect(protectedRequestCount).toBe(2);
+ expect(refreshRequestCount).toBe(1);
+ });
+
+ it("shares one token refresh across concurrent unauthorized requests", async () => {
+ let refreshRequestCount = 0;
+
+ setAccessToken("expired-token");
+ server.use(
+ http.get("http://localhost/protected/:id", ({ request, params }) => {
+ if (request.headers.get("authorization") === "Bearer fresh-token") {
+ return HttpResponse.json({ id: params.id });
+ }
+
+ return HttpResponse.json({ message: "Unauthorized" }, { status: 401 });
+ }),
+ http.post("http://localhost/auth/refresh-token", async () => {
+ refreshRequestCount += 1;
+ await delay(10);
+ return HttpResponse.json({ access_token: "fresh-token" });
+ }),
+ );
+
+ await expect(
+ Promise.all([
+ apiClient.get<{ id: string }>("/protected/one"),
+ apiClient.get<{ id: string }>("/protected/two"),
+ ]),
+ ).resolves.toEqual([{ id: "one" }, { id: "two" }]);
+ expect(refreshRequestCount).toBe(1);
+ });
+
+ it("clears authentication once when a shared token refresh fails", async () => {
+ const onUnauthorized = vi.fn();
+ let refreshRequestCount = 0;
+
+ setAccessToken("expired-token");
+ setUnauthorizedHandler(onUnauthorized);
+ server.use(
+ http.get("http://localhost/protected/:id", () =>
+ HttpResponse.json({ message: "Unauthorized" }, { status: 401 }),
+ ),
+ http.post("http://localhost/auth/refresh-token", async () => {
+ refreshRequestCount += 1;
+ await delay(10);
+ return HttpResponse.json(
+ { message: "Refresh rejected" },
+ { status: 401 },
+ );
+ }),
+ );
+
+ const results = await Promise.allSettled([
+ apiClient.get("/protected/one"),
+ apiClient.get("/protected/two"),
+ ]);
+
+ expect(results.every(({ status }) => status === "rejected")).toBe(true);
+ expect(getAccessToken()).toBeNull();
+ expect(onUnauthorized).toHaveBeenCalledOnce();
+ expect(refreshRequestCount).toBe(1);
+ });
+
+ it("clears authentication when a single token refresh fails", async () => {
+ const onUnauthorized = vi.fn();
+ let protectedRequestCount = 0;
+ let refreshRequestCount = 0;
+
+ setAccessToken("expired-token");
+ setUnauthorizedHandler(onUnauthorized);
+ server.use(
+ http.get("http://localhost/protected", () => {
+ protectedRequestCount += 1;
+ return HttpResponse.json({ message: "Unauthorized" }, { status: 401 });
+ }),
+ http.post("http://localhost/auth/refresh-token", () => {
+ refreshRequestCount += 1;
+ return HttpResponse.json(
+ { message: "Refresh rejected" },
+ { status: 401 },
+ );
+ }),
+ );
+
+ await expect(apiClient.get("/protected")).rejects.toThrow("Unauthorized");
+ expect(protectedRequestCount).toBe(1);
+ expect(refreshRequestCount).toBe(1);
+ expect(getAccessToken()).toBeNull();
+ expect(onUnauthorized).toHaveBeenCalledOnce();
+ });
+
+ it("does not start another refresh when the retried request is unauthorized", async () => {
+ const onUnauthorized = vi.fn();
+ let protectedRequestCount = 0;
+ let refreshRequestCount = 0;
+
+ setAccessToken("expired-token");
+ setUnauthorizedHandler(onUnauthorized);
+ server.use(
+ http.get("http://localhost/protected", () => {
+ protectedRequestCount += 1;
+ return HttpResponse.json({ message: "Unauthorized" }, { status: 401 });
+ }),
+ http.post("http://localhost/auth/refresh-token", () => {
+ refreshRequestCount += 1;
+ return HttpResponse.json({ access_token: "fresh-token" });
+ }),
+ );
+
+ await expect(apiClient.get("/protected")).rejects.toThrow("Unauthorized");
+ expect(protectedRequestCount).toBe(2);
+ expect(refreshRequestCount).toBe(1);
+ expect(getAccessToken()).toBeNull();
+ expect(onUnauthorized).toHaveBeenCalledOnce();
+ });
+
+ it("does not refresh or clear authentication for public auth requests", async () => {
+ const onUnauthorized = vi.fn();
+ const refresh = vi.fn();
+
+ setUnauthorizedHandler(onUnauthorized);
+ server.use(
+ http.post("http://localhost/auth/login", () =>
+ HttpResponse.json({ message: "Invalid credentials" }, { status: 401 }),
+ ),
+ http.post("http://localhost/auth/refresh-token", refresh),
+ );
+
+ await expect(
+ apiClient.post("/auth/login", {
+ identifier: "ada",
+ password: syntheticCredential,
+ }),
+ ).rejects.toThrow("Invalid credentials");
+ expect(refresh).not.toHaveBeenCalled();
+ expect(onUnauthorized).not.toHaveBeenCalled();
+ });
+});
+
+describe("API client errors", () => {
+ it("transforms structured API errors and preserves their cause", async () => {
+ server.use(
+ http.get("http://localhost/invalid-request", () =>
+ HttpResponse.json(
+ {
+ cause: { email: "Email is invalid" },
+ message: "Validation failed",
+ },
+ { status: 422 },
+ ),
+ ),
+ );
+
+ await expect(apiClient.get("/invalid-request")).rejects.toMatchObject({
+ cause: { email: "Email is invalid" },
+ message: "Validation failed",
+ });
+ });
+
+ it.each([
+ ["null", null],
+ ["primitive", "failure"],
+ ["array", []],
+ ["missing message", {}],
+ ["empty message", { message: "" }],
+ ["non-string message", { message: 500 }],
+ ])(
+ "uses the unexpected-error message for a %s response body",
+ async (label, body) => {
+ const path = `/malformed-error/${label.replaceAll(" ", "-")}`;
+ server.use(
+ http.get(`http://localhost${path}`, () =>
+ HttpResponse.json(body, { status: 500 }),
+ ),
+ );
+
+ await expect(apiClient.get(path)).rejects.toThrow(
+ i18nInstance.t("unexpectedError"),
+ );
+ },
+ );
+
+ it("preserves request cancellation", async () => {
+ const controller = new AbortController();
+
+ server.use(
+ http.get("http://localhost/cancelled", async () => {
+ await delay(100);
+ return HttpResponse.json({ status: "ready" });
+ }),
+ );
+
+ const request = apiClient.get("/cancelled", {
+ signal: controller.signal,
+ });
+ controller.abort();
+
+ await expect(request).rejects.toSatisfy((error: unknown) =>
+ axios.isCancel(error),
+ );
+ });
+
+ it("uses the network-error message when a request times out", async () => {
+ await expect(
+ apiClient.get("/slow", {
+ adapter: (config) =>
+ Promise.reject(
+ new AxiosError(
+ "timeout exceeded",
+ AxiosError.ETIMEDOUT,
+ config,
+ {},
+ ),
+ ),
+ }),
+ ).rejects.toThrow(i18nInstance.t("networkError"));
+ });
+
+ it("uses the network-error message for network failures", async () => {
+ server.use(
+ http.get("http://localhost/network-failure", () => HttpResponse.error()),
+ );
+
+ await expect(apiClient.get("/network-failure")).rejects.toThrow(
+ i18nInstance.t("networkError"),
+ );
+ });
+});
diff --git a/src/api/instance.ts b/src/api/instance.ts
index 08419d4..c74d0a3 100644
--- a/src/api/instance.ts
+++ b/src/api/instance.ts
@@ -1,6 +1,12 @@
-import axios, { AxiosRequestConfig, InternalAxiosRequestConfig } from "axios";
+import axios, {
+ AxiosError,
+ AxiosRequestConfig,
+ AxiosResponse,
+ InternalAxiosRequestConfig,
+} from "axios";
import { apiUrl } from "@/config";
+import { i18nInstance } from "@/i18n";
import { AccessTokenProps } from "@/types";
import { toCamelCase, toSnakeCase } from "@/utils";
@@ -23,6 +29,23 @@ type RetryableRequestConfig = InternalAxiosRequestConfig & {
_retried?: boolean;
};
+type ApiErrorBody = {
+ cause?: unknown;
+ message: string;
+};
+
+const isApiErrorBody = (value: unknown): value is ApiErrorBody => {
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
+ return false;
+ }
+
+ return (
+ "message" in value &&
+ typeof value.message === "string" &&
+ value.message.length > 0
+ );
+};
+
const handleUnauthorized = () => {
if (isHandlingUnauthorized) return;
@@ -53,6 +76,37 @@ const refreshAccessToken = (): Promise => {
return refreshPromise;
};
+const retryUnauthorizedRequest = async (
+ error: AxiosError,
+): Promise | undefined> => {
+ if (error.response?.status !== 401) return;
+
+ const requestConfig = error.config as RetryableRequestConfig | undefined;
+ const isPublicAuthRequest = publicAuthUrls.has(requestConfig?.url ?? "");
+
+ if (!requestConfig || requestConfig._retried || isPublicAuthRequest) {
+ if (!isPublicAuthRequest) handleUnauthorized();
+ return;
+ }
+
+ requestConfig._retried = true;
+
+ try {
+ await refreshAccessToken();
+ return api.request(requestConfig);
+ } catch {
+ handleUnauthorized();
+ }
+};
+
+const createResponseError = (data: unknown): Error => {
+ if (!isApiErrorBody(data)) {
+ return new Error(i18nInstance.t("unexpectedError"));
+ }
+
+ return new Error(data.message, { cause: data.cause });
+};
+
api.interceptors.request.use(
(config) => {
const authToken = getAccessToken();
@@ -72,50 +126,25 @@ api.interceptors.request.use(
api.interceptors.response.use(
(response) => response,
async (error) => {
- if (
- axios.isAxiosError<{ errors: Record; message: string }>(
- error,
- )
- ) {
- if (error.response?.status === 401) {
- const requestConfig = error.config as
- RetryableRequestConfig | undefined;
- const isPublicAuthRequest = publicAuthUrls.has(
- requestConfig?.url ?? "",
- );
- const canRefresh =
- requestConfig && !requestConfig._retried && !isPublicAuthRequest;
-
- if (canRefresh) {
- requestConfig._retried = true;
-
- try {
- await refreshAccessToken();
- return api.request(requestConfig);
- } catch {
- handleUnauthorized();
- }
- } else if (!isPublicAuthRequest) {
- handleUnauthorized();
- }
- }
-
- if (error.response) {
- return Promise.reject(
- new Error(error.response.data.message, {
- cause: error.response.data.errors,
- }),
- );
- }
-
- if (error.request) {
- return Promise.reject(
- new Error("Network error - please check your connection"),
- );
- }
+ if (axios.isCancel(error)) throw error;
+
+ if (!axios.isAxiosError(error)) {
+ throw new Error(i18nInstance.t("unexpectedError"));
+ }
+
+ const retryResponse = await retryUnauthorizedRequest(error);
+
+ if (retryResponse) return retryResponse;
+
+ if (error.response) {
+ throw createResponseError(error.response.data);
+ }
+
+ if (error.request) {
+ throw new Error(i18nInstance.t("networkError"));
}
- return Promise.reject(error);
+ throw new Error(i18nInstance.t("unexpectedError"));
},
);
diff --git a/src/api/role.test.ts b/src/api/role.test.ts
new file mode 100644
index 0000000..8573b3e
--- /dev/null
+++ b/src/api/role.test.ts
@@ -0,0 +1,51 @@
+import { beforeEach, describe, expect, it, vi } from "vitest";
+
+import { apiClient } from "./instance";
+import {
+ createRole,
+ deleteRole,
+ fetchPermissions,
+ fetchRole,
+ fetchRoles,
+ updateRole,
+} from "./role";
+
+vi.mock("./instance", () => ({
+ apiClient: {
+ del: vi.fn(),
+ get: vi.fn(),
+ patch: vi.fn(),
+ post: vi.fn(),
+ },
+}));
+
+beforeEach(() => {
+ vi.mocked(apiClient.del).mockResolvedValue(undefined);
+ vi.mocked(apiClient.get).mockResolvedValue([]);
+ vi.mocked(apiClient.patch).mockResolvedValue({});
+ vi.mocked(apiClient.post).mockResolvedValue({});
+});
+
+describe("roles API", () => {
+ it("creates, updates, and deletes roles", async () => {
+ const params = { name: "Operators", permissions: ["users.read"] };
+
+ await createRole(params);
+ await updateRole("role-1", params);
+ await deleteRole("role-1");
+
+ expect(apiClient.post).toHaveBeenCalledWith("/roles", params);
+ expect(apiClient.patch).toHaveBeenCalledWith("/roles/role-1", params);
+ expect(apiClient.del).toHaveBeenCalledWith("/roles/role-1");
+ });
+
+ it("loads role collections, details, and permissions", async () => {
+ await fetchRoles();
+ await fetchRole("role-1");
+ await fetchPermissions();
+
+ expect(apiClient.get).toHaveBeenNthCalledWith(1, "/roles");
+ expect(apiClient.get).toHaveBeenNthCalledWith(2, "/roles/role-1");
+ expect(apiClient.get).toHaveBeenNthCalledWith(3, "/permissions");
+ });
+});
diff --git a/src/api/token.test.ts b/src/api/token.test.ts
new file mode 100644
index 0000000..e0fd9a9
--- /dev/null
+++ b/src/api/token.test.ts
@@ -0,0 +1,27 @@
+import { beforeEach, describe, expect, it } from "vitest";
+
+import { clearAccessToken, getAccessToken, setAccessToken } from "./token";
+
+beforeEach(() => {
+ clearAccessToken();
+});
+
+describe("access token", () => {
+ it("is absent before a token is set", () => {
+ expect(getAccessToken()).toBeNull();
+ });
+
+ it("stores and retrieves a token", () => {
+ setAccessToken("access-token");
+
+ expect(getAccessToken()).toBe("access-token");
+ });
+
+ it("clears a stored token", () => {
+ setAccessToken("access-token");
+
+ clearAccessToken();
+
+ expect(getAccessToken()).toBeNull();
+ });
+});
diff --git a/src/api/user.test.ts b/src/api/user.test.ts
index 3fd8a13..7b96bfd 100644
--- a/src/api/user.test.ts
+++ b/src/api/user.test.ts
@@ -1,10 +1,15 @@
import { http, HttpResponse } from "msw";
-import { describe, expect, it } from "vitest";
+import { describe, expect, it, vi } from "vitest";
+import { i18nInstance } from "@/i18n";
import { server } from "@/test/mocks/server";
import { fetchUsers } from "./user";
+vi.mock("@/i18n", () => ({
+ i18nInstance: { t: (key: string) => key },
+}));
+
describe("users API", () => {
it("serializes filters and converts the response to camel case", async () => {
server.use(
@@ -50,4 +55,24 @@ describe("users API", () => {
total: 1,
});
});
+
+ it("localizes network errors", async () => {
+ server.use(http.get("http://localhost/users", () => HttpResponse.error()));
+
+ await expect(fetchUsers({})).rejects.toThrow(
+ i18nInstance.t("networkError"),
+ );
+ });
+
+ it("localizes unexpected API responses", async () => {
+ server.use(
+ http.get("http://localhost/users", () =>
+ HttpResponse.json(null, { status: 500 }),
+ ),
+ );
+
+ await expect(fetchUsers({})).rejects.toThrow(
+ i18nInstance.t("unexpectedError"),
+ );
+ });
});
diff --git a/src/assets/fonts/vazirmatn/OFL.txt b/src/assets/fonts/vazirmatn/OFL.txt
new file mode 100644
index 0000000..be66b38
--- /dev/null
+++ b/src/assets/fonts/vazirmatn/OFL.txt
@@ -0,0 +1,93 @@
+Copyright 2015 The Vazirmatn Project Authors (https://github.com/rastikerdar/vazirmatn)
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/src/assets/fonts/vazirmatn/Vazirmatn-Variable.woff2 b/src/assets/fonts/vazirmatn/Vazirmatn-Variable.woff2
new file mode 100644
index 0000000..a501289
Binary files /dev/null and b/src/assets/fonts/vazirmatn/Vazirmatn-Variable.woff2 differ
diff --git a/src/components/DigitsInput.test.tsx b/src/components/digits-input/DigitsInput.test.tsx
similarity index 93%
rename from src/components/DigitsInput.test.tsx
rename to src/components/digits-input/DigitsInput.test.tsx
index f3f61ca..165d8d5 100644
--- a/src/components/DigitsInput.test.tsx
+++ b/src/components/digits-input/DigitsInput.test.tsx
@@ -3,7 +3,7 @@ import { describe, expect, it } from "vitest";
import { render, screen } from "@/test/render";
-import { DigitsInput } from "./DigitsInput";
+import { DigitsInput } from "./";
const DigitsInputHarness = () => {
const [value, setValue] = useState("");
diff --git a/src/components/DigitsInput.tsx b/src/components/digits-input/DigitsInput.tsx
similarity index 73%
rename from src/components/DigitsInput.tsx
rename to src/components/digits-input/DigitsInput.tsx
index 897b810..91eb97e 100644
--- a/src/components/DigitsInput.tsx
+++ b/src/components/digits-input/DigitsInput.tsx
@@ -1,7 +1,9 @@
import { Input, InputProps } from "antd";
import { FC } from "react";
-export const DigitsInput: FC = ({ onChange, ...rest }) => (
+type DigitsInputProps = InputProps;
+
+export const DigitsInput: FC = ({ onChange, ...rest }) => (
{
if (onChange) {
diff --git a/src/components/digits-input/index.ts b/src/components/digits-input/index.ts
new file mode 100644
index 0000000..8a1450e
--- /dev/null
+++ b/src/components/digits-input/index.ts
@@ -0,0 +1 @@
+export { DigitsInput } from "./DigitsInput";
diff --git a/src/components/error-boundary/ErrorBoundary.test.tsx b/src/components/error-boundary/ErrorBoundary.test.tsx
new file mode 100644
index 0000000..6068ddf
--- /dev/null
+++ b/src/components/error-boundary/ErrorBoundary.test.tsx
@@ -0,0 +1,57 @@
+import { render, screen } from "@testing-library/react";
+import { afterEach, describe, expect, it, vi } from "vitest";
+
+import { i18nInstance } from "@/i18n";
+import { setObservabilityTransport } from "@/utils";
+
+import { ErrorBoundary } from "./";
+
+const BrokenView = () => {
+ throw new Error("render failed");
+};
+
+afterEach(() => {
+ setObservabilityTransport();
+ vi.restoreAllMocks();
+});
+
+describe("ErrorBoundary", () => {
+ it("renders healthy children", () => {
+ render(
+
+ Ready
+ ,
+ );
+
+ expect(screen.getByText("Ready")).toBeInTheDocument();
+ });
+
+ it("reports render failures and offers recovery", async () => {
+ await i18nInstance.changeLanguage("en");
+ vi.spyOn(console, "error").mockImplementation(() => undefined);
+ const send = vi.fn();
+ setObservabilityTransport(send);
+
+ render(
+
+
+ ,
+ );
+
+ expect(
+ screen.getByRole("heading", {
+ name: "An unexpected error occurred.",
+ }),
+ ).toBeInTheDocument();
+ expect(screen.getByRole("button", { name: "Reload" })).toBeInTheDocument();
+ expect(send).toHaveBeenCalledWith(
+ expect.objectContaining({
+ context: expect.objectContaining({
+ source: "react.error-boundary",
+ }),
+ message: "render failed",
+ name: "error",
+ }),
+ );
+ });
+});
diff --git a/src/components/error-boundary/ErrorBoundary.tsx b/src/components/error-boundary/ErrorBoundary.tsx
new file mode 100644
index 0000000..03f1a57
--- /dev/null
+++ b/src/components/error-boundary/ErrorBoundary.tsx
@@ -0,0 +1,56 @@
+import {
+ Component,
+ type ErrorInfo,
+ Fragment,
+ type ReactElement,
+ type ReactNode,
+} from "react";
+
+import { i18nInstance } from "@/i18n";
+import { reportError } from "@/utils";
+
+type ErrorBoundaryProps = {
+ children: ReactNode;
+};
+
+type State = {
+ hasError: boolean;
+};
+
+export class ErrorBoundary extends Component {
+ public state: State = { hasError: false };
+
+ public static getDerivedStateFromError(): State {
+ return { hasError: true };
+ }
+
+ public componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
+ reportError(error, {
+ componentStack: errorInfo.componentStack,
+ source: "react.error-boundary",
+ });
+ }
+
+ public render(): ReactElement {
+ if (!this.state.hasError) {
+ return {this.props.children};
+ }
+
+ return (
+
+ {i18nInstance.t("unexpectedError")}
+
+
+ );
+ }
+}
diff --git a/src/components/error-boundary/index.ts b/src/components/error-boundary/index.ts
new file mode 100644
index 0000000..8dd2689
--- /dev/null
+++ b/src/components/error-boundary/index.ts
@@ -0,0 +1 @@
+export { ErrorBoundary } from "./ErrorBoundary";
diff --git a/src/components/form-drawer/FormDrawer.tsx b/src/components/form-drawer/FormDrawer.tsx
new file mode 100644
index 0000000..10949a8
--- /dev/null
+++ b/src/components/form-drawer/FormDrawer.tsx
@@ -0,0 +1,49 @@
+import { Button, Drawer, Space } from "antd";
+import { ReactNode } from "react";
+import { useTranslation } from "react-i18next";
+
+type FormDrawerProps = {
+ afterOpenChange?: (open: boolean) => void;
+ children: ReactNode;
+ onClose: () => void;
+ onSubmit: () => void;
+ open: boolean;
+ submitting: boolean;
+ title: ReactNode;
+};
+
+export const FormDrawer = ({
+ afterOpenChange,
+ children,
+ onClose,
+ onSubmit,
+ open,
+ submitting,
+ title,
+}: FormDrawerProps) => {
+ const { t } = useTranslation();
+
+ return (
+
+
+
+
+ }
+ mask={{ closable: false }}
+ onClose={onClose}
+ open={open}
+ styles={{ footer: { textAlign: "end" } }}
+ title={title}
+ >
+ {children}
+
+ );
+};
diff --git a/src/components/form-drawer/index.ts b/src/components/form-drawer/index.ts
new file mode 100644
index 0000000..61b16ca
--- /dev/null
+++ b/src/components/form-drawer/index.ts
@@ -0,0 +1 @@
+export * from "./FormDrawer";
diff --git a/src/components/Icon.tsx b/src/components/icon/Icon.tsx
similarity index 76%
rename from src/components/Icon.tsx
rename to src/components/icon/Icon.tsx
index 047b924..061e69a 100644
--- a/src/components/Icon.tsx
+++ b/src/components/icon/Icon.tsx
@@ -17,10 +17,12 @@ const iconMap = {
type IconName = keyof typeof iconMap;
-export const Icon: FC<{ name: IconName; size?: number }> = ({
- name,
- size = 16,
-}) => (
+type IconProps = {
+ name: IconName;
+ size?: number;
+};
+
+export const Icon: FC = ({ name, size = 16 }) => (
{iconMap[name]}
diff --git a/src/components/icon/index.ts b/src/components/icon/index.ts
new file mode 100644
index 0000000..65d727b
--- /dev/null
+++ b/src/components/icon/index.ts
@@ -0,0 +1 @@
+export { Icon } from "./Icon";
diff --git a/src/components/otp-input/OtpInput.test.tsx b/src/components/otp-input/OtpInput.test.tsx
new file mode 100644
index 0000000..a6510e5
--- /dev/null
+++ b/src/components/otp-input/OtpInput.test.tsx
@@ -0,0 +1,15 @@
+import { render } from "@testing-library/react";
+import { describe, expect, it } from "vitest";
+
+import { OtpInput } from "./";
+
+describe("OtpInput", () => {
+ it("does not forward aria-required to the OTP group", () => {
+ const { container } = render(
+ ,
+ );
+
+ expect(container.querySelectorAll("input")).toHaveLength(4);
+ expect(container.querySelector("[aria-required]")).not.toBeInTheDocument();
+ });
+});
diff --git a/src/components/otp-input/OtpInput.tsx b/src/components/otp-input/OtpInput.tsx
new file mode 100644
index 0000000..601c6a0
--- /dev/null
+++ b/src/components/otp-input/OtpInput.tsx
@@ -0,0 +1,10 @@
+import { Input } from "antd";
+import { ComponentProps, FC } from "react";
+
+const Otp = Input.OTP;
+
+type OtpInputProps = ComponentProps;
+
+export const OtpInput: FC = (props) => {
+ return ;
+};
diff --git a/src/components/otp-input/index.ts b/src/components/otp-input/index.ts
new file mode 100644
index 0000000..053decd
--- /dev/null
+++ b/src/components/otp-input/index.ts
@@ -0,0 +1 @@
+export { OtpInput } from "./OtpInput";
diff --git a/src/components/password-fields/PasswordFields.tsx b/src/components/password-fields/PasswordFields.tsx
new file mode 100644
index 0000000..eaaca82
--- /dev/null
+++ b/src/components/password-fields/PasswordFields.tsx
@@ -0,0 +1,51 @@
+import { Form, Input } from "antd";
+import { useTranslation } from "react-i18next";
+
+type PasswordFieldsProps = {
+ passwordLabel?: "newPass" | "password";
+ size?: "large";
+};
+
+export const PasswordFields = ({
+ passwordLabel = "password",
+ size,
+}: PasswordFieldsProps) => {
+ const { t } = useTranslation();
+
+ return (
+ <>
+
+
+
+ ({
+ validator(_, value) {
+ return !value || getFieldValue("password") === value
+ ? Promise.resolve()
+ : Promise.reject(new Error(t("passsMismatch")));
+ },
+ }),
+ ]}
+ >
+
+
+ >
+ );
+};
diff --git a/src/components/password-fields/index.ts b/src/components/password-fields/index.ts
new file mode 100644
index 0000000..4bf905b
--- /dev/null
+++ b/src/components/password-fields/index.ts
@@ -0,0 +1 @@
+export * from "./PasswordFields";
diff --git a/src/forms/UserPassword.tsx b/src/forms/UserPassword.tsx
deleted file mode 100644
index 98c68a6..0000000
--- a/src/forms/UserPassword.tsx
+++ /dev/null
@@ -1,111 +0,0 @@
-/* eslint-disable react-hooks/set-state-in-effect */
-import { Button, Drawer, Form, FormProps, Input, Space } from "antd";
-import { FC, useEffect, useState } from "react";
-import { useTranslation } from "react-i18next";
-import { useLocation } from "react-router-dom";
-
-import { updateUserPassword } from "@/api";
-import { modalKeys } from "@/config";
-import { useAntd, useGoBack } from "@/hooks";
-import { UserPasswordParams, UserProps } from "@/types";
-
-type UserPasswordFormParams = UserPasswordParams & {
- confirmPassword: string;
-};
-
-export const UserPasswordForm: FC<{ data?: UserProps }> = ({ data }) => {
- const { t } = useTranslation();
- const [open, setOpen] = useState(false);
- const [submitting, setSubmitting] = useState(false);
- const { messageAPI } = useAntd();
- const { hash } = useLocation();
- const [form] = Form.useForm();
- const goBack = useGoBack();
-
- const handleSubmit: FormProps["onFinish"] = async ({
- password,
- }) => {
- if (submitting || !data) return;
-
- try {
- setSubmitting(true);
- await updateUserPassword(data.id, { password });
- messageAPI.success(t("passwordUpdated"));
- goBack();
- } catch (error) {
- if (error instanceof Error) messageAPI.error(error.message);
- else console.error(error);
- } finally {
- setSubmitting(false);
- }
- };
-
- useEffect(() => {
- if (hash === modalKeys.password) {
- if (data) setOpen(true);
- else goBack();
- } else {
- if (open) form.resetFields();
- setOpen(false);
- setSubmitting(false);
- }
- }, [data, form, goBack, hash, open]);
-
- return (
-
-
-
-
- }
- mask={{ closable: false }}
- onClose={() => goBack()}
- open={open}
- styles={{ footer: { textAlign: "end" } }}
- title={t("changePassword")}
- >
-
-
-
- ({
- validator(_, value) {
- if (!value || getFieldValue("password") === value) {
- return Promise.resolve();
- }
-
- return Promise.reject(new Error(t("passsMismatch")));
- },
- }),
- ]}
- >
-
-
-
-
- );
-};
diff --git a/src/forms/forms.test.tsx b/src/forms/forms.test.tsx
new file mode 100644
index 0000000..2e8a06f
--- /dev/null
+++ b/src/forms/forms.test.tsx
@@ -0,0 +1,254 @@
+import { waitFor } from "@testing-library/react";
+import { MemoryRouter } from "react-router";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+
+import * as api from "@/api";
+import { render, screen } from "@/test/render";
+import { UserProps } from "@/types";
+
+import { RoleForm } from "./role";
+import { UserForm } from "./user";
+import { UserPasswordForm } from "./user-password";
+import { UserFormRole } from "./user-role";
+import { UserWorkspaceForm } from "./user-workspace";
+
+const mocks = vi.hoisted(() => ({
+ goBack: vi.fn(),
+ messageError: vi.fn(),
+ messageSuccess: vi.fn(),
+}));
+
+vi.mock("@/api", () => ({
+ createRole: vi.fn(),
+ createUser: vi.fn(),
+ updateRole: vi.fn(),
+ updateUser: vi.fn(),
+ updateUserPassword: vi.fn(),
+ updateUserRoles: vi.fn(),
+ updateUserWorkspaces: vi.fn(),
+}));
+
+vi.mock("@/hooks", () => ({
+ useAntd: () => ({
+ messageAPI: {
+ error: mocks.messageError,
+ success: mocks.messageSuccess,
+ },
+ }),
+ useGoBack: () => mocks.goBack,
+}));
+
+vi.mock("react-i18next", async (importOriginal) => ({
+ ...(await importOriginal()),
+ useTranslation: () => ({ t: (key: string) => key }),
+}));
+
+const userData: UserProps = {
+ email: "ada@example.com",
+ firstName: "Ada",
+ id: "user-1",
+ isSystemAdmin: false,
+ lastName: "Lovelace",
+ mobile: "09120000000",
+ personnelCode: "1001",
+ roleIds: ["role-1"],
+ username: "ada",
+ workspaceIds: ["workspace-1"],
+};
+const mismatchedCredential = "mismatched-test-credential";
+const replacementCredential = "replacement-test-credential";
+const syntheticCredential = "synthetic-test-credential";
+
+const renderAtHash = (ui: React.ReactNode, hash: string) =>
+ render({ui});
+
+beforeEach(() => {
+ vi.mocked(api.createRole).mockResolvedValue({
+ id: "role-1",
+ name: "Operators",
+ permissions: ["users.read"],
+ });
+ vi.mocked(api.createUser).mockResolvedValue(userData);
+ vi.mocked(api.updateRole).mockResolvedValue({
+ id: "role-1",
+ name: "Operators",
+ permissions: ["users.read"],
+ });
+ vi.mocked(api.updateUser).mockResolvedValue(userData);
+ vi.mocked(api.updateUserPassword).mockResolvedValue(undefined);
+ vi.mocked(api.updateUserRoles).mockResolvedValue(undefined);
+ vi.mocked(api.updateUserWorkspaces).mockResolvedValue(undefined);
+});
+
+describe("management forms", () => {
+ it("validates and creates a user", async () => {
+ const onFinish = vi.fn();
+ const { user } = renderAtHash(, "#create");
+
+ await user.type(await screen.findByLabelText("firstName"), "Ada");
+ await user.type(screen.getByLabelText("lastName"), "Lovelace");
+ await user.type(screen.getByLabelText("mobile"), "09120000000");
+ await user.type(screen.getByLabelText("personnelCode"), "1001");
+ await user.type(screen.getByLabelText("password"), syntheticCredential);
+ await user.type(screen.getByLabelText("confirmPass"), syntheticCredential);
+ await user.click(screen.getByRole("button", { name: "submit" }));
+
+ await waitFor(() =>
+ expect(api.createUser).toHaveBeenCalledWith({
+ firstName: "Ada",
+ lastName: "Lovelace",
+ mobile: "09120000000",
+ password: syntheticCredential,
+ personnelCode: "1001",
+ }),
+ );
+ expect(mocks.messageSuccess).toHaveBeenCalledWith("userCreated");
+ expect(mocks.goBack).toHaveBeenCalledOnce();
+ expect(onFinish).toHaveBeenCalledOnce();
+ });
+
+ it("rejects mismatched user passwords", async () => {
+ const { user } = renderAtHash(, "#create");
+
+ await user.type(await screen.findByLabelText("firstName"), "Ada");
+ await user.type(screen.getByLabelText("lastName"), "Lovelace");
+ await user.type(screen.getByLabelText("mobile"), "09120000000");
+ await user.type(screen.getByLabelText("personnelCode"), "1001");
+ await user.type(screen.getByLabelText("password"), syntheticCredential);
+ await user.type(screen.getByLabelText("confirmPass"), mismatchedCredential);
+ await user.click(screen.getByRole("button", { name: "submit" }));
+
+ expect(await screen.findByText("passsMismatch")).toBeInTheDocument();
+ expect(api.createUser).not.toHaveBeenCalled();
+ });
+
+ it("updates a user without submitting password fields", async () => {
+ const onFinish = vi.fn();
+ const { user } = renderAtHash(
+ ,
+ "#update",
+ );
+
+ const firstName = await screen.findByLabelText("firstName");
+ await user.clear(firstName);
+ await user.type(firstName, "Augusta");
+ await user.click(screen.getByRole("button", { name: "submit" }));
+
+ await waitFor(() =>
+ expect(api.updateUser).toHaveBeenCalledWith("user-1", {
+ firstName: "Augusta",
+ lastName: "Lovelace",
+ mobile: "09120000000",
+ personnelCode: "1001",
+ }),
+ );
+ expect(screen.queryByLabelText("password")).not.toBeInTheDocument();
+ expect(mocks.messageSuccess).toHaveBeenCalledWith("userUpdated");
+ expect(onFinish).toHaveBeenCalledOnce();
+ });
+
+ it("creates a role with selected permissions and reports failures", async () => {
+ vi.mocked(api.createRole).mockRejectedValueOnce(
+ new Error("Role already exists"),
+ );
+ const { user } = renderAtHash(
+ ,
+ "#create",
+ );
+
+ await user.type(await screen.findByLabelText("name"), "Operators");
+ await user.click(screen.getByLabelText("Read users"));
+ await user.click(screen.getByRole("button", { name: "submit" }));
+
+ await waitFor(() =>
+ expect(api.createRole).toHaveBeenCalledWith({
+ name: "Operators",
+ permissions: ["users.read"],
+ }),
+ );
+ expect(mocks.messageError).toHaveBeenCalledWith("Role already exists");
+ });
+
+ it("updates a user's password", async () => {
+ const { user } = renderAtHash(
+ ,
+ "#password",
+ );
+
+ await user.type(
+ await screen.findByLabelText("newPass"),
+ replacementCredential,
+ );
+ await user.type(
+ screen.getByLabelText("confirmPass"),
+ replacementCredential,
+ );
+ await user.click(screen.getByRole("button", { name: "submit" }));
+
+ await waitFor(() =>
+ expect(api.updateUserPassword).toHaveBeenCalledWith("user-1", {
+ password: replacementCredential,
+ }),
+ );
+ expect(mocks.messageSuccess).toHaveBeenCalledWith("passwordUpdated");
+ });
+
+ it("submits the user's current roles", async () => {
+ const onFinish = vi.fn();
+ const { user } = renderAtHash(
+ ,
+ "#roles",
+ );
+
+ await screen.findByText("Operators");
+ await user.click(screen.getByRole("button", { name: "submit" }));
+
+ await waitFor(() =>
+ expect(api.updateUserRoles).toHaveBeenCalledWith("user-1", {
+ roleIds: ["role-1"],
+ }),
+ );
+ expect(mocks.messageSuccess).toHaveBeenCalledWith("rolesUpdated");
+ expect(onFinish).toHaveBeenCalledOnce();
+ });
+
+ it("submits the user's current workspaces", async () => {
+ const onFinish = vi.fn();
+ const { user } = renderAtHash(
+ ,
+ "#workspaces",
+ );
+
+ await screen.findByText("Analytical Engine");
+ await user.click(screen.getByRole("button", { name: "submit" }));
+
+ await waitFor(() =>
+ expect(api.updateUserWorkspaces).toHaveBeenCalledWith("user-1", {
+ workspaceIds: ["workspace-1"],
+ }),
+ );
+ expect(mocks.messageSuccess).toHaveBeenCalledWith("workspacesUpdated");
+ expect(onFinish).toHaveBeenCalledOnce();
+ });
+});
diff --git a/src/forms/Role.tsx b/src/forms/role/Role.tsx
similarity index 74%
rename from src/forms/Role.tsx
rename to src/forms/role/Role.tsx
index e4fe2e5..10b3c67 100644
--- a/src/forms/Role.tsx
+++ b/src/forms/role/Role.tsx
@@ -1,29 +1,27 @@
/* eslint-disable react-hooks/set-state-in-effect */
-import {
- Button,
- Checkbox,
- Divider,
- Drawer,
- Flex,
- Form,
- FormProps,
- Input,
- Space,
-} from "antd";
+import { Checkbox, Divider, Flex, Form, Input } from "antd";
import { FC, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
-import { useLocation } from "react-router-dom";
+import { useLocation } from "react-router";
import { createRole, updateRole } from "@/api";
+import { FormDrawer } from "@/components/form-drawer";
import { modalKeys } from "@/config";
import { useAntd, useGoBack } from "@/hooks";
import { PermissionGroupProps, RoleMutationParams, RoleProps } from "@/types";
+import { getErrorMessage } from "@/utils";
-export const RoleForm: FC<{
+type RoleFormProps = {
data?: RoleProps;
onFinish: () => void;
options: { permissions: PermissionGroupProps[] };
-}> = ({ data, onFinish, options: { permissions } }) => {
+};
+
+export const RoleForm: FC = ({
+ data,
+ onFinish,
+ options: { permissions },
+}) => {
const { t } = useTranslation();
const [open, setOpen] = useState(false);
const [submitting, setSubmitting] = useState(false);
@@ -33,9 +31,7 @@ export const RoleForm: FC<{
const goBack = useGoBack();
const isUpdate = hash === modalKeys.update && !!data;
- const handleSubmit: FormProps["onFinish"] = async (
- values,
- ) => {
+ const handleSubmit = async (values: RoleMutationParams) => {
if (submitting) return;
try {
@@ -46,8 +42,7 @@ export const RoleForm: FC<{
goBack();
onFinish();
} catch (error) {
- if (error instanceof Error) messageAPI.error(error.message);
- else console.error(error);
+ messageAPI.error(getErrorMessage(error));
} finally {
setSubmitting(false);
}
@@ -77,33 +72,21 @@ export const RoleForm: FC<{
}, [data, form, goBack, hash, open]);
return (
-
-
-
-
- }
- mask={{ closable: false }}
- onClose={() => goBack()}
+ {
+ if (isOpen) form.focusField("name");
+ }}
+ onClose={goBack}
+ onSubmit={() => form.submit()}
open={open}
- styles={{ footer: { textAlign: "end" } }}
+ submitting={submitting}
title={t(isUpdate ? "update" : "create")}
>
@@ -134,6 +117,6 @@ export const RoleForm: FC<{
-
+
);
};
diff --git a/src/forms/role/index.ts b/src/forms/role/index.ts
new file mode 100644
index 0000000..0a9c9bc
--- /dev/null
+++ b/src/forms/role/index.ts
@@ -0,0 +1 @@
+export { RoleForm } from "./Role";
diff --git a/src/forms/user-password/UserPassword.tsx b/src/forms/user-password/UserPassword.tsx
new file mode 100644
index 0000000..bcd5c7a
--- /dev/null
+++ b/src/forms/user-password/UserPassword.tsx
@@ -0,0 +1,75 @@
+/* eslint-disable react-hooks/set-state-in-effect */
+import { Form } from "antd";
+import { FC, useEffect, useState } from "react";
+import { useTranslation } from "react-i18next";
+import { useLocation } from "react-router";
+
+import { updateUserPassword } from "@/api";
+import { FormDrawer } from "@/components/form-drawer";
+import { PasswordFields } from "@/components/password-fields";
+import { modalKeys } from "@/config";
+import { useAntd, useGoBack } from "@/hooks";
+import { UserPasswordParams, UserProps } from "@/types";
+import { getErrorMessage } from "@/utils";
+
+type UserPasswordFormParams = UserPasswordParams & {
+ confirmPassword: string;
+};
+
+type UserPasswordFormProps = {
+ data?: UserProps;
+};
+
+export const UserPasswordForm: FC = ({ data }) => {
+ const { t } = useTranslation();
+ const [open, setOpen] = useState(false);
+ const [submitting, setSubmitting] = useState(false);
+ const { messageAPI } = useAntd();
+ const { hash } = useLocation();
+ const [form] = Form.useForm();
+ const goBack = useGoBack();
+
+ const handleSubmit = async ({ password }: UserPasswordFormParams) => {
+ if (submitting || !data) return;
+
+ try {
+ setSubmitting(true);
+ await updateUserPassword(data.id, { password });
+ messageAPI.success(t("passwordUpdated"));
+ goBack();
+ } catch (error) {
+ messageAPI.error(getErrorMessage(error));
+ } finally {
+ setSubmitting(false);
+ }
+ };
+
+ useEffect(() => {
+ if (hash === modalKeys.password) {
+ if (data) setOpen(true);
+ else goBack();
+ } else {
+ if (open) form.resetFields();
+ setOpen(false);
+ setSubmitting(false);
+ }
+ }, [data, form, goBack, hash, open]);
+
+ return (
+ form.submit()}
+ open={open}
+ submitting={submitting}
+ title={t("changePassword")}
+ >
+
+
+ );
+};
diff --git a/src/forms/user-password/index.ts b/src/forms/user-password/index.ts
new file mode 100644
index 0000000..8838a47
--- /dev/null
+++ b/src/forms/user-password/index.ts
@@ -0,0 +1 @@
+export { UserPasswordForm } from "./UserPassword";
diff --git a/src/forms/UserRole.tsx b/src/forms/user-role/UserRole.tsx
similarity index 68%
rename from src/forms/UserRole.tsx
rename to src/forms/user-role/UserRole.tsx
index 646643f..e49fba6 100644
--- a/src/forms/UserRole.tsx
+++ b/src/forms/user-role/UserRole.tsx
@@ -1,13 +1,15 @@
/* eslint-disable react-hooks/set-state-in-effect */
-import { Button, Drawer, Form, FormProps, Select, Space } from "antd";
+import { Form, Select } from "antd";
import { FC, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
-import { useLocation } from "react-router-dom";
+import { useLocation } from "react-router";
import { updateUserRoles } from "@/api";
+import { FormDrawer } from "@/components/form-drawer";
import { modalKeys } from "@/config";
import { useAntd, useGoBack } from "@/hooks";
import { UserOptionProps, UserProps, UserRoleParams } from "@/types";
+import { getErrorMessage } from "@/utils";
type UserFormRoleProps = {
data?: UserProps;
@@ -28,9 +30,7 @@ export const UserFormRole: FC = ({
const [form] = Form.useForm();
const goBack = useGoBack();
- const handleSubmit: FormProps["onFinish"] = async (
- values,
- ) => {
+ const handleSubmit = async (values: UserRoleParams) => {
if (submitting || !data) return;
try {
@@ -40,8 +40,7 @@ export const UserFormRole: FC = ({
goBack();
onFinish();
} catch (error) {
- if (error instanceof Error) messageAPI.error(error.message);
- else console.error(error);
+ messageAPI.error(getErrorMessage(error));
} finally {
setSubmitting(false);
}
@@ -63,32 +62,17 @@ export const UserFormRole: FC = ({
}, [data, form, goBack, hash, open]);
return (
-
-
-
-
- }
- mask={{ closable: false }}
- onClose={() => goBack()}
+ form.submit()}
open={open}
+ submitting={submitting}
title={t("roles")}
- styles={{ footer: { textAlign: "end" } }}
>
-
+
);
};
diff --git a/src/forms/user-role/index.ts b/src/forms/user-role/index.ts
new file mode 100644
index 0000000..1ab45a7
--- /dev/null
+++ b/src/forms/user-role/index.ts
@@ -0,0 +1 @@
+export { UserFormRole } from "./UserRole";
diff --git a/src/forms/UserWorkspace.tsx b/src/forms/user-workspace/UserWorkspace.tsx
similarity index 69%
rename from src/forms/UserWorkspace.tsx
rename to src/forms/user-workspace/UserWorkspace.tsx
index 313c404..5da0a94 100644
--- a/src/forms/UserWorkspace.tsx
+++ b/src/forms/user-workspace/UserWorkspace.tsx
@@ -1,13 +1,15 @@
/* eslint-disable react-hooks/set-state-in-effect */
-import { Button, Drawer, Form, FormProps, Select, Space } from "antd";
+import { Form, Select } from "antd";
import { FC, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
-import { useLocation } from "react-router-dom";
+import { useLocation } from "react-router";
import { updateUserWorkspaces } from "@/api";
+import { FormDrawer } from "@/components/form-drawer";
import { modalKeys } from "@/config";
import { useAntd, useGoBack } from "@/hooks";
import { UserOptionProps, UserProps, UserWorkspaceParams } from "@/types";
+import { getErrorMessage } from "@/utils";
type UserWorkspaceFormProps = {
data?: UserProps;
@@ -28,9 +30,7 @@ export const UserWorkspaceForm: FC = ({
const [form] = Form.useForm();
const goBack = useGoBack();
- const handleSubmit: FormProps["onFinish"] = async (
- values,
- ) => {
+ const handleSubmit = async (values: UserWorkspaceParams) => {
if (submitting || !data) return;
try {
@@ -40,8 +40,7 @@ export const UserWorkspaceForm: FC = ({
goBack();
onFinish();
} catch (error) {
- if (error instanceof Error) messageAPI.error(error.message);
- else console.error(error);
+ messageAPI.error(getErrorMessage(error));
} finally {
setSubmitting(false);
}
@@ -63,32 +62,17 @@ export const UserWorkspaceForm: FC = ({
}, [data, form, goBack, hash, open]);
return (
-
-
-
-
- }
- mask={{ closable: false }}
- onClose={() => goBack()}
+ form.submit()}
open={open}
+ submitting={submitting}
title={t("workspaces")}
- styles={{ footer: { textAlign: "end" } }}
>
-
+
);
};
diff --git a/src/forms/user-workspace/index.ts b/src/forms/user-workspace/index.ts
new file mode 100644
index 0000000..a7e40a9
--- /dev/null
+++ b/src/forms/user-workspace/index.ts
@@ -0,0 +1 @@
+export { UserWorkspaceForm } from "./UserWorkspace";
diff --git a/src/forms/User.tsx b/src/forms/user/User.tsx
similarity index 57%
rename from src/forms/User.tsx
rename to src/forms/user/User.tsx
index 58d5c79..d9f91d0 100644
--- a/src/forms/User.tsx
+++ b/src/forms/user/User.tsx
@@ -1,23 +1,28 @@
/* eslint-disable react-hooks/set-state-in-effect */
-import { Button, Drawer, Form, FormProps, Input, Space } from "antd";
+import { Form, Input } from "antd";
import { FC, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
-import { useLocation } from "react-router-dom";
+import { useLocation } from "react-router";
import { createUser, updateUser } from "@/api";
-import { DigitsInput } from "@/components/DigitsInput";
+import { DigitsInput } from "@/components/digits-input";
+import { FormDrawer } from "@/components/form-drawer";
+import { PasswordFields } from "@/components/password-fields";
import { modalKeys } from "@/config";
import { useAntd, useGoBack } from "@/hooks";
import { CreateUserParams, UpdateUserParams, UserProps } from "@/types";
+import { getErrorMessage } from "@/utils";
type UserFormParams = CreateUserParams & {
confirmPassword: string;
};
-export const UserForm: FC<{ data?: UserProps; onFinish: () => void }> = ({
- data,
- onFinish,
-}) => {
+type UserFormProps = {
+ data?: UserProps;
+ onFinish: () => void;
+};
+
+export const UserForm: FC = ({ data, onFinish }) => {
const { t } = useTranslation();
const [open, setOpen] = useState(false);
const [submitting, setSubmitting] = useState(false);
@@ -27,13 +32,13 @@ export const UserForm: FC<{ data?: UserProps; onFinish: () => void }> = ({
const goBack = useGoBack();
const isUpdate = hash === modalKeys.update && !!data;
- const handleSubmit: FormProps["onFinish"] = async ({
+ const handleSubmit = async ({
firstName,
lastName,
mobile,
password,
personnelCode,
- }) => {
+ }: UserFormParams) => {
if (submitting) return;
try {
@@ -51,8 +56,7 @@ export const UserForm: FC<{ data?: UserProps; onFinish: () => void }> = ({
goBack();
onFinish();
} catch (error) {
- if (error instanceof Error) messageAPI.error(error.message);
- else console.error(error);
+ messageAPI.error(getErrorMessage(error));
} finally {
setSubmitting(false);
}
@@ -82,32 +86,17 @@ export const UserForm: FC<{ data?: UserProps; onFinish: () => void }> = ({
}, [data, form, goBack, hash, open]);
return (
-
-
-
-
- }
- mask={{ closable: false }}
- onClose={() => goBack()}
+ form.submit()}
open={open}
+ submitting={submitting}
title={t(isUpdate ? "update" : "create")}
- styles={{ footer: { textAlign: "end" } }}
>
void }> = ({
>
- {!isUpdate && (
- <>
-
-
-
- ({
- validator(_, value) {
- if (!value || getFieldValue("password") === value) {
- return Promise.resolve();
- }
-
- return Promise.reject(new Error(t("passsMismatch")));
- },
- }),
- ]}
- >
-
-
- >
- )}
+ {!isUpdate && }
-
+
);
};
diff --git a/src/forms/user/index.ts b/src/forms/user/index.ts
new file mode 100644
index 0000000..9066b86
--- /dev/null
+++ b/src/forms/user/index.ts
@@ -0,0 +1 @@
+export { UserForm } from "./User";
diff --git a/src/hooks/index.ts b/src/hooks/index.ts
index fd9a0d0..327e02c 100644
--- a/src/hooks/index.ts
+++ b/src/hooks/index.ts
@@ -1,6 +1,6 @@
-export * from "./useActionPermissions";
-export * from "./useAllowedRoutes";
-export * from "./useAntd";
-export * from "./useCore";
-export * from "./useFilterParams";
-export * from "./useGoBack";
+export * from "./use-action-permissions";
+export * from "./use-allowed-routes";
+export * from "./use-antd";
+export * from "./use-core";
+export * from "./use-filter-params";
+export * from "./use-go-back";
diff --git a/src/hooks/use-action-permissions/index.ts b/src/hooks/use-action-permissions/index.ts
new file mode 100644
index 0000000..4fe5e63
--- /dev/null
+++ b/src/hooks/use-action-permissions/index.ts
@@ -0,0 +1 @@
+export { useActionPermissions } from "./useActionPermissions";
diff --git a/src/hooks/use-action-permissions/useActionPermissions.test.tsx b/src/hooks/use-action-permissions/useActionPermissions.test.tsx
new file mode 100644
index 0000000..ae7fa79
--- /dev/null
+++ b/src/hooks/use-action-permissions/useActionPermissions.test.tsx
@@ -0,0 +1,83 @@
+import { renderHook } from "@testing-library/react";
+import { ReactNode } from "react";
+import { describe, expect, it } from "vitest";
+
+import { CoreContext, CoreContextProps } from "@/contexts";
+import { AccountProps } from "@/types";
+
+import { useActionPermissions } from "./";
+
+const createUser = (overrides: Partial = {}): AccountProps => ({
+ email: null,
+ firstName: "Test",
+ id: "user-1",
+ isSystemAdmin: false,
+ lastName: "User",
+ mobile: "09120000000",
+ permissions: [],
+ personnelCode: null,
+ status: "active",
+ username: "test-user",
+ ...overrides,
+});
+
+const renderActionPermissions = (user?: AccountProps) => {
+ const value: CoreContextProps = {
+ currentRoute: "root",
+ language: "en",
+ setCurrentRoute: () => undefined,
+ setLanguage: () => undefined,
+ setTheme: () => undefined,
+ setUser: () => undefined,
+ theme: "light",
+ user,
+ };
+ const wrapper = ({ children }: { children: ReactNode }) => (
+ {children}
+ );
+
+ return renderHook(useActionPermissions, { wrapper }).result.current;
+};
+
+describe("useActionPermissions", () => {
+ it("denies every action for an unauthenticated user", () => {
+ expect(renderActionPermissions()).toEqual({
+ canCreateRoles: false,
+ canCreateUsers: false,
+ canDeleteRoles: false,
+ canDeleteUsers: false,
+ canUpdateRoles: false,
+ canUpdateUsers: false,
+ });
+ });
+
+ it("allows only explicitly granted actions", () => {
+ expect(
+ renderActionPermissions(
+ createUser({
+ permissions: ["roles.create", "users.delete", "users.update"],
+ }),
+ ),
+ ).toEqual({
+ canCreateRoles: true,
+ canCreateUsers: false,
+ canDeleteRoles: false,
+ canDeleteUsers: true,
+ canUpdateRoles: false,
+ canUpdateUsers: true,
+ });
+ });
+
+ it("allows every action for a system administrator", () => {
+ expect(
+ renderActionPermissions(createUser({ isSystemAdmin: true })),
+ ).toEqual({
+ canCreateRoles: true,
+ canCreateUsers: true,
+ canDeleteRoles: true,
+ canDeleteUsers: true,
+ canUpdateRoles: true,
+ canUpdateUsers: true,
+ });
+ });
+});
diff --git a/src/hooks/useActionPermissions.ts b/src/hooks/use-action-permissions/useActionPermissions.ts
similarity index 86%
rename from src/hooks/useActionPermissions.ts
rename to src/hooks/use-action-permissions/useActionPermissions.ts
index 49b7af1..1ad82bc 100644
--- a/src/hooks/useActionPermissions.ts
+++ b/src/hooks/use-action-permissions/useActionPermissions.ts
@@ -1,8 +1,6 @@
import { useMemo } from "react";
-import { PermissionKey } from "@/types";
-
-import { useCore } from "./useCore";
+import { useCore } from "@/hooks/use-core";
type ActionPermissions = {
canCreateRoles: boolean;
@@ -28,7 +26,7 @@ export const useActionPermissions = (): ActionPermissions => {
};
}
- const permissions = new Set(user?.permissions);
+ const permissions = new Set(user?.permissions);
return {
canCreateRoles: permissions.has("roles.create"),
diff --git a/src/hooks/use-allowed-routes/index.ts b/src/hooks/use-allowed-routes/index.ts
new file mode 100644
index 0000000..13d0584
--- /dev/null
+++ b/src/hooks/use-allowed-routes/index.ts
@@ -0,0 +1 @@
+export { useAllowedRoutes } from "./useAllowedRoutes";
diff --git a/src/hooks/use-allowed-routes/useAllowedRoutes.test.tsx b/src/hooks/use-allowed-routes/useAllowedRoutes.test.tsx
new file mode 100644
index 0000000..60db01c
--- /dev/null
+++ b/src/hooks/use-allowed-routes/useAllowedRoutes.test.tsx
@@ -0,0 +1,92 @@
+import { renderHook } from "@testing-library/react";
+import { ReactNode } from "react";
+import { describe, expect, it } from "vitest";
+
+import { CoreContext, CoreContextProps } from "@/contexts";
+import { AccountProps } from "@/types";
+
+import { useAllowedRoutes } from "./";
+
+const createUser = (overrides: Partial = {}): AccountProps => ({
+ email: null,
+ firstName: "Test",
+ id: "user-1",
+ isSystemAdmin: false,
+ lastName: "User",
+ mobile: "09120000000",
+ permissions: [],
+ personnelCode: null,
+ status: "active",
+ username: "test-user",
+ ...overrides,
+});
+
+const renderAllowedRoutes = (user?: AccountProps) => {
+ const value: CoreContextProps = {
+ currentRoute: "root",
+ language: "en",
+ setCurrentRoute: () => undefined,
+ setLanguage: () => undefined,
+ setTheme: () => undefined,
+ setUser: () => undefined,
+ theme: "light",
+ user,
+ };
+ const wrapper = ({ children }: { children: ReactNode }) => (
+ {children}
+ );
+
+ return renderHook(useAllowedRoutes, { wrapper }).result.current;
+};
+
+describe("useAllowedRoutes", () => {
+ it("allows only public routes for an unauthenticated user", () => {
+ const routes = renderAllowedRoutes();
+
+ expect(routes).toEqual(
+ new Set(["auth", "forgotPassword", "notFound", "register"]),
+ );
+ });
+
+ it("allows authenticated routes and routes with granted permissions", () => {
+ const routes = renderAllowedRoutes(
+ createUser({ permissions: ["users.read"] }),
+ );
+
+ expect(routes).toEqual(
+ new Set([
+ "account",
+ "auth",
+ "forgotPassword",
+ "notFound",
+ "register",
+ "root",
+ "users",
+ ]),
+ );
+ });
+
+ it("denies routes when the required permission is absent", () => {
+ const routes = renderAllowedRoutes(createUser());
+
+ expect(routes.has("users")).toBe(false);
+ expect(routes.has("roles")).toBe(false);
+ });
+
+ it("allows every route for a system administrator", () => {
+ const routes = renderAllowedRoutes(createUser({ isSystemAdmin: true }));
+
+ expect(routes).toEqual(
+ new Set([
+ "account",
+ "auth",
+ "forgotPassword",
+ "notFound",
+ "register",
+ "roles",
+ "root",
+ "users",
+ ]),
+ );
+ });
+});
diff --git a/src/hooks/useAllowedRoutes.ts b/src/hooks/use-allowed-routes/useAllowedRoutes.ts
similarity index 81%
rename from src/hooks/useAllowedRoutes.ts
rename to src/hooks/use-allowed-routes/useAllowedRoutes.ts
index b86ebb4..f82bbe5 100644
--- a/src/hooks/useAllowedRoutes.ts
+++ b/src/hooks/use-allowed-routes/useAllowedRoutes.ts
@@ -1,13 +1,9 @@
import { useMemo } from "react";
import { RouteKey } from "@/config";
-import { PermissionKey } from "@/types";
+import { useCore } from "@/hooks/use-core";
-import { useCore } from "./useCore";
-
-type RouteAccess = "auth" | "public" | PermissionKey;
-
-const ROUTE_RULES: Record = {
+const ROUTE_RULES: Record = {
account: "auth",
auth: "public",
forgotPassword: "public",
diff --git a/src/hooks/use-antd/index.ts b/src/hooks/use-antd/index.ts
new file mode 100644
index 0000000..babfa0a
--- /dev/null
+++ b/src/hooks/use-antd/index.ts
@@ -0,0 +1 @@
+export { useAntd } from "./useAntd";
diff --git a/src/hooks/useAntd.ts b/src/hooks/use-antd/useAntd.ts
similarity index 100%
rename from src/hooks/useAntd.ts
rename to src/hooks/use-antd/useAntd.ts
diff --git a/src/hooks/use-core/index.ts b/src/hooks/use-core/index.ts
new file mode 100644
index 0000000..5fd5b17
--- /dev/null
+++ b/src/hooks/use-core/index.ts
@@ -0,0 +1 @@
+export { useCore } from "./useCore";
diff --git a/src/hooks/useCore.ts b/src/hooks/use-core/useCore.ts
similarity index 100%
rename from src/hooks/useCore.ts
rename to src/hooks/use-core/useCore.ts
diff --git a/src/hooks/use-filter-params/index.ts b/src/hooks/use-filter-params/index.ts
new file mode 100644
index 0000000..ff974ee
--- /dev/null
+++ b/src/hooks/use-filter-params/index.ts
@@ -0,0 +1 @@
+export { useFilterParams } from "./useFilterParams";
diff --git a/src/hooks/useFilterParams.ts b/src/hooks/use-filter-params/useFilterParams.ts
similarity index 57%
rename from src/hooks/useFilterParams.ts
rename to src/hooks/use-filter-params/useFilterParams.ts
index 493b3c4..a46d60b 100644
--- a/src/hooks/useFilterParams.ts
+++ b/src/hooks/use-filter-params/useFilterParams.ts
@@ -1,5 +1,5 @@
import { useCallback, useMemo } from "react";
-import { useSearchParams } from "react-router-dom";
+import { useSearchParams } from "react-router";
export const useFilterParams = >() => {
const [searchParams, setSearchParams] = useSearchParams();
@@ -17,12 +17,19 @@ export const useFilterParams = >() => {
const setFilters = useCallback(
(newFilters: Partial) => {
- const sanitized = Object.fromEntries(
- Object.entries(newFilters).filter(
- ([, v]) => v !== undefined && v !== null && v !== "",
- ),
- );
- setSearchParams(sanitized);
+ setSearchParams((current) => {
+ const next = new URLSearchParams(current);
+
+ for (const [key, value] of Object.entries(newFilters)) {
+ if (value === undefined || value === null || value === "") {
+ next.delete(key);
+ } else {
+ next.set(key, value);
+ }
+ }
+
+ return next;
+ });
},
[setSearchParams],
);
diff --git a/src/hooks/use-go-back/index.ts b/src/hooks/use-go-back/index.ts
new file mode 100644
index 0000000..069cfd6
--- /dev/null
+++ b/src/hooks/use-go-back/index.ts
@@ -0,0 +1 @@
+export { useGoBack } from "./useGoBack";
diff --git a/src/hooks/useGoBack.ts b/src/hooks/use-go-back/useGoBack.ts
similarity index 84%
rename from src/hooks/useGoBack.ts
rename to src/hooks/use-go-back/useGoBack.ts
index a43eb52..04eae3b 100644
--- a/src/hooks/useGoBack.ts
+++ b/src/hooks/use-go-back/useGoBack.ts
@@ -1,10 +1,5 @@
import { useCallback } from "react";
-import {
- NavigateOptions,
- To,
- useLocation,
- useNavigate,
-} from "react-router-dom";
+import { NavigateOptions, To, useLocation, useNavigate } from "react-router";
export const useGoBack = (): ((to?: To, options?: NavigateOptions) => void) => {
const { pathname, state } = useLocation();
diff --git a/src/layouts/Default.tsx b/src/layouts/Default.tsx
deleted file mode 100644
index d055b41..0000000
--- a/src/layouts/Default.tsx
+++ /dev/null
@@ -1,178 +0,0 @@
-import {
- Avatar,
- Button,
- Divider,
- Drawer,
- Dropdown,
- Flex,
- Grid,
- Menu,
- Spin,
- Typography,
-} from "antd";
-import { useAntdToken } from "antd-style";
-import { useCallback, useEffect, useState } from "react";
-import { useTranslation } from "react-i18next";
-import { Link, Outlet, useNavigate } from "react-router-dom";
-
-import { getAccount, logout, setUnauthorizedHandler } from "@/api";
-import { clearAccessToken } from "@/api/token";
-import { Icon } from "@/components/Icon";
-import { RouteKey, routeTree } from "@/config";
-import { useAllowedRoutes, useCore } from "@/hooks";
-
-const { useBreakpoint } = Grid;
-
-export const DefaultLayout = () => {
- const { t } = useTranslation();
- const [open, setOpen] = useState(false);
- const { lg } = useBreakpoint();
- const { currentRoute, setTheme, setUser, theme: coreTheme, user } = useCore();
- const token = useAntdToken();
- const navigate = useNavigate();
- const allowedRoutes = useAllowedRoutes();
- const darkMode = coreTheme === "dark";
-
- const menuItemLabels: Partial> = {
- root: t("dashboard"),
- users: t("users"),
- roles: t("roles"),
- };
-
- const menuItems = (Object.entries(menuItemLabels) as [RouteKey, string][])
- .filter(([key]) => allowedRoutes.has(key))
- .map(([key, label]) => ({
- key,
- label: {label},
- }));
-
- const clearSession = useCallback(() => {
- clearAccessToken();
- setUser();
-
- navigate(routeTree.auth.path, { replace: true });
- }, [navigate, setUser]);
-
- const handleLogout = async () => {
- await logout().catch(() => undefined);
-
- clearSession();
- };
-
- useEffect(() => {
- void (async () => {
- try {
- const user = await getAccount();
-
- setUser(user);
- } catch {
- clearSession();
- }
- })();
-
- setUnauthorizedHandler(clearSession);
-
- return () => setUnauthorizedHandler(null);
- }, [clearSession, setUser]);
-
- if (!user) return ;
-
- return (
- <>
-
-
-
- kernel
-
-
- {lg ? (
-
- ) : (
-
- }
- onClick={() => setOpen(true)}
- type="text"
- />
-
- )}
- ,
- key: "1",
- label: t("account"),
- onClick: () =>
- navigate(routeTree.account.path, { replace: true }),
- },
- {
- icon: ,
- key: "2",
- label: t(darkMode ? "lightMode" : "darkMode"),
- onClick: () => setTheme(darkMode ? "light" : "dark"),
- },
- {
- danger: true,
- icon: ,
- key: "3",
- label: t("logout"),
- onClick: () => void handleLogout(),
- },
- ],
- }}
- popupRender={(menu) => (
- <>
-
-
-
- {`${user.firstName} ${user.lastName}`.trim()}
-
-
-
- {menu}
- >
- )}
- >
- } style={{ cursor: "pointer" }} />
-
-
-
- setOpen(false)}
- open={!lg && open}
- title={false}
- styles={{ body: { padding: 8 } }}
- >
-
-
- >
- );
-};
diff --git a/src/layouts/auth/Auth.test.tsx b/src/layouts/auth/Auth.test.tsx
new file mode 100644
index 0000000..476dbb1
--- /dev/null
+++ b/src/layouts/auth/Auth.test.tsx
@@ -0,0 +1,46 @@
+import { MemoryRouter, Route, Routes } from "react-router";
+import { describe, expect, it, vi } from "vitest";
+
+import { clearAccessToken } from "@/api/token";
+import { render, screen } from "@/test/render";
+
+import { AuthLayout } from "./Auth";
+
+const mocks = vi.hoisted(() => ({
+ setUser: vi.fn(),
+}));
+
+vi.mock("@/api/token", () => ({
+ clearAccessToken: vi.fn(),
+}));
+
+vi.mock("@/hooks", () => ({
+ useCore: () => ({ setUser: mocks.setUser }),
+}));
+
+vi.mock("antd-style", () => ({
+ useAntdToken: () => ({
+ colorBgBase: "#ffffff",
+ colorBgLayout: "#f5f5f5",
+ paddingSM: 8,
+ screenXS: 480,
+ }),
+}));
+
+describe("AuthLayout", () => {
+ it("clears the existing session and renders the auth route", () => {
+ render(
+
+
+ } path="/auth">
+ Sign in} />
+
+
+ ,
+ );
+
+ expect(screen.getByRole("heading", { name: "Sign in" })).toBeVisible();
+ expect(clearAccessToken).toHaveBeenCalledOnce();
+ expect(mocks.setUser).toHaveBeenCalledWith();
+ });
+});
diff --git a/src/layouts/Auth.tsx b/src/layouts/auth/Auth.tsx
similarity index 89%
rename from src/layouts/Auth.tsx
rename to src/layouts/auth/Auth.tsx
index e80941b..2d9c8c3 100644
--- a/src/layouts/Auth.tsx
+++ b/src/layouts/auth/Auth.tsx
@@ -1,7 +1,6 @@
-import { Flex } from "antd";
import { useAntdToken } from "antd-style";
import { useEffect } from "react";
-import { Outlet } from "react-router-dom";
+import { Outlet } from "react-router";
import { clearAccessToken } from "@/api/token";
import { useCore } from "@/hooks";
@@ -16,8 +15,9 @@ export const AuthLayout = () => {
}, [setUser]);
return (
- {
>
-
+
);
};
diff --git a/src/layouts/auth/index.ts b/src/layouts/auth/index.ts
new file mode 100644
index 0000000..d23055f
--- /dev/null
+++ b/src/layouts/auth/index.ts
@@ -0,0 +1 @@
+export { AuthLayout } from "./Auth";
diff --git a/src/layouts/default/Default.test.tsx b/src/layouts/default/Default.test.tsx
new file mode 100644
index 0000000..fa498e1
--- /dev/null
+++ b/src/layouts/default/Default.test.tsx
@@ -0,0 +1,125 @@
+import { waitFor } from "@testing-library/react";
+import { MemoryRouter, Route, Routes, useLocation } from "react-router";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+
+import { getAccount, logout, setUnauthorizedHandler } from "@/api";
+import { clearAccessToken } from "@/api/token";
+import { render, screen } from "@/test/render";
+
+import { DefaultLayout } from "./Default";
+
+const mocks = vi.hoisted(() => {
+ const account = {
+ email: "ada@example.com",
+ firstName: "Ada",
+ id: "user-1",
+ isSystemAdmin: false,
+ lastName: "Lovelace",
+ mobile: "09120000000",
+ permissions: ["users.read"],
+ personnelCode: "1001",
+ status: "active",
+ username: "ada",
+ };
+
+ return {
+ account,
+ core: {
+ currentRoute: "root",
+ setTheme: vi.fn(),
+ setUser: vi.fn(),
+ theme: "light",
+ user: account,
+ },
+ };
+});
+
+vi.mock("@/api", () => ({
+ getAccount: vi.fn(),
+ logout: vi.fn(),
+ setUnauthorizedHandler: vi.fn(),
+}));
+
+vi.mock("@/api/token", () => ({
+ clearAccessToken: vi.fn(),
+}));
+
+vi.mock("@/hooks", () => ({
+ useAllowedRoutes: () => new Set(["account", "root", "roles", "users"]),
+ useCore: () => mocks.core,
+}));
+
+vi.mock("antd-style", () => ({
+ useAntdToken: () => ({
+ colorBgContainer: "#ffffff",
+ colorTextBase: "#222222",
+ paddingSM: 8,
+ }),
+}));
+
+vi.mock("react-i18next", async (importOriginal) => ({
+ ...(await importOriginal()),
+ useTranslation: () => ({ t: (key: string) => key }),
+}));
+
+const Location = () => {
+ const location = useLocation();
+ return ;
+};
+
+const renderLayout = () =>
+ render(
+
+
+
+ } path="/">
+ Dashboard content} />
+
+ Authentication} path="/auth" />
+
+ ,
+ );
+
+beforeEach(() => {
+ mocks.core.theme = "light";
+ mocks.core.user = mocks.account;
+ vi.mocked(getAccount).mockResolvedValue(mocks.account);
+ vi.mocked(logout).mockResolvedValue(undefined);
+});
+
+describe("DefaultLayout", () => {
+ it("loads the account, registers session handling, and logs out", async () => {
+ const { unmount, user } = renderLayout();
+
+ expect(
+ screen.getByRole("heading", { name: "Dashboard content" }),
+ ).toBeVisible();
+ expect(screen.getByRole("link", { name: "kernel" })).toBeVisible();
+ await waitFor(() => expect(getAccount).toHaveBeenCalledOnce());
+ expect(mocks.core.setUser).toHaveBeenCalledWith(mocks.account);
+ expect(setUnauthorizedHandler).toHaveBeenCalledWith(expect.any(Function));
+
+ await user.click(screen.getByRole("button", { name: "account" }));
+ await user.click(await screen.findByText("logout"));
+
+ await waitFor(() => expect(logout).toHaveBeenCalledOnce());
+ expect(clearAccessToken).toHaveBeenCalledOnce();
+ expect(mocks.core.setUser).toHaveBeenLastCalledWith();
+ expect(screen.getByLabelText("location")).toHaveTextContent("/auth");
+
+ unmount();
+ expect(setUnauthorizedHandler).toHaveBeenLastCalledWith(null);
+ });
+
+ it("clears the session when loading the account fails", async () => {
+ vi.mocked(getAccount).mockRejectedValueOnce(new Error("Unauthorized"));
+
+ renderLayout();
+
+ await waitFor(() =>
+ expect(screen.getByLabelText("location")).toHaveTextContent("/auth"),
+ );
+ expect(clearAccessToken).toHaveBeenCalledOnce();
+ expect(mocks.core.setUser).toHaveBeenCalledWith();
+ });
+});
diff --git a/src/layouts/default/Default.tsx b/src/layouts/default/Default.tsx
new file mode 100644
index 0000000..1fdcad8
--- /dev/null
+++ b/src/layouts/default/Default.tsx
@@ -0,0 +1,191 @@
+import {
+ Avatar,
+ Button,
+ Divider,
+ Drawer,
+ Dropdown,
+ Flex,
+ Grid,
+ Menu,
+ Spin,
+ Typography,
+} from "antd";
+import { useAntdToken } from "antd-style";
+import { useCallback, useEffect, useState } from "react";
+import { useTranslation } from "react-i18next";
+import { Link, Outlet, useNavigate } from "react-router";
+
+import { getAccount, logout, setUnauthorizedHandler } from "@/api";
+import { clearAccessToken } from "@/api/token";
+import { Icon } from "@/components/icon";
+import { RouteKey, routeTree } from "@/config";
+import { useAllowedRoutes, useCore } from "@/hooks";
+
+const { useBreakpoint } = Grid;
+
+export const DefaultLayout = () => {
+ const { t } = useTranslation();
+ const [open, setOpen] = useState(false);
+ const { lg } = useBreakpoint();
+ const { currentRoute, setTheme, setUser, theme: coreTheme, user } = useCore();
+ const token = useAntdToken();
+ const navigate = useNavigate();
+ const allowedRoutes = useAllowedRoutes();
+ const darkMode = coreTheme === "dark";
+
+ const menuItemLabels: Partial> = {
+ root: t("dashboard"),
+ users: t("users"),
+ roles: t("roles"),
+ };
+
+ const menuItems = (Object.entries(menuItemLabels) as [RouteKey, string][])
+ .filter(([key]) => allowedRoutes.has(key))
+ .map(([key, label]) => ({
+ key,
+ label: {label},
+ }));
+
+ const clearSession = useCallback(() => {
+ clearAccessToken();
+ setUser();
+
+ navigate(routeTree.auth.path, { replace: true });
+ }, [navigate, setUser]);
+
+ const handleLogout = async () => {
+ await logout().catch(() => undefined);
+
+ clearSession();
+ };
+
+ useEffect(() => {
+ void (async () => {
+ try {
+ const user = await getAccount();
+
+ setUser(user);
+ } catch {
+ clearSession();
+ }
+ })();
+
+ setUnauthorizedHandler(clearSession);
+
+ return () => setUnauthorizedHandler(null);
+ }, [clearSession, setUser]);
+
+ if (!user) return ;
+
+ return (
+ <>
+
+
+
+
+ kernel
+
+
+ {lg ? (
+
+ ) : (
+
+ }
+ onClick={() => setOpen(true)}
+ type="text"
+ />
+
+ )}
+ ,
+ key: "1",
+ label: t("account"),
+ onClick: () =>
+ navigate(routeTree.account.path, { replace: true }),
+ },
+ {
+ icon: ,
+ key: "2",
+ label: t(darkMode ? "lightMode" : "darkMode"),
+ onClick: () => setTheme(darkMode ? "light" : "dark"),
+ },
+ {
+ danger: true,
+ icon: ,
+ key: "3",
+ label: t("logout"),
+ onClick: () => void handleLogout(),
+ },
+ ],
+ }}
+ popupRender={(menu) => (
+ <>
+
+
+
+ {`${user.firstName} ${user.lastName}`.trim()}
+
+
+
+ {menu}
+ >
+ )}
+ >
+ } />}
+ type="text"
+ />
+
+
+
+
+
+
+ setOpen(false)}
+ open={!lg && open}
+ title={false}
+ styles={{ body: { padding: 8 } }}
+ >
+
+
+ >
+ );
+};
diff --git a/src/layouts/default/index.ts b/src/layouts/default/index.ts
new file mode 100644
index 0000000..6e2fc83
--- /dev/null
+++ b/src/layouts/default/index.ts
@@ -0,0 +1 @@
+export { DefaultLayout } from "./Default";
diff --git a/src/locales/en.ts b/src/locales/en.ts
index bbe7bdc..e94d9e4 100644
--- a/src/locales/en.ts
+++ b/src/locales/en.ts
@@ -1,4 +1,5 @@
export const en = {
+ reload: "Reload",
action: "Action",
active: "Active",
account: "Account",
@@ -28,8 +29,10 @@ export const en = {
lightMode: "Light mode",
login: "Login",
logout: "Logout",
+ menu: "Menu",
mobile: "Mobile",
name: "Name",
+ networkError: "Network error. Please check your connection.",
newPass: "New password",
no: "No",
notFoundDescription: "Sorry, the page you visited does not exist.",
@@ -60,6 +63,7 @@ export const en = {
"Are you sure you want to change system administrator access?",
systemAdminUpdated: "System administrator access updated successfully.",
update: "Update",
+ unexpectedError: "An unexpected error occurred.",
userCreated: "User created successfully.",
userDeleted: "User deleted successfully.",
userUpdated: "User updated successfully.",
diff --git a/src/locales/fa.ts b/src/locales/fa.ts
index 6817102..ccf99fe 100644
--- a/src/locales/fa.ts
+++ b/src/locales/fa.ts
@@ -1,4 +1,5 @@
export const fa = {
+ reload: "بارگذاری مجدد",
account: "حساب کاربری",
backHome: "بازگشت به خانه",
backToLogin: "بازگشت به ورود",
@@ -39,7 +40,9 @@ export const fa = {
lightMode: "حالت روشن",
login: "ورود به سیستم",
logout: "خروج",
+ menu: "منو",
name: "نام",
+ networkError: "خطای شبکه. لطفاً اتصال اینترنت خود را بررسی کنید.",
newPass: "رمز عبور جدید",
no: "خیر",
passsMismatch: "تکرار رمز عبور با رمز عبور جدید مطابقت ندارد.",
@@ -54,6 +57,7 @@ export const fa = {
statusConfirm: "آیا از تغییر وضعیت مطمئن هستید؟",
submit: "ثبت",
update: "ویرایش",
+ unexpectedError: "خطای غیرمنتظرهای رخ داد.",
username: "نام کاربری",
users: "کاربران",
yes: "بله",
diff --git a/src/main.tsx b/src/main.tsx
index 5d06ac1..fecb6ba 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -1,8 +1,12 @@
import { StrictMode } from "react";
import ReactDOM from "react-dom/client";
+import { initializeObservability } from "@/utils";
+
import { App } from "./App";
+initializeObservability();
+
if (import.meta.env.DEV) {
ReactDOM.createRoot(document.getElementById("root")!).render();
} else {
diff --git a/src/pages/Account.tsx b/src/pages/account/Account.tsx
similarity index 80%
rename from src/pages/Account.tsx
rename to src/pages/account/Account.tsx
index 2c56948..92332b3 100644
--- a/src/pages/Account.tsx
+++ b/src/pages/account/Account.tsx
@@ -21,6 +21,7 @@ import {
updateUsername,
verifyEmail,
} from "@/api";
+import { OtpInput } from "@/components/otp-input";
import { useAntd, useCore } from "@/hooks";
import {
ChangePasswordParams,
@@ -28,6 +29,7 @@ import {
UpdateUsernameParams,
VerifyEmailParams,
} from "@/types";
+import { getErrorMessage } from "@/utils";
type PasswordFormParams = ChangePasswordParams & {
confirmPassword: string;
@@ -63,45 +65,42 @@ export const AccountPage = () => {
if (!user) return null;
const showError = (error: unknown) => {
- if (error instanceof Error) messageAPI.error(error.message);
- else console.error(error);
+ messageAPI.error(getErrorMessage(error));
};
- const handleProfileSubmit: FormProps["onFinish"] =
- async (values) => {
- if (profileSubmitting) return;
+ const submitProfile = async (values: UpdateProfileParams) => {
+ if (profileSubmitting) return;
- try {
- setProfileSubmitting(true);
+ try {
+ setProfileSubmitting(true);
- const account = await updateProfile(values);
+ const account = await updateProfile(values);
- setUser(account);
- messageAPI.success(t("profileUpdated"));
- } catch (error) {
- showError(error);
- } finally {
- setProfileSubmitting(false);
- }
- };
+ setUser(account);
+ messageAPI.success(t("profileUpdated"));
+ } catch (error) {
+ showError(error);
+ } finally {
+ setProfileSubmitting(false);
+ }
+ };
- const handleUsernameSubmit: FormProps["onFinish"] =
- async (values) => {
- if (usernameSubmitting) return;
+ const submitUsername = async (values: UpdateUsernameParams) => {
+ if (usernameSubmitting) return;
- try {
- setUsernameSubmitting(true);
+ try {
+ setUsernameSubmitting(true);
- const account = await updateUsername(values);
+ const account = await updateUsername(values);
- setUser(account);
- messageAPI.success(t("usernameUpdated"));
- } catch (error) {
- showError(error);
- } finally {
- setUsernameSubmitting(false);
- }
- };
+ setUser(account);
+ messageAPI.success(t("usernameUpdated"));
+ } catch (error) {
+ showError(error);
+ } finally {
+ setUsernameSubmitting(false);
+ }
+ };
const handleEmailVerificationRequest = async () => {
if (otpSubmitting || otpRemainingSeconds) return;
@@ -122,9 +121,7 @@ export const AccountPage = () => {
}
};
- const handleEmailSubmit: FormProps["onFinish"] = async (
- values,
- ) => {
+ const submitEmail = async (values: VerifyEmailParams) => {
if (emailSubmitting) return;
try {
@@ -144,23 +141,46 @@ export const AccountPage = () => {
}
};
- const handlePasswordSubmit: FormProps["onFinish"] =
- async ({ currentPassword, newPassword }) => {
- if (passwordSubmitting) return;
+ const submitPassword = async ({
+ currentPassword,
+ newPassword,
+ }: PasswordFormParams) => {
+ if (passwordSubmitting) return;
+
+ try {
+ setPasswordSubmitting(true);
- try {
- setPasswordSubmitting(true);
+ await changePassword({ currentPassword, newPassword });
- await changePassword({ currentPassword, newPassword });
+ messageAPI.success(t("passwordChanged"));
+ passwordForm.resetFields();
+ } catch (error) {
+ showError(error);
+ } finally {
+ setPasswordSubmitting(false);
+ }
+ };
- messageAPI.success(t("passwordChanged"));
- passwordForm.resetFields();
- } catch (error) {
- showError(error);
- } finally {
- setPasswordSubmitting(false);
- }
- };
+ const handleProfileSubmit: NonNullable<
+ FormProps["onFinish"]
+ > = (values) => {
+ void submitProfile(values);
+ };
+ const handleUsernameSubmit: NonNullable<
+ FormProps["onFinish"]
+ > = (values) => {
+ void submitUsername(values);
+ };
+ const handleEmailSubmit: NonNullable<
+ FormProps["onFinish"]
+ > = (values) => {
+ void submitEmail(values);
+ };
+ const handlePasswordSubmit: NonNullable<
+ FormProps["onFinish"]
+ > = (values) => {
+ void submitPassword(values);
+ };
return (
{
}}
vertical
>
- {t("account")}
+
+ {t("account")}
+
@@ -265,7 +287,7 @@ export const AccountPage = () => {
-
+
-
- label={t("newPass")}
- name="password"
- rules={[{ required: true }]}
- >
-
-
-
- dependencies={["password"]}
- label={t("confirmPass")}
- name="confirmPassword"
- rules={[
- { required: true },
- ({ getFieldValue }) => ({
- validator(_, value) {
- if (!value || getFieldValue("password") === value) {
- return Promise.resolve();
- }
-
- return Promise.reject(new Error(t("passsMismatch")));
- },
- }),
- ]}
- >
-
-
+