diff --git a/.github/codeql/config.yml b/.github/codeql/config.yml deleted file mode 100644 index ad81a268eda4..000000000000 --- a/.github/codeql/config.yml +++ /dev/null @@ -1,8 +0,0 @@ -name: 'Angular CLI CodeQL config' - -query-filters: - # TODO(josephperrott): reevaluate if these can be reenabled. - - exclude: - id: js/bad-code-sanitization - - exclude: - id: js/regex-injection diff --git a/.github/workflows/assistant-to-the-branch-manager.yml b/.github/workflows/assistant-to-the-branch-manager.yml index 4a9daab0a148..9147cb3a65b8 100644 --- a/.github/workflows/assistant-to-the-branch-manager.yml +++ b/.github/workflows/assistant-to-the-branch-manager.yml @@ -18,6 +18,6 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: angular/dev-infra/github-actions/branch-manager@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + - uses: angular/dev-infra/github-actions/branch-manager@e713eca55b2813448af42f2e0e752258e4edfeea # main with: angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 05fa1692e7c8..b7d66e4aafc5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,9 +21,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/bazel/setup@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Install node modules run: pnpm install --frozen-lockfile - name: Generate JSON schema types @@ -44,11 +44,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/bazel/setup@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/bazel/configure-remote@e713eca55b2813448af42f2e0e752258e4edfeea # main with: google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }} - name: Install node modules @@ -61,11 +61,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/bazel/setup@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/bazel/configure-remote@e713eca55b2813448af42f2e0e752258e4edfeea # main with: google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }} - name: Install node modules @@ -84,13 +84,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Install node modules run: pnpm install --frozen-lockfile - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/bazel/setup@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/bazel/configure-remote@e713eca55b2813448af42f2e0e752258e4edfeea # main with: google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }} - name: Run CLI E2E tests @@ -100,11 +100,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/bazel/setup@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/bazel/configure-remote@e713eca55b2813448af42f2e0e752258e4edfeea # main with: google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }} - name: Install node modules @@ -137,7 +137,7 @@ jobs: runs-on: windows-2025 steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Install node modules run: pnpm install --frozen-lockfile - name: Download built Windows E2E tests @@ -164,13 +164,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Install node modules run: pnpm install --frozen-lockfile - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/bazel/setup@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/bazel/configure-remote@e713eca55b2813448af42f2e0e752258e4edfeea # main with: google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }} - name: Run CLI E2E tests @@ -188,13 +188,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Install node modules run: pnpm install --frozen-lockfile - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/bazel/setup@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/bazel/configure-remote@e713eca55b2813448af42f2e0e752258e4edfeea # main with: google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }} - name: Run CLI E2E tests @@ -208,13 +208,13 @@ jobs: SAUCE_TUNNEL_IDENTIFIER: angular-cli-${{ github.workflow }}-${{ github.run_number }} steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Install node modules run: pnpm install --frozen-lockfile - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/bazel/setup@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/bazel/configure-remote@e713eca55b2813448af42f2e0e752258e4edfeea # main with: google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }} - name: Start Sauce Connect @@ -245,11 +245,11 @@ jobs: CIRCLE_BRANCH: ${{ github.ref_name }} steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Install node modules run: pnpm install --frozen-lockfile - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/bazel/setup@e713eca55b2813448af42f2e0e752258e4edfeea # main - run: pnpm admin snapshots --verbose env: SNAPSHOT_BUILDS_GITHUB_TOKEN: ${{ secrets.SNAPSHOT_BUILDS_GITHUB_TOKEN }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index 7a75337fd3df..000000000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: 'CodeQL' - -on: - push: - branches: ['main', '*.*.x'] - schedule: - - cron: '39 9 * * 1' - -permissions: {} - -jobs: - analyze: - name: Analyze - runs-on: 'ubuntu-latest' - permissions: - security-events: write - packages: read - strategy: - fail-fast: false - steps: - - name: Checkout repository - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - name: Initialize CodeQL - uses: github/codeql-action/init@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4 - with: - languages: javascript-typescript - build-mode: none - config-file: .github/codeql/config.yml - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4 - with: - category: '/language:javascript-typescript' diff --git a/.github/workflows/dev-infra.yml b/.github/workflows/dev-infra.yml index 0be73a55a51a..f0b468e675ea 100644 --- a/.github/workflows/dev-infra.yml +++ b/.github/workflows/dev-infra.yml @@ -16,21 +16,21 @@ jobs: if: github.event_name == 'pull_request_target' runs-on: ubuntu-latest steps: - - uses: angular/dev-infra/github-actions/labeling/pull-request@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + - uses: angular/dev-infra/github-actions/labeling/pull-request@e713eca55b2813448af42f2e0e752258e4edfeea # main with: angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }} post_approval_changes: if: github.event_name == 'pull_request_target' runs-on: ubuntu-latest steps: - - uses: angular/dev-infra/github-actions/post-approval-changes@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + - uses: angular/dev-infra/github-actions/post-approval-changes@e713eca55b2813448af42f2e0e752258e4edfeea # main with: angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }} issue_labels: if: github.event_name == 'issues' runs-on: ubuntu-latest steps: - - uses: angular/dev-infra/github-actions/labeling/issue@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + - uses: angular/dev-infra/github-actions/labeling/issue@e713eca55b2813448af42f2e0e752258e4edfeea # main with: angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }} google-generative-ai-key: ${{ secrets.GOOGLE_GENERATIVE_AI_KEY }} diff --git a/.github/workflows/perf.yml b/.github/workflows/perf.yml index 5bca0fe665c8..392fbd4cc108 100644 --- a/.github/workflows/perf.yml +++ b/.github/workflows/perf.yml @@ -22,7 +22,7 @@ jobs: workflows: ${{ steps.workflows.outputs.workflows }} steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Install node modules run: pnpm install --frozen-lockfile - id: workflows @@ -40,9 +40,9 @@ jobs: workflow: ${{ fromJSON(needs.list.outputs.workflows) }} steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/bazel/setup@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Install node modules run: pnpm install --frozen-lockfile # We utilize the google-github-actions/auth action to allow us to get an active credential using workflow diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 4100bed1bcc8..0837c2b0fa67 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -23,7 +23,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2 + - uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 id: filter with: filters: | @@ -34,9 +34,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/bazel/setup@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Setup ESLint Caching uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: @@ -66,17 +66,17 @@ jobs: # it has been merged. run: pnpm ng-dev format changed --check ${{ github.event.pull_request.base.sha }} - name: Check Package Licenses - uses: angular/dev-infra/github-actions/linting/licenses@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/linting/licenses@e713eca55b2813448af42f2e0e752258e4edfeea # main build: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/bazel/setup@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/bazel/configure-remote@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Install node modules run: pnpm install --frozen-lockfile - name: Build release targets @@ -93,11 +93,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/bazel/setup@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/bazel/configure-remote@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Install node modules run: pnpm install --frozen-lockfile - name: Run module and package tests @@ -114,13 +114,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Install node modules run: pnpm install --frozen-lockfile - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/bazel/setup@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/bazel/configure-remote@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Run CLI E2E tests run: pnpm bazel test --test_env=E2E_SHARD_TOTAL=6 --test_env=E2E_SHARD_INDEX=${{ matrix.shard }} --config=e2e //tests:e2e.${{ matrix.subset }}_node${{ matrix.node }} @@ -128,11 +128,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/bazel/setup@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/bazel/configure-remote@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Install node modules run: pnpm install --frozen-lockfile - name: Build E2E tests for Windows on Linux @@ -156,7 +156,7 @@ jobs: runs-on: windows-2025 steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Install node modules run: pnpm install --frozen-lockfile - name: Download built Windows E2E tests @@ -183,13 +183,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Install node modules run: pnpm install --frozen-lockfile - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/bazel/setup@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/bazel/configure-remote@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Run CLI E2E tests run: pnpm bazel test --test_env=E2E_SHARD_TOTAL=3 --test_env=E2E_SHARD_INDEX=${{ matrix.shard }} --config=e2e //tests:e2e.${{ matrix.subset }}_node${{ matrix.node }} @@ -205,12 +205,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Install node modules run: pnpm install --frozen-lockfile - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/bazel/setup@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@0b701b31038419ad73422a9d9d3dff43a725a0d2 # main + uses: angular/dev-infra/github-actions/bazel/configure-remote@e713eca55b2813448af42f2e0e752258e4edfeea # main - name: Run CLI E2E tests run: pnpm bazel test --test_env=E2E_SHARD_TOTAL=6 --test_env=E2E_SHARD_INDEX=${{ matrix.shard }} --config=e2e //tests:e2e.snapshots.${{ matrix.subset }}_node${{ matrix.node }} diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml deleted file mode 100644 index ac5d75573754..000000000000 --- a/.github/workflows/scorecard.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: OpenSSF Scorecard -on: - branch_protection_rule: - schedule: - - cron: '0 2 * * 0' - push: - branches: [main] - workflow_dispatch: - -# Declare default permissions as read only. -permissions: - contents: read - -jobs: - analysis: - name: Scorecards analysis - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - # Needed to upload the results to code-scanning dashboard. - security-events: write - # Needed to publish results - id-token: write - - steps: - - name: 'Checkout code' - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - - name: 'Run analysis' - uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4 - with: - results_file: results.sarif - results_format: sarif - publish_results: true - - # Upload the results as artifacts. - - name: 'Upload artifact' - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: SARIF file - path: results.sarif - retention-days: 5 - - # Upload the results to GitHub's code scanning dashboard. - - name: 'Upload to code-scanning' - uses: github/codeql-action/upload-sarif@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4 - with: - sarif_file: results.sarif diff --git a/.nvmrc b/.nvmrc index c94711948a66..60ade1ae01e8 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -22.23.2 +24.19.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ff82d9e52d9..e1fcb5a988ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,258 @@ + + +# 22.2.0-next.5 (2026-08-26) + +### @angular/build + +| Commit | Type | Description | +| ---------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------------------------- | +| [4d0be683b7](https://github.com/angular/angular-cli/commit/4d0be683b780a9a1629dc8f9cddad49153bddabf) | fix | ignore side-effect annotations in unit test builds | +| [e4111ae818](https://github.com/angular/angular-cli/commit/e4111ae8180f3cd5800f248c23010844aa1730ad) | fix | prevent memory leaks in parallel compilation and caches | +| [62999cabf4](https://github.com/angular/angular-cli/commit/62999cabf4d65cf1060598150a77494e147fcc2a) | perf | batch prerender routes and share in-memory server bundles | +| [0589edd9cd](https://github.com/angular/angular-cli/commit/0589edd9cdc493ecf34a160cc7b8491618b59f72) | perf | consolidate component stylesheet bundling with shared load result cache | +| [a877aa7ebd](https://github.com/angular/angular-cli/commit/a877aa7ebdbf919d6bc17e6ec0920ca702db0db4) | perf | implement SharedArrayBuffer translation dictionaries for zero-copy worker access | +| [f1cc959439](https://github.com/angular/angular-cli/commit/f1cc959439d8ca11a45d693520c34dd94e0b6548) | perf | implement sliding-window batching and worker translation eviction | +| [f4dad31cce](https://github.com/angular/angular-cli/commit/f4dad31cce41bc3e8ed8767256d9e50a712c3a72) | perf | unify Oxc linking and optimization AST traversal passes | + +### @angular/ssr + +| Commit | Type | Description | +| ---------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------- | +| [f8576e3c8b](https://github.com/angular/angular-cli/commit/f8576e3c8bfe3490b1b0b85832b489593a4e43ac) | fix | abort web request signal when node request is aborted | + + + + + +# 22.1.6 (2026-08-26) + +### @angular/build + +| Commit | Type | Description | +| ---------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------- | +| [3f132eaa97](https://github.com/angular/angular-cli/commit/3f132eaa97b95e16c58b8095c2566e3c78190446) | fix | ignore side-effect annotations in unit test builds | +| [5b1028936f](https://github.com/angular/angular-cli/commit/5b1028936f6e9582917f95ac37942dca0ee2c659) | perf | batch prerender routes and share in-memory server bundles | + +### @angular/ssr + +| Commit | Type | Description | +| ---------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------- | +| [76818cb811](https://github.com/angular/angular-cli/commit/76818cb811a8fe669575f988fec2e4528dca8c73) | fix | abort web request signal when node request is aborted | + + + + + +# 21.2.22 (2026-08-26) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| ---------------------------------------------------------------------------------------------------- | ---- | ---------------------------------- | +| [b0047b04f5](https://github.com/angular/angular-cli/commit/b0047b04f56f5cb3fb8e4465ba0fad777497144a) | fix | update webpack-dev-server to 5.2.6 | + + + + + +# 20.3.35 (2026-08-26) + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| ---------------------------------------------------------------------------------------------------- | ---- | ---------------------------------- | +| [94b620acd7](https://github.com/angular/angular-cli/commit/94b620acd76e39c7d5cc8527dfc6fe9096855cbd) | fix | update webpack-dev-server to 5.2.6 | + + + + + +# 22.2.0-next.4 (2026-08-19) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------------- | +| [34e1e0bb5](https://github.com/angular/angular-cli/commit/34e1e0bb5d5ccf374f51a1b241158f6f2ea0dd42) | fix | enforce MCP roots in get_best_practices tool | +| [2b060630c](https://github.com/angular/angular-cli/commit/2b060630c66ce688b7ecbe51d282955869e5d4b0) | fix | handle errors from isAllowedWorkspacePath in best-practices tool | +| [24fd8fce2](https://github.com/angular/angular-cli/commit/24fd8fce2a3ff035acdc5f3e036eb4ba21f71a76) | fix | throw on out-of-roots workspace in best-practices tool | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------ | +| [ce1b60f89](https://github.com/angular/angular-cli/commit/ce1b60f89699c3a76496a0489e5f2b9e4fe62429) | fix | transform fail() to expect.fail() in refactor-jasmine-vitest | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------------------------------------- | +| [175273931](https://github.com/angular/angular-cli/commit/175273931ebb0cf08bf62dd1694dbde5ff5229b6) | feat | Support splitting browser and server stats jsonfiles for easier consumption | +| [1ee0beca7](https://github.com/angular/angular-cli/commit/1ee0beca7b243cc4c06eb42b25d216d7b256b097) | fix | correct misleading error message for top-level await | +| [0ffe2d27f](https://github.com/angular/angular-cli/commit/0ffe2d27f256a5755809fa4788e4adb97c65b806) | fix | disable code splitting for unit test builds | +| [50994d76e](https://github.com/angular/angular-cli/commit/50994d76e59a4b632f1782c51cd60f80c9cd50fb) | fix | preserve integrity and crossorigin in autoCsp loader | +| [d6fd24320](https://github.com/angular/angular-cli/commit/d6fd243207ad1c3e242b8592698afa986320cc6c) | perf | traverse AST with iterative post-order walker in i18n inliner | + + + + + +# 22.1.5 (2026-08-19) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------------- | +| [e672271f8](https://github.com/angular/angular-cli/commit/e672271f81e7b8b422b98c17fca5b67896d40844) | fix | enforce MCP roots in get_best_practices tool | +| [a14916cc4](https://github.com/angular/angular-cli/commit/a14916cc4b13412205d3e4a1c32d7e40be52f0e1) | fix | handle errors from isAllowedWorkspacePath in best-practices tool | +| [d6e1cddff](https://github.com/angular/angular-cli/commit/d6e1cddff92842f6889585ae7899efc028decc78) | fix | throw on out-of-roots workspace in best-practices tool | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------ | +| [ba2b0e4c2](https://github.com/angular/angular-cli/commit/ba2b0e4c291842c699fdbeaaddec1531dbdd7952) | fix | transform fail() to expect.fail() in refactor-jasmine-vitest | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------- | +| [cba72902d](https://github.com/angular/angular-cli/commit/cba72902d42022bd5a1bd17b23d65cacc0013e35) | fix | correct misleading error message for top-level await | +| [b4679998c](https://github.com/angular/angular-cli/commit/b4679998c205b6cce01d40fad24c4ee7954aa48d) | fix | disable code splitting for unit test builds | +| [ff1d3565e](https://github.com/angular/angular-cli/commit/ff1d3565e87749a8347c3be98cd4a7ebccb69fc0) | fix | preserve integrity and crossorigin in autoCsp loader | +| [1fc1fb05c](https://github.com/angular/angular-cli/commit/1fc1fb05cfe2c69ee7b9345b582d3c2177b923fd) | perf | traverse AST with iterative post-order walker in i18n inliner | + + + + + +# 22.2.0-next.3 (2026-08-13) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------- | +| [7106676e6](https://github.com/angular/angular-cli/commit/7106676e64ee234641b1b3cbd88fc058703d4d3e) | fix | disable searching current directory for bare executable names on Windows | +| [ecf8c0822](https://github.com/angular/angular-cli/commit/ecf8c0822e5d56b44a0be4be8fccb41373135d3e) | fix | serialize configuration as a single argv token in run_target strategies ([#33657](https://github.com/angular/angular-cli/pull/33657)) | +| [2e4dd90c3](https://github.com/angular/angular-cli/commit/2e4dd90c3ae898bf36af42f699c7b7d560bfc66a) | perf | avoid eager module loading during global bootstrap | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------- | +| [1161e6c99](https://github.com/angular/angular-cli/commit/1161e6c99992c884e2e0ce9fc295b0e60b2ae1df) | fix | generate CLAUDE.md for Claude Code instead of AGENTS.md | +| [c536ae364](https://github.com/angular/angular-cli/commit/c536ae364975dd0088fd8717464c723a4dcb74e3) | fix | import UrlSegment instead of subPath in guard generator | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------- | +| [04b532772](https://github.com/angular/angular-cli/commit/04b532772f6347d341b182135dee1758616a69d6) | fix | count statically imported chunks in the initial total | +| [2274babb6](https://github.com/angular/angular-cli/commit/2274babb662b8da2189f1ae9faaa528838052c32) | fix | normalize setupFiles paths to POSIX for vitest runner | +| [199a864df](https://github.com/angular/angular-cli/commit/199a864df97fc8c20b8d4e1fc8b891c61140671a) | fix | prevent syntax corruption for Crockford-style enum IIFE | +| [0d9851600](https://github.com/angular/angular-cli/commit/0d9851600ca618ccabfc3143a9c552a5448d6eec) | fix | recursively ignore output and cache paths in watch mode | +| [3eba6f726](https://github.com/angular/angular-cli/commit/3eba6f7269f3b0f8556510d0b668e978ea686c88) | fix | return direct file contents for non-Angular TypeScript files | +| [3c7ac1518](https://github.com/angular/angular-cli/commit/3c7ac1518b062f14409b4d3c6c3e65c956424ad9) | fix | return only lowest version per target engine | +| [3e9fed9a6](https://github.com/angular/angular-cli/commit/3e9fed9a6a1d24f30812ca29ca621d9d9a14496f) | fix | set target for Rolldown dependency prebundling in Vite dev server | +| [87551ad5c](https://github.com/angular/angular-cli/commit/87551ad5c53f90cbdc68fdb5e47007b9436c7f75) | perf | avoid encoding intermediate source maps before remapping | +| [194be2088](https://github.com/angular/angular-cli/commit/194be2088587918ed7b47ed606ea587a7116608a) | perf | avoid encoding the inline source map before remapping | +| [a55a6b78e](https://github.com/angular/angular-cli/commit/a55a6b78e89debb9c93c83375c54f6716bc26c65) | perf | batch last_accessed updates in sqlite cache store | +| [33b305416](https://github.com/angular/angular-cli/commit/33b3054169929dc98f93a758a90da5d26cf522f7) | perf | hash the i18n inline cache key options once per locale | +| [1c00edce0](https://github.com/angular/angular-cli/commit/1c00edce02bdd0201546417d71518f78bfb89d34) | perf | optimize sourcemap stripping and loading with buffer fast path | +| [b6269a816](https://github.com/angular/angular-cli/commit/b6269a8169046a78281808dcf92054140912a77d) | perf | optimize template string size calculation in server manifest | +| [a6ef9cfbe](https://github.com/angular/angular-cli/commit/a6ef9cfbeace725d58c0f7f65640ef6de9b39c33) | perf | replace watchpack with @parcel/watcher and chokidar | +| [596847f89](https://github.com/angular/angular-cli/commit/596847f89913684a84465d004aa3d6c7e1a40b06) | perf | share i18n translations with the inliner workers by reference | +| [19c91e48d](https://github.com/angular/angular-cli/commit/19c91e48d4b8a95a76dfe4bffd105130b416843d) | perf | use Map for chunk asset size lookups in budget calculator | + +### @angular/ssr + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------- | +| [6a3446770](https://github.com/angular/angular-cli/commit/6a3446770a8fbfecc7265eb2a6d764e2f1614b3a) | fix | destroy platform when response stream is cancelled | + + + + + +# 22.1.4 (2026-08-13) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------- | +| [67a29899b](https://github.com/angular/angular-cli/commit/67a29899b07f760bac9582cbc6175966cce1c5d2) | fix | disable searching current directory for bare executable names on Windows | +| [1b0ba5c17](https://github.com/angular/angular-cli/commit/1b0ba5c17f0be2679b31bad1bee8b435315dac1a) | fix | serialize configuration as a single argv token in run_target strategies ([#33657](https://github.com/angular/angular-cli/pull/33657)) | +| [9440432d2](https://github.com/angular/angular-cli/commit/9440432d273c044965508d80e808c2fe123af0aa) | perf | avoid eager module loading during global bootstrap | + +### @schematics/angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------- | +| [d4a48e3ab](https://github.com/angular/angular-cli/commit/d4a48e3abe320e090eefe17cb70abdf8e7eafaed) | fix | generate CLAUDE.md for Claude Code instead of AGENTS.md | +| [d451f15b2](https://github.com/angular/angular-cli/commit/d451f15b2838a8282363dcb30161dd5dc858750f) | fix | import UrlSegment instead of subPath in guard generator | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------- | +| [46fcb29d6](https://github.com/angular/angular-cli/commit/46fcb29d6c1ded962de95449d63716c13950f59a) | fix | count statically imported chunks in the initial total | +| [796b57684](https://github.com/angular/angular-cli/commit/796b576843191a4d5c74e4f9363ec0e46ae3a881) | fix | normalize setupFiles paths to POSIX for vitest runner | +| [c805e5cfb](https://github.com/angular/angular-cli/commit/c805e5cfb308d02a1944ef446d0a01af8a7ec6d2) | fix | prevent syntax corruption for Crockford-style enum IIFE | +| [cc6c17716](https://github.com/angular/angular-cli/commit/cc6c17716df5c411efca5e6f4c960f0565f45d90) | fix | prevent syntax corruption in oxc transform | +| [a142f6f83](https://github.com/angular/angular-cli/commit/a142f6f83d2f501b64f2404909ef9325bbfa70fb) | fix | return direct file contents for non-Angular TypeScript files | +| [956c6578d](https://github.com/angular/angular-cli/commit/956c6578d31d8482c04dc45723d7ab3b4fe355a5) | fix | return only lowest version per target engine | +| [689b5b116](https://github.com/angular/angular-cli/commit/689b5b116717140da98494e61ac5ea5ad7b68d73) | fix | set target for Rolldown dependency prebundling in Vite dev server | +| [70461f6ed](https://github.com/angular/angular-cli/commit/70461f6ed2e30aeaad2b93400ad00d9274665967) | perf | avoid encoding the inline source map before remapping | +| [22cce7563](https://github.com/angular/angular-cli/commit/22cce7563e82f17373b69e590d3e445767bd9264) | perf | batch last_accessed updates in sqlite cache store | +| [ab2ed18d5](https://github.com/angular/angular-cli/commit/ab2ed18d5739e8565403062ca74aa2d5cb3b95ec) | perf | hash the i18n inline cache key options once per locale | +| [34516ff73](https://github.com/angular/angular-cli/commit/34516ff736185e20ef8239b1fed8eda1f0193f62) | perf | optimize template string size calculation in server manifest | +| [712971c37](https://github.com/angular/angular-cli/commit/712971c370738beaebff18f6995859b1713ed8ae) | perf | share i18n translations with the inliner workers by reference | +| [78f8c5fef](https://github.com/angular/angular-cli/commit/78f8c5fef44dc6ff466b17c98fa1d01222cb1781) | perf | use Map for chunk asset size lookups in budget calculator | + +### @angular/ssr + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------- | +| [2331c1047](https://github.com/angular/angular-cli/commit/2331c10476830e7a53e0f2fa4a2f37dad58e2d5d) | fix | destroy platform when response stream is cancelled | + + + + + +# 21.2.21 (2026-08-12) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------ | +| [ba5d16cac](https://github.com/angular/angular-cli/commit/ba5d16cacab4139897d1f0cf4161562b1bd00553) | fix | update dependency @modelcontextprotocol/sdk to v1.30.0 | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------- | +| [f7f60e69e](https://github.com/angular/angular-cli/commit/f7f60e69e77cda60378543a9ca9cc2c76c0bf34f) | fix | bump undici to 7.29.0 | + + + + + +# 20.3.34 (2026-08-12) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------ | +| [3962517b9](https://github.com/angular/angular-cli/commit/3962517b95c76016ff7bcb53662704dff18c0552) | fix | update dependency @modelcontextprotocol/sdk to v1.30.0 | + +### @angular-devkit/build-angular + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | --------------------- | +| [f348efe8b](https://github.com/angular/angular-cli/commit/f348efe8b6d9c9d7014afb331eecda0a9478ba0a) | fix | bump undici to 7.29.0 | + + + # 22.2.0-next.2 (2026-08-05) diff --git a/MODULE.bazel b/MODULE.bazel index d4fb6902ff2a..3015308e9d38 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -7,48 +7,48 @@ module( bazel_dep(name = "platforms", version = "1.1.0") bazel_dep(name = "yq.bzl", version = "0.3.6") bazel_dep(name = "rules_nodejs", version = "6.7.5") -bazel_dep(name = "aspect_rules_js", version = "3.3.1") -bazel_dep(name = "aspect_rules_ts", version = "3.9.2") -bazel_dep(name = "rules_pkg", version = "1.2.0") +bazel_dep(name = "aspect_rules_js", version = "3.4.0") +bazel_dep(name = "aspect_rules_ts", version = "3.10.0") +bazel_dep(name = "rules_pkg", version = "1.3.0") bazel_dep(name = "rules_cc", version = "0.2.22") bazel_dep(name = "jq.bzl", version = "0.6.1") -bazel_dep(name = "bazel_lib", version = "3.5.0") +bazel_dep(name = "bazel_lib", version = "3.7.1") bazel_dep(name = "bazel_skylib", version = "1.9.2") -bazel_dep(name = "aspect_rules_esbuild", version = "0.26.0") +bazel_dep(name = "aspect_rules_esbuild", version = "0.27.0") bazel_dep(name = "aspect_rules_jasmine", version = "2.0.4") bazel_dep(name = "rules_angular") git_override( module_name = "rules_angular", - commit = "a0bbcceea19b7888114eeafe3f52f9822ca2b34a", + commit = "a30fc96ae7cbf6738d37d9d7d3e1c6e6cb7744f5", remote = "https://github.com/angular/rules_angular.git", ) bazel_dep(name = "devinfra") git_override( module_name = "devinfra", - commit = "0b701b31038419ad73422a9d9d3dff43a725a0d2", + commit = "e713eca55b2813448af42f2e0e752258e4edfeea", remote = "https://github.com/angular/dev-infra.git", ) bazel_dep(name = "rules_browsers") git_override( module_name = "rules_browsers", - commit = "7fe4598226334b0809d3054f6948d72df4b5ed59", + commit = "c6cb5590120af8ae8125343f9fc522b7b66eb0e8", remote = "https://github.com/angular/rules_browsers.git", ) node = use_extension("@rules_nodejs//nodejs:extensions.bzl", "node") node.toolchain( node_repositories = { - "22.23.2-darwin_arm64": ("node-v22.23.2-darwin-arm64.tar.gz", "node-v22.23.2-darwin-arm64", "61130f394c1630d211dd50aecc4353d379480f36d3ac913cd85dbba1aed585c6"), - "22.23.2-darwin_amd64": ("node-v22.23.2-darwin-x64.tar.gz", "node-v22.23.2-darwin-x64", "58e99022c2ff89395576cc7fd4d98cea24bb68081475d5f88b801ee8729fb026"), - "22.23.2-linux_arm64": ("node-v22.23.2-linux-arm64.tar.xz", "node-v22.23.2-linux-arm64", "fff4078c5def658577f92c88db7db3bc0072924bfb93fe52c1e744a54e94abb8"), - "22.23.2-linux_ppc64le": ("node-v22.23.2-linux-ppc64le.tar.xz", "node-v22.23.2-linux-ppc64le", "e2ba10110ad34096c051fe47134fcd12175ecc48e7e879d7d4c78b70cf66442c"), - "22.23.2-linux_s390x": ("node-v22.23.2-linux-s390x.tar.xz", "node-v22.23.2-linux-s390x", "50aa0935c7caee2f95434f84c2c19f16e4f223257eadb341a3a2d5aaa545bbe6"), - "22.23.2-linux_amd64": ("node-v22.23.2-linux-x64.tar.xz", "node-v22.23.2-linux-x64", "d60acfe00a2932254bb0ad20e01b0d74397a0875595de719654b214f4b03f307"), - "22.23.2-windows_amd64": ("node-v22.23.2-win-x64.zip", "node-v22.23.2-win-x64", "1177b4137ba5adaa56354ae40f1080c7450e8ae09cecb47da459d1c52ac99f97"), + "24.19.0-darwin_arm64": ("node-v24.19.0-darwin-arm64.tar.gz", "node-v24.19.0-darwin-arm64", "8294b7aa9b03997481c06babf1e8b270c859358f27da57a11509afe537ac381d"), + "24.19.0-darwin_amd64": ("node-v24.19.0-darwin-x64.tar.gz", "node-v24.19.0-darwin-x64", "d1b5e999db158c62fe8f7267a4476b035d8bd93b1a605bac24a3f0dd166e3316"), + "24.19.0-linux_arm64": ("node-v24.19.0-linux-arm64.tar.xz", "node-v24.19.0-linux-arm64", "01443c1e1a29e531ccad5a46fefa6df490d2189c49f7955904aecdbb0fe86fdc"), + "24.19.0-linux_ppc64le": ("node-v24.19.0-linux-ppc64le.tar.xz", "node-v24.19.0-linux-ppc64le", "c510c6ce12f07010f771e6edb22a3fe23f4f2e6f40b1ffd4941aed0646a0d8b3"), + "24.19.0-linux_s390x": ("node-v24.19.0-linux-s390x.tar.xz", "node-v24.19.0-linux-s390x", "a4792e65962ffa0af42627aacf1122a60c3c88dbf4e4184f06820d66f9da8ba4"), + "24.19.0-linux_amd64": ("node-v24.19.0-linux-x64.tar.xz", "node-v24.19.0-linux-x64", "14b342e71204f811bde6153be8e04b62aef63c236fef92b55f9c83154b409647"), + "24.19.0-windows_amd64": ("node-v24.19.0-win-x64.zip", "node-v24.19.0-win-x64", "57f71ab3652e797d84acddc79c81cc9ff1c6ddb2a1974cdb83f00fee9bff4c73"), }, - node_version = "22.23.2", + node_version = "24.19.0", ) use_repo( node, @@ -131,8 +131,8 @@ use_repo( pnpm = use_extension("@aspect_rules_js//npm:extensions.bzl", "pnpm") pnpm.pnpm( name = "pnpm", - pnpm_version = "11.20.0", - pnpm_version_integrity = "sha512-mm8zCpW2ZEbqCI+vFSFAWooB8H/ecSTMmVjf7VLUu0NnN+ZbCPhfN7Rvy6N1CSVYrFEmK4FoRLIvY0Bu0Wa/7g==", + pnpm_version = "11.24.0", + pnpm_version_integrity = "sha512-vSfjRel23LC+C3oSKCF7BJqBfiGx81XJDb59xGZxiVqLwebQbCRVRQXqk+oLRfSJon7Bv7yN5qlln8oPFvoAAA==", ) use_repo(pnpm, "pnpm") diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index a30e6d148916..ee16930eae77 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -18,16 +18,15 @@ "https://bcr.bazel.build/modules/aspect_bazel_lib/2.22.5/source.json": "ac2c3213df8f985785f1d0aeb7f0f73d5324e6e67d593d9b9470fb74a25d4a9b", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.7.7/MODULE.bazel": "491f8681205e31bb57892d67442ce448cda4f472a8e6b3dc062865e29a64f89c", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.8.1/MODULE.bazel": "812d2dd42f65dca362152101fbec418029cc8fd34cbad1a2fde905383d705838", - "https://bcr.bazel.build/modules/aspect_rules_esbuild/0.26.0/MODULE.bazel": "6c902d97038c3ab07b6c4e67c97abc61b20182fcfa84fa7dee82fc724f12e455", - "https://bcr.bazel.build/modules/aspect_rules_esbuild/0.26.0/source.json": "4cc3ece7ab661bb391a9e24fe55c4b567d60a9ea9d9e91d772dad373cbcb6217", + "https://bcr.bazel.build/modules/aspect_rules_esbuild/0.27.0/MODULE.bazel": "877dafc0b925f8af19e8bc2abed04a757bb565c57c1866e8851ac4d15ed5e6d2", + "https://bcr.bazel.build/modules/aspect_rules_esbuild/0.27.0/source.json": "21f8738b3e62310ef43b7cef4284e1bafd69bd8e4e50251b71b20bbfed4372d8", "https://bcr.bazel.build/modules/aspect_rules_jasmine/2.0.4/MODULE.bazel": "fbb819eb8b7e5d7f67fdd38f7cecb413e287594cd666ce192c72c8828527775a", "https://bcr.bazel.build/modules/aspect_rules_jasmine/2.0.4/source.json": "81ffb708333cd98ec3c0b4cc004f4d5cf92a16914b5196a2892c45141bba7cff", "https://bcr.bazel.build/modules/aspect_rules_js/2.0.0/MODULE.bazel": "b45b507574aa60a92796e3e13c195cd5744b3b8aff516a9c0cb5ae6a048161c5", - "https://bcr.bazel.build/modules/aspect_rules_js/3.0.3/MODULE.bazel": "28a30e8fc33bf64a67835d64d124f6e05a7d59648dcb27b110fb3502f761e503", - "https://bcr.bazel.build/modules/aspect_rules_js/3.3.1/MODULE.bazel": "3e02b51b503ba8dda69b043290f6cc11add9aeb8db0bf1f6c861c396c7ddc5b2", - "https://bcr.bazel.build/modules/aspect_rules_js/3.3.1/source.json": "d5e0736539b00bdd33015bb336877ad5a4d16f1d1d4af13ffeade395106cabd7", - "https://bcr.bazel.build/modules/aspect_rules_ts/3.9.2/MODULE.bazel": "feeb6c45b69c995eca3e5ca5872658c80df658022e01044eca00cf472bb89142", - "https://bcr.bazel.build/modules/aspect_rules_ts/3.9.2/source.json": "cf3075502f798f71a9c5707a7684eaf0b86da11ed440fd90ea34385dc297676a", + "https://bcr.bazel.build/modules/aspect_rules_js/3.4.0/MODULE.bazel": "88844ac411e1961f4574a92f3c5be5b20d1c6997778c6b88316c5c3b4b60e284", + "https://bcr.bazel.build/modules/aspect_rules_js/3.4.0/source.json": "85e5822f00dcbe64a1eda1324119e289c8c03cacb5c3695dffee16397b529078", + "https://bcr.bazel.build/modules/aspect_rules_ts/3.10.0/MODULE.bazel": "69d06f57f30f4a2b6e53471584a9559d3b7cd7f891e1699876991230c7cabb95", + "https://bcr.bazel.build/modules/aspect_rules_ts/3.10.0/source.json": "56f28a3ddb55ceaaf57a1ef8d7195136789ca1d72d0c8a6a9eeaad313be4099d", "https://bcr.bazel.build/modules/aspect_tools_telemetry/0.2.8/MODULE.bazel": "aa975a83e72bcaac62ee61ab12b788ea324a1d05c4aab28aadb202f647881679", "https://bcr.bazel.build/modules/aspect_tools_telemetry/0.3.3/MODULE.bazel": "37c764292861c2f70314efa9846bb6dbb44fc0308903b3285da6528305450183", "https://bcr.bazel.build/modules/aspect_tools_telemetry/0.4.2/MODULE.bazel": "f31aa84151d31e98cffd43eb7217ccff5ec52bdd5f2d10db8f053aeb23342eca", @@ -51,9 +50,9 @@ "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", "https://bcr.bazel.build/modules/bazel_lib/3.0.0-rc.0/MODULE.bazel": "d6e00979a98ac14ada5e31c8794708b41434d461e7e7ca39b59b765e6d233b18", "https://bcr.bazel.build/modules/bazel_lib/3.0.0/MODULE.bazel": "22b70b80ac89ad3f3772526cd9feee2fa412c2b01933fea7ed13238a448d370d", - "https://bcr.bazel.build/modules/bazel_lib/3.2.2/MODULE.bazel": "e2c890c8a515d6bca9c66d47718aa9e44b458fde64ec7204b8030bf2d349058c", - "https://bcr.bazel.build/modules/bazel_lib/3.5.0/MODULE.bazel": "1eba0ba67d418d99fb682c36147a9e1725abbc8fc25544be64702e32eda2be29", - "https://bcr.bazel.build/modules/bazel_lib/3.5.0/source.json": "98d49ec7e2b15298c990f09ec8cdea8d4788c2885497aa9cbb0a74e36e994df8", + "https://bcr.bazel.build/modules/bazel_lib/3.7.0/MODULE.bazel": "d7c10ed67f0f7f1fda179db8f86c22642581bd614882e1a50545fbe069525173", + "https://bcr.bazel.build/modules/bazel_lib/3.7.1/MODULE.bazel": "b6fd9b2f8fab956420c11836f416efac4a70e20804ae384ebe62773a4ed70046", + "https://bcr.bazel.build/modules/bazel_lib/3.7.1/source.json": "635fdaa28b50c04febc5e60ef51bc913d3bc87bfbaac7045449273c2341648cb", "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", "https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e", "https://bcr.bazel.build/modules/bazel_skylib/1.2.0/MODULE.bazel": "44fe84260e454ed94ad326352a698422dbe372b21a1ac9f3eab76eb531223686", @@ -68,6 +67,7 @@ "https://bcr.bazel.build/modules/bazel_skylib/1.8.0/MODULE.bazel": "2fb3fb53675f6adfc1ca5bfbd5cfb655ae350fba4706d924a8ec7e3ba945671c", "https://bcr.bazel.build/modules/bazel_skylib/1.8.1/MODULE.bazel": "88ade7293becda963e0e3ea33e7d54d3425127e0a326e0d17da085a5f1f03ff6", "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/MODULE.bazel": "69ad6927098316848b34a9142bcc975e018ba27f08c4ff403f50c1b6e646ca67", + "https://bcr.bazel.build/modules/bazel_skylib/1.9.0/MODULE.bazel": "72997b29dfd95c3fa0d0c48322d05590418edef451f8db8db5509c57875fb4b7", "https://bcr.bazel.build/modules/bazel_skylib/1.9.2/MODULE.bazel": "8c51259b0f4481475586dbfede7591e57b75702e637f840f6138eec80c34b270", "https://bcr.bazel.build/modules/bazel_skylib/1.9.2/source.json": "41cbde7546542dee2f26e3f10bf1c4ac57909943194b7ac48cc5238a01893aa8", "https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84", @@ -170,8 +170,8 @@ "https://bcr.bazel.build/modules/rules_nodejs/6.7.5/source.json": "d60ee5a76258b1c8f99545ed24172b44d43ba64ca1a2dfc04371ef203df19fdf", "https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc", "https://bcr.bazel.build/modules/rules_pkg/1.0.1/MODULE.bazel": "5b1df97dbc29623bccdf2b0dcd0f5cb08e2f2c9050aab1092fd39a41e82686ff", - "https://bcr.bazel.build/modules/rules_pkg/1.2.0/MODULE.bazel": "c7db3c2b407e673c7a39e3625dc05dc9f12d6682cbd82a3a5924a13b491eda7e", - "https://bcr.bazel.build/modules/rules_pkg/1.2.0/source.json": "9062e00845bf91a4247465d371baa837adf9b6ff44c542f73ba084f07667e1dc", + "https://bcr.bazel.build/modules/rules_pkg/1.3.0/MODULE.bazel": "ae0bdefbacc990c91f843206c90cf0f4be620639a5bf22119043599ba86d51a3", + "https://bcr.bazel.build/modules/rules_pkg/1.3.0/source.json": "58ae84c545141762f7c434c0ce78bfb55ef0be08d84863e7f0503fff135fe4e2", "https://bcr.bazel.build/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06", "https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7", "https://bcr.bazel.build/modules/rules_proto/6.0.0/MODULE.bazel": "b531d7f09f58dce456cd61b4579ce8c86b38544da75184eadaf0a7cb7966453f", @@ -185,7 +185,6 @@ "https://bcr.bazel.build/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58", "https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c", "https://bcr.bazel.build/modules/rules_python/0.40.0/MODULE.bazel": "9d1a3cd88ed7d8e39583d9ffe56ae8a244f67783ae89b60caafc9f5cf318ada7", - "https://bcr.bazel.build/modules/rules_python/1.0.0/MODULE.bazel": "898a3d999c22caa585eb062b600f88654bf92efb204fa346fb55f6f8edffca43", "https://bcr.bazel.build/modules/rules_python/1.7.0/MODULE.bazel": "d01f995ecd137abf30238ad9ce97f8fc3ac57289c8b24bd0bf53324d937a14f8", "https://bcr.bazel.build/modules/rules_python/1.7.0/source.json": "028a084b65dcf8f4dc4f82f8778dbe65df133f234b316828a82e060d81bdce32", "https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c", @@ -201,14 +200,12 @@ "https://bcr.bazel.build/modules/stardoc/0.7.2/MODULE.bazel": "fc152419aa2ea0f51c29583fab1e8c99ddefd5b3778421845606ee628629e0e5", "https://bcr.bazel.build/modules/stardoc/0.7.2/source.json": "58b029e5e901d6802967754adf0a9056747e8176f017cfe3607c0851f4d42216", "https://bcr.bazel.build/modules/tar.bzl/0.10.4/MODULE.bazel": "e8f9ff79199e8d9eaad7f1b0a77ad74b30bb82d794b87d8ca942bead5de83ae9", - "https://bcr.bazel.build/modules/tar.bzl/0.10.7/MODULE.bazel": "e06d0072c8adef7b9efbeec951d7e6b4b7e6bfa5845c3d8f402289c7b5d6331c", - "https://bcr.bazel.build/modules/tar.bzl/0.10.7/source.json": "c660155f239fcfadfb85f0b9ff304b95390632c15e3f6cb718cd3e08f2bb5c86", + "https://bcr.bazel.build/modules/tar.bzl/0.10.8/MODULE.bazel": "443884cabe241f640cfef256b1de2ccb116752895532900a4be9500e1124323f", + "https://bcr.bazel.build/modules/tar.bzl/0.10.8/source.json": "4173be64b38e471d92d2eb139a6496311de6de75e710f04edce78c43122c5419", "https://bcr.bazel.build/modules/tar.bzl/0.2.1/MODULE.bazel": "52d1c00a80a8cc67acbd01649e83d8dd6a9dc426a6c0b754a04fe8c219c76468", "https://bcr.bazel.build/modules/tar.bzl/0.5.1/MODULE.bazel": "7c2eb3dcfc53b0f3d6f9acdfd911ca803eaf92aadf54f8ca6e4c1f3aee288351", - "https://bcr.bazel.build/modules/tar.bzl/0.6.0/MODULE.bazel": "a3584b4edcfafcabd9b0ef9819808f05b372957bbdff41601429d5fd0aac2e7c", "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", "https://bcr.bazel.build/modules/yq.bzl/0.1.1/MODULE.bazel": "9039681f9bcb8958ee2c87ffc74bdafba9f4369096a2b5634b88abc0eaefa072", - "https://bcr.bazel.build/modules/yq.bzl/0.3.2/MODULE.bazel": "0384efa70e8033d842ea73aa4b7199fa099709e236a7264345c03937166670b6", "https://bcr.bazel.build/modules/yq.bzl/0.3.4/MODULE.bazel": "d3a270662f5d766cd7229732d65a5a5bc485240c3007343dd279edfb60c9ae27", "https://bcr.bazel.build/modules/yq.bzl/0.3.6/MODULE.bazel": "985c2a0cb4ad9994bb0e33cc7fae931c91105eeefe3faa355b8f4c258d0607c0", "https://bcr.bazel.build/modules/yq.bzl/0.3.6/source.json": "678aaf6e291164f3cd761bb3e872e8a151248f413dbb63c5524a50b82a5bc890", @@ -222,8 +219,8 @@ "moduleExtensions": { "@@aspect_rules_esbuild+//esbuild:extensions.bzl%esbuild": { "general": { - "bzlTransitiveDigest": "Nhop9eqlQBcxge+uYzGmhiFAZmagl+D5CUXkaUpOBCc=", - "usagesDigest": "LSQ+zZp7JNgnBONTxxXnwGr4NTh2qtQYk7qwXXz5qWo=", + "bzlTransitiveDigest": "eGsGosHZTHOEZx11j7nDLNMHxCkESoe1oypcL3+G6GI=", + "usagesDigest": "LZ71sshnfqI8R/HeWwBTwawxIa7KnitayBl2hYrqPo4=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, "envVariables": {}, @@ -438,7 +435,7 @@ "@@aspect_tools_telemetry+//:extension.bzl%telemetry": { "general": { "bzlTransitiveDigest": "4w9RM0xjdKo1crk5zL20a/TuhqO0P1z1LsuXDneBXD4=", - "usagesDigest": "q7mSkkDF8zaV9mtEPYuvConVymuL69UDTnd+J5Pgo0w=", + "usagesDigest": "VoGyqZ+r/3juGZUfSXD4FAe965Z3XS4WANRrYH79+1Q=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, "envVariables": { @@ -449,9 +446,9 @@ "repoRuleId": "@@aspect_tools_telemetry+//:extension.bzl%tel_repository", "attributes": { "deps": { - "aspect_rules_js": "3.3.1", - "aspect_rules_ts": "3.9.2", - "aspect_rules_esbuild": "0.26.0", + "aspect_rules_js": "3.4.0", + "aspect_rules_ts": "3.10.0", + "aspect_rules_esbuild": "0.27.0", "aspect_rules_jasmine": "2.0.4", "aspect_tools_telemetry": "0.4.2" }, @@ -539,7 +536,7 @@ }, "@@rules_browsers+//browsers:extensions.bzl%browsers": { "general": { - "bzlTransitiveDigest": "w4tgtXA7WLl79lEQFfedhl7OVEGAKFqAFLnlI5c/Rtk=", + "bzlTransitiveDigest": "85AglD+Q1+ncEIApuW5Ng/2cY4OkSMPrOZ05Jp4CTmk=", "usagesDigest": "FmXYJVoVJlnfUU8x8gObSvu4qWcco/9Faw61aC/wBF0=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, @@ -548,9 +545,9 @@ "rules_browsers_chrome_linux": { "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", "attributes": { - "sha256": "7220f7149a01e85e631cb949b03fd472eba72fe0a92c6d8dab7833dd94e0c72c", + "sha256": "1a20882bf1a4f9d1167171b6905efe359bb229be1ce636ae9c931f3d285fbabe", "urls": [ - "https://storage.googleapis.com/chrome-for-testing-public/153.0.7981.3/linux64/chrome-headless-shell-linux64.zip" + "https://storage.googleapis.com/chrome-for-testing-public/154.0.8013.0/linux64/chrome-headless-shell-linux64.zip" ], "named_files": { "CHROME-HEADLESS-SHELL": "chrome-headless-shell-linux64/chrome-headless-shell" @@ -566,9 +563,9 @@ "rules_browsers_chrome_mac": { "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", "attributes": { - "sha256": "0d80d90379960943f3dc9f5eb966876010dd950736c97386e375581a56baf7c7", + "sha256": "1b0e0a078fc62c2b9f8cf837d41461084bd4064cf50cfc15ff84447a1f1b8dab", "urls": [ - "https://storage.googleapis.com/chrome-for-testing-public/153.0.7981.3/mac-x64/chrome-headless-shell-mac-x64.zip" + "https://storage.googleapis.com/chrome-for-testing-public/154.0.8013.0/mac-x64/chrome-headless-shell-mac-x64.zip" ], "named_files": { "CHROME-HEADLESS-SHELL": "chrome-headless-shell-mac-x64/chrome-headless-shell" @@ -584,9 +581,9 @@ "rules_browsers_chrome_mac_arm": { "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", "attributes": { - "sha256": "7fad05ead762524231ed026cf40385cba80cb3daab2973622f3533308fc045fa", + "sha256": "7438714da3696778b697b277a2df1d9701b99819ae58f5ab809b1009933677ec", "urls": [ - "https://storage.googleapis.com/chrome-for-testing-public/153.0.7981.3/mac-arm64/chrome-headless-shell-mac-arm64.zip" + "https://storage.googleapis.com/chrome-for-testing-public/154.0.8013.0/mac-arm64/chrome-headless-shell-mac-arm64.zip" ], "named_files": { "CHROME-HEADLESS-SHELL": "chrome-headless-shell-mac-arm64/chrome-headless-shell" @@ -602,9 +599,9 @@ "rules_browsers_chrome_win64": { "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", "attributes": { - "sha256": "2c08f3f53115434e3dd623e2e1f1f54896fa4b2cbaf86518d7f4708dc80fe67d", + "sha256": "4349c800be1f063adc0f1a7e820d024b43ea73917a6893a3f6b3797a5200d49b", "urls": [ - "https://storage.googleapis.com/chrome-for-testing-public/153.0.7981.3/win64/chrome-headless-shell-win64.zip" + "https://storage.googleapis.com/chrome-for-testing-public/154.0.8013.0/win64/chrome-headless-shell-win64.zip" ], "named_files": { "CHROME-HEADLESS-SHELL": "chrome-headless-shell-win64/chrome-headless-shell.exe" @@ -620,9 +617,9 @@ "rules_browsers_chromedriver_linux": { "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", "attributes": { - "sha256": "8bab44616dd81669a7a99d77c6b7e952ee3ec3bfd01ddf864cf795a880cfbd92", + "sha256": "d275f01236831bd44c6b6712ba5bec0a028b1ce9473a6182aceae6657740a3d3", "urls": [ - "https://storage.googleapis.com/chrome-for-testing-public/153.0.7981.3/linux64/chromedriver-linux64.zip" + "https://storage.googleapis.com/chrome-for-testing-public/154.0.8013.0/linux64/chromedriver-linux64.zip" ], "named_files": { "CHROMEDRIVER": "chromedriver-linux64/chromedriver" @@ -636,9 +633,9 @@ "rules_browsers_chromedriver_mac": { "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", "attributes": { - "sha256": "d1f8618664ec5b0433c19b36eef91b55671a1adf595ffd012da8b859c32dc7b1", + "sha256": "cb331889191aa8ab11be6d5a3cc4de760f8cf63dbea5d1f22c80cf16ae68e46f", "urls": [ - "https://storage.googleapis.com/chrome-for-testing-public/153.0.7981.3/mac-x64/chromedriver-mac-x64.zip" + "https://storage.googleapis.com/chrome-for-testing-public/154.0.8013.0/mac-x64/chromedriver-mac-x64.zip" ], "named_files": { "CHROMEDRIVER": "chromedriver-mac-x64/chromedriver" @@ -652,9 +649,9 @@ "rules_browsers_chromedriver_mac_arm": { "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", "attributes": { - "sha256": "3c8b50af153cf6e8de8f2ec9f4b9a1ce717cd76a3ca6c82f9e736d7b705bdbe2", + "sha256": "1584dc9ae35f4a22c4ba4b142335d91d6d3096813395d3acaf135fb6e716ce8e", "urls": [ - "https://storage.googleapis.com/chrome-for-testing-public/153.0.7981.3/mac-arm64/chromedriver-mac-arm64.zip" + "https://storage.googleapis.com/chrome-for-testing-public/154.0.8013.0/mac-arm64/chromedriver-mac-arm64.zip" ], "named_files": { "CHROMEDRIVER": "chromedriver-mac-arm64/chromedriver" @@ -668,9 +665,9 @@ "rules_browsers_chromedriver_win64": { "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", "attributes": { - "sha256": "2fdba8a5036173c57627b38df844e2f9c6634dd027eaa5a5f306033e14414e08", + "sha256": "061b4d1be9eb2a11ed036b3e562b565ebfd257fcd7cda3681a58e63bff5dd6d0", "urls": [ - "https://storage.googleapis.com/chrome-for-testing-public/153.0.7981.3/win64/chromedriver-win64.zip" + "https://storage.googleapis.com/chrome-for-testing-public/154.0.8013.0/win64/chromedriver-win64.zip" ], "named_files": { "CHROMEDRIVER": "chromedriver-win64/chromedriver.exe" @@ -684,9 +681,9 @@ "rules_browsers_firefox_linux": { "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", "attributes": { - "sha256": "bfc57e7b6b4e6204b11e7e03c4b93cff708e9fb37f6b9948be243455311d82ee", + "sha256": "7665cd49ab13417270748325838e565136adbc76d41bbd76fb24d15a0cc7792b", "urls": [ - "https://archive.mozilla.org/pub/firefox/releases/153.0/linux-x86_64/en-US/firefox-153.0.tar.xz" + "https://archive.mozilla.org/pub/firefox/releases/154.0/linux-x86_64/en-US/firefox-154.0.tar.xz" ], "named_files": { "FIREFOX": "firefox/firefox" @@ -700,9 +697,9 @@ "rules_browsers_firefox_mac": { "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", "attributes": { - "sha256": "2f9b5a20e546e7e79e4182f8fe10353a3e251635963ab3f6d399a3f290adeb96", + "sha256": "b0295d3b77ec632a60282cf2b9770e1dada085879d00e43d8d23fddf5015e06a", "urls": [ - "https://archive.mozilla.org/pub/firefox/releases/153.0/mac/en-US/Firefox%20153.0.dmg" + "https://archive.mozilla.org/pub/firefox/releases/154.0/mac/en-US/Firefox%20154.0.dmg" ], "named_files": { "FIREFOX": "Firefox.app/Contents/MacOS/firefox" @@ -716,9 +713,9 @@ "rules_browsers_firefox_mac_arm": { "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", "attributes": { - "sha256": "2f9b5a20e546e7e79e4182f8fe10353a3e251635963ab3f6d399a3f290adeb96", + "sha256": "b0295d3b77ec632a60282cf2b9770e1dada085879d00e43d8d23fddf5015e06a", "urls": [ - "https://archive.mozilla.org/pub/firefox/releases/153.0/mac/en-US/Firefox%20153.0.dmg" + "https://archive.mozilla.org/pub/firefox/releases/154.0/mac/en-US/Firefox%20154.0.dmg" ], "named_files": { "FIREFOX": "Firefox.app/Contents/MacOS/firefox" @@ -732,9 +729,9 @@ "rules_browsers_firefox_win64": { "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", "attributes": { - "sha256": "b409ff5af3419214f11f928e67b1f44c1ad0f8a46f7db7e697caedefa02a6d2a", + "sha256": "6b3c996d27b50dca8e5b6dd2956233cc6bf4720cea3f0847a9423c2cd6759e73", "urls": [ - "https://archive.mozilla.org/pub/firefox/releases/153.0/win64/en-US/Firefox%20Setup%20153.0.exe" + "https://archive.mozilla.org/pub/firefox/releases/154.0/win64/en-US/Firefox%20Setup%20154.0.exe" ], "named_files": { "FIREFOX": "core/firefox.exe" @@ -1175,7 +1172,7 @@ "@@yq.bzl+//yq:extensions.bzl%yq": { "general": { "bzlTransitiveDigest": "UfFMy8CWK4/dVo/tfaSAIYUiDGNAPes5eRllx9O9Q9Q=", - "usagesDigest": "hKjC8oZmSZDlZ1NsPZgCEomqFtqBXEQo88wUuHfg4F4=", + "usagesDigest": "9kbJ6OfvQpAM3otgkGgS5xpOHOoVgV5ZJ9jSgMgUG2g=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, "envVariables": {}, diff --git a/modules/testing/builder/package.json b/modules/testing/builder/package.json index 1a7fa852247a..f2db890d966d 100644 --- a/modules/testing/builder/package.json +++ b/modules/testing/builder/package.json @@ -4,12 +4,12 @@ "@angular-devkit/build-angular": "workspace:*", "@angular-devkit/core": "workspace:*", "@angular/ssr": "workspace:*", - "@vitest/coverage-v8": "4.1.10", + "@vitest/coverage-v8": "4.1.11", "browser-sync": "3.0.4", "istanbul-lib-instrument": "6.0.3", "jsdom": "30.0.1", - "ng-packagr": "22.2.0-next.2", + "ng-packagr": "22.2.0-next.3", "rxjs": "7.8.2", - "vitest": "4.1.10" + "vitest": "4.1.11" } } diff --git a/package.json b/package.json index 935b6cae3ecd..b4a082868bcb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@angular/devkit-repo", - "version": "22.2.0-next.2", + "version": "22.2.0-next.5", "private": true, "description": "Software Development Kit for Angular", "keywords": [ @@ -28,12 +28,12 @@ "type": "git", "url": "git+https://github.com/angular/angular-cli.git" }, - "packageManager": "pnpm@11.20.0", + "packageManager": "pnpm@11.24.0", "engines": { "node": "^22.22.3 || ^24.15.0 || >=26.0.0", "npm": "Please use pnpm instead of NPM to install dependencies", "yarn": "Please use pnpm instead of Yarn to install dependencies", - "pnpm": "11.20.0" + "pnpm": "11.24.0" }, "author": "Angular Authors", "license": "MIT", @@ -42,23 +42,23 @@ }, "homepage": "https://github.com/angular/angular-cli", "dependencies": { - "@angular/compiler-cli": "22.2.0-next.0", + "@angular/compiler-cli": "22.2.0-next.3", "typescript": "6.0.3" }, "devDependencies": { - "@angular/animations": "22.2.0-next.0", - "@angular/cdk": "22.1.0-rc.0", - "@angular/common": "22.2.0-next.0", - "@angular/compiler": "22.2.0-next.0", - "@angular/core": "22.2.0-next.0", - "@angular/forms": "22.2.0-next.0", - "@angular/localize": "22.2.0-next.0", - "@angular/material": "22.1.0-rc.0", - "@angular/ng-dev": "https://github.com/angular/dev-infra-private-ng-dev-builds.git#dcf9b6776377d5d301e054111add3ef80f433b70", - "@angular/platform-browser": "22.2.0-next.0", - "@angular/platform-server": "22.2.0-next.0", - "@angular/router": "22.2.0-next.0", - "@angular/service-worker": "22.2.0-next.0", + "@angular/animations": "22.2.0-next.3", + "@angular/cdk": "22.2.0-next.2", + "@angular/common": "22.2.0-next.3", + "@angular/compiler": "22.2.0-next.3", + "@angular/core": "22.2.0-next.3", + "@angular/forms": "22.2.0-next.3", + "@angular/localize": "22.2.0-next.3", + "@angular/material": "22.2.0-next.2", + "@angular/ng-dev": "https://github.com/angular/dev-infra-private-ng-dev-builds.git#bcad06e14cb29b52b0856020af5e2ab868075602", + "@angular/platform-browser": "22.2.0-next.3", + "@angular/platform-server": "22.2.0-next.3", + "@angular/router": "22.2.0-next.3", + "@angular/service-worker": "22.2.0-next.3", "@babel/core": "8.0.1", "@bazel/bazelisk": "1.28.1", "@bazel/buildifier": "8.2.1", @@ -70,7 +70,7 @@ "@rollup/plugin-commonjs": "^29.0.0", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "16.0.3", - "@rollup/wasm-node": "4.62.4", + "@rollup/wasm-node": "4.62.5", "@stylistic/eslint-plugin": "^5.0.0", "@tony.ganchev/eslint-plugin-header": "~3.4.0", "@types/babel__core": "7.20.5", @@ -89,21 +89,20 @@ "@types/picomatch": "^4.0.0", "@types/progress": "^2.0.3", "@types/semver": "^7.3.12", - "@types/watchpack": "^2.4.4", "@types/yargs": "^17.0.20", "@types/yargs-parser": "^21.0.0", - "@typescript-eslint/eslint-plugin": "8.65.0", - "@typescript-eslint/parser": "8.65.0", + "@typescript-eslint/eslint-plugin": "8.67.0", + "@typescript-eslint/parser": "8.67.0", "ajv": "8.20.0", "buffer": "6.0.3", - "esbuild": "0.28.1", - "esbuild-wasm": "0.28.1", - "eslint": "10.8.0", + "esbuild": "0.28.2", + "esbuild-wasm": "0.28.2", + "eslint": "10.9.0", "eslint-config-prettier": "10.1.8", "eslint-plugin-import": "2.32.0", "express": "5.2.1", "fast-glob": "3.3.3", - "globals": "17.9.0", + "globals": "17.11.0", "http-proxy": "^1.18.1", "http-proxy-middleware": "4.2.0", "husky": "9.1.7", @@ -115,23 +114,23 @@ "karma-chrome-launcher": "~3.2.0", "karma-coverage": "~2.2.0", "karma-jasmine": "~5.1.0", - "karma-jasmine-html-reporter": "~2.2.0", + "karma-jasmine-html-reporter": "~2.3.0", "karma-source-map-support": "1.4.0", "lodash": "^4.17.21", - "magic-string": "1.1.0", + "magic-string": "1.2.2", "prettier": "^3.0.0", - "puppeteer": "25.4.0", + "puppeteer": "25.8.0", "quicktype-core": "26.0.0", - "rollup": "4.62.4", + "rollup": "4.62.5", "rollup-license-plugin": "~3.2.0", - "rollup-plugin-dts": "6.4.1", + "rollup-plugin-dts": "6.5.1", "rollup-plugin-sourcemaps2": "0.5.8", "semver": "7.8.5", "source-map-support": "0.5.21", "tslib": "2.8.1", - "undici": "8.9.0", + "undici": "8.10.0", "unenv": "^1.10.0", - "verdaccio": "6.9.2", + "verdaccio": "6.10.0", "verdaccio-auth-memory": "^13.0.0", "zone.js": "^0.16.0" }, diff --git a/packages/angular/build/BUILD.bazel b/packages/angular/build/BUILD.bazel index b2f0cdfd63f1..e63b084ec458 100644 --- a/packages/angular/build/BUILD.bazel +++ b/packages/angular/build/BUILD.bazel @@ -84,9 +84,11 @@ ts_project( ":node_modules/@babel/core", ":node_modules/@inquirer/confirm", ":node_modules/@oxc-project/types", + ":node_modules/@parcel/watcher", ":node_modules/@vitejs/plugin-basic-ssl", ":node_modules/beasties", ":node_modules/browserslist", + ":node_modules/chokidar", ":node_modules/https-proxy-agent", ":node_modules/istanbul-lib-instrument", ":node_modules/jsonc-parser", @@ -104,11 +106,12 @@ ts_project( ":node_modules/rolldown", ":node_modules/rollup", ":node_modules/sass", + ":node_modules/sass-embedded", ":node_modules/source-map-support", ":node_modules/tinyglobby", ":node_modules/vite", ":node_modules/vitest", - ":node_modules/watchpack", + ":node_modules/xxhash-wasm", "//:node_modules/@angular/common", "//:node_modules/@angular/compiler", "//:node_modules/@angular/compiler-cli", @@ -123,7 +126,6 @@ ts_project( "//:node_modules/@types/node", "//:node_modules/@types/picomatch", "//:node_modules/@types/semver", - "//:node_modules/@types/watchpack", "//:node_modules/esbuild", "//:node_modules/esbuild-wasm", "//:node_modules/karma", @@ -337,7 +339,7 @@ npm_package( ], stamp_files = [ "src/utils/version.js", - "src/tools/esbuild/utils.js", + "src/tools/esbuild/target.js", "src/utils/normalize-cache.js", "src/utils/supported-browsers.js", ], diff --git a/packages/angular/build/package.json b/packages/angular/build/package.json index 633064827709..609cf3132ce0 100644 --- a/packages/angular/build/package.json +++ b/packages/angular/build/package.json @@ -21,27 +21,30 @@ "@ampproject/remapping": "2.3.0", "@angular-devkit/architect": "workspace:0.0.0-EXPERIMENTAL-PLACEHOLDER", "@babel/core": "8.0.1", - "@inquirer/confirm": "6.1.1", + "@inquirer/confirm": "6.2.0", + "@parcel/watcher": "2.6.0", "@vitejs/plugin-basic-ssl": "2.3.0", "beasties": "0.4.3", "browserslist": "^4.26.0", - "esbuild": "0.28.1", + "chokidar": "5.0.0", + "esbuild": "0.28.2", "https-proxy-agent": "9.1.0", "jsonc-parser": "3.3.1", "listr2": "11.0.0", - "magic-string": "1.1.0", + "magic-string": "1.2.2", "mrmime": "2.0.1", - "oxc-parser": "0.142.0", + "oxc-parser": "0.146.0", "parse5-html-rewriting-stream": "8.0.1", "picomatch": "4.0.5", - "piscina": "5.3.0", - "rolldown": "1.2.1", - "sass": "1.102.0", + "piscina": "5.3.1", + "rolldown": "1.2.5", + "sass": "1.103.1", + "sass-embedded": "1.103.1", "semver": "7.8.5", "source-map-support": "0.5.21", "tinyglobby": "0.2.17", - "vite": "8.2.0", - "watchpack": "2.5.2" + "vite": "8.2.2", + "xxhash-wasm": "1.1.0" }, "optionalDependencies": { "lmdb": "3.5.6" @@ -49,15 +52,15 @@ "devDependencies": { "@angular-devkit/core": "workspace:*", "@angular/ssr": "workspace:*", - "@oxc-project/types": "0.142.0", + "@oxc-project/types": "0.146.0", "istanbul-lib-instrument": "6.0.3", "jsdom": "30.0.1", - "less": "4.8.1", - "ng-packagr": "22.2.0-next.2", - "postcss": "8.5.25", - "rollup": "4.62.4", + "less": "4.9.0", + "ng-packagr": "22.2.0-next.3", + "postcss": "8.5.26", + "rollup": "4.62.5", "rxjs": "7.8.2", - "vitest": "4.1.10" + "vitest": "4.1.11" }, "peerDependencies": { "@angular/compiler": "0.0.0-ANGULAR-FW-PEER-DEP", diff --git a/packages/angular/build/src/builders/application/build-action.ts b/packages/angular/build/src/builders/application/build-action.ts index dbec8d687b9f..e71663765274 100644 --- a/packages/angular/build/src/builders/application/build-action.ts +++ b/packages/angular/build/src/builders/application/build-action.ts @@ -15,6 +15,7 @@ import { shutdownSassWorkerPool } from '../../tools/esbuild/stylesheets/sass-lan import { logMessages, withNoProgress, withSpinner } from '../../tools/esbuild/utils'; import { ChangedFiles } from '../../tools/esbuild/watcher'; import { shouldWatchRoot } from '../../utils/environment-options'; +import { initializeHash } from '../../utils/hash'; import { NormalizedCachedOptions } from '../../utils/normalize-cache'; import { toPosixPath } from '../../utils/path'; import { NormalizedApplicationBuildOptions, NormalizedOutputOptions } from './options'; @@ -41,6 +42,7 @@ const packageWatchFiles = [ '.pnp.data.json', ]; +// eslint-disable-next-line max-lines-per-function export async function* runEsBuildBuildAction( action: (rebuildState?: RebuildState) => Promise, options: { @@ -78,6 +80,8 @@ export async function* runEsBuildBuildAction( incrementalResults, } = options; + await initializeHash(); + const withProgress: typeof withSpinner = progress ? withSpinner : withNoProgress; // Initial build @@ -104,20 +108,25 @@ export async function* runEsBuildBuildAction( logger.info('Watch mode enabled. Watching for file changes...'); } + const normalizedOutputBase = toPosixPath(outputOptions.base); + const normalizedCacheBase = toPosixPath(cacheOptions.basePath); const ignored: string[] = [ // Ignore the output and cache paths to avoid infinite rebuild cycles - outputOptions.base, - cacheOptions.basePath, + normalizedOutputBase, + `${normalizedOutputBase}/**`, + normalizedCacheBase, + `${normalizedCacheBase}/**`, `${toPosixPath(workspaceRoot)}/**/.*/**`, ]; // Setup a watcher const { createWatcher } = await import('../../tools/esbuild/watcher'); - watcher = createWatcher({ + watcher = await createWatcher({ polling: typeof poll === 'number', interval: poll, followSymlinks: preserveSymlinks, ignored, + cwd: workspaceRoot, }); // Setup abort support @@ -215,6 +224,9 @@ export async function* runEsBuildBuildAction( // Remove any stale locations if the build was successful if (staleWatchFiles?.size) { watcher.remove([...staleWatchFiles]); + for (const staleFile of staleWatchFiles) { + currentWatchFiles.delete(staleFile); + } } for (const outputResult of emitOutputResults( diff --git a/packages/angular/build/src/builders/application/chunk-optimizer.ts b/packages/angular/build/src/builders/application/chunk-optimizer.ts index 7d7a6e56b829..ea4c2f8076f9 100644 --- a/packages/angular/build/src/builders/application/chunk-optimizer.ts +++ b/packages/angular/build/src/builders/application/chunk-optimizer.ts @@ -20,7 +20,7 @@ import type { Message, Metafile } from 'esbuild'; import assert from 'node:assert'; import type { Plugin } from 'rollup'; -import { BundleContextResult } from '../../tools/esbuild/bundler-context'; +import { BundleMergedContextResult } from '../../tools/esbuild/bundler-context'; import { type BuildOutputFile, BuildOutputFileType, @@ -212,9 +212,9 @@ function createChunkOptimizationFailureMessage(message: string): Message { */ // eslint-disable-next-line max-lines-per-function export async function optimizeChunks( - original: BundleContextResult, + original: BundleMergedContextResult, sourcemap: boolean | 'hidden', -): Promise { +): Promise { // Failed builds cannot be optimized if (original.errors) { return original; @@ -235,7 +235,7 @@ export async function optimizeChunks( } // No action required if no browser main entrypoint or metafile for stats - if (!mainFile || !original.metafile) { + if (!mainFile || !original.metafiles.browser) { return original; } @@ -340,9 +340,9 @@ export async function optimizeChunks( } // Update metafile - const newMetafile = bundleOutputToEsbuildMetafile(optimizedOutput, original.metafile); + const newMetafile = bundleOutputToEsbuildMetafile(optimizedOutput, original.metafiles.browser); // Add back the outputs that were not part of the optimization - for (const [path, output] of Object.entries(original.metafile.outputs)) { + for (const [path, output] of Object.entries(original.metafiles.browser.outputs)) { if (usedChunks.has(path)) { continue; } @@ -350,11 +350,11 @@ export async function optimizeChunks( newMetafile.outputs[path] = output; for (const inputPath of Object.keys(output.inputs)) { if (!newMetafile.inputs[inputPath]) { - newMetafile.inputs[inputPath] = original.metafile.inputs[inputPath]; + newMetafile.inputs[inputPath] = original.metafiles.browser.inputs[inputPath]; } } } - original.metafile = newMetafile; + original.metafiles.browser = newMetafile; // Remove used chunks and associated sourcemaps from the original result original.outputFiles = original.outputFiles.filter( @@ -418,6 +418,7 @@ export async function optimizeChunks( const record = createInitialFileRecord(entryRecord.depth + 1); + original.initialFiles.set(importPath, record); entriesToAnalyze.push([importPath, record]); } } diff --git a/packages/angular/build/src/builders/application/execute-build.ts b/packages/angular/build/src/builders/application/execute-build.ts index faa61c6e523b..8fe488a8ff76 100644 --- a/packages/angular/build/src/builders/application/execute-build.ts +++ b/packages/angular/build/src/builders/application/execute-build.ts @@ -18,10 +18,11 @@ import { checkCommonJSModules } from '../../tools/esbuild/commonjs-checker'; import { LOCALE_DATA_BASE_MODULE } from '../../tools/esbuild/i18n-locale-plugin'; import { extractLicenses } from '../../tools/esbuild/license-extractor'; import { profileAsync } from '../../tools/esbuild/profiling'; +import { transformSupportedBrowsersToTargets } from '../../tools/esbuild/target'; import { calculateEstimatedTransferSizes, + isZonelessApp, logBuildStats, - transformSupportedBrowsersToTargets, } from '../../tools/esbuild/utils'; import { BudgetCalculatorResult, checkBudgets } from '../../utils/bundle-calculator'; import { optimizeChunksThreshold } from '../../utils/environment-options'; @@ -36,6 +37,9 @@ import { inlineI18n, loadActiveTranslations } from './i18n'; import { NormalizedApplicationBuildOptions } from './options'; import { createComponentStyleBundler, setupBundlerContexts } from './setup-bundling'; +/** The esbuild error text prefix used to detect top-level await errors. */ +const TOP_LEVEL_AWAIT_ERROR_TEXT = + 'Top-level await is not available in the configured target environment'; // eslint-disable-next-line max-lines-per-function export async function executeBuild( options: NormalizedApplicationBuildOptions, @@ -71,7 +75,7 @@ export async function executeBuild( let bundlerContexts; let componentStyleBundler; let codeBundleCache; - let bundlingResult: BundleContextResult; + let bundlingIndividualResults: BundleContextResult[]; let templateUpdates: Map | undefined; let angularCompilationContext: AngularCompilationContext | undefined; let executionResult: ExecutionResult | undefined; @@ -94,7 +98,7 @@ export async function executeBuild( // Bundle all contexts that do not require TypeScript changed file checks. // These will automatically use cached results based on the changed files. - bundlingResult = await BundlerContext.bundleAll( + bundlingIndividualResults = await BundlerContext.bundleAll( bundlerContexts.otherContexts, allFileChanges, ); @@ -108,7 +112,8 @@ export async function executeBuild( const result = await typescriptContext.bundle(forceTypeScriptRebuild); typescriptResults.push(result); } - bundlingResult = BundlerContext.mergeResults([bundlingResult, ...typescriptResults]); + + bundlingIndividualResults.push(...typescriptResults); } else { const target = transformSupportedBrowsersToTargets(browsers); codeBundleCache = new SourceFileCache(cacheOptions.enabled ? cacheOptions.path : undefined); @@ -137,7 +142,7 @@ export async function executeBuild( ); // Bundle everything on initial build - bundlingResult = await BundlerContext.bundleAll([ + bundlingIndividualResults = await BundlerContext.bundleAll([ ...bundlerContexts.typescriptContexts, ...bundlerContexts.otherContexts, ]); @@ -149,9 +154,11 @@ export async function executeBuild( componentStyleBundler.invalidate(rebuildState.fileChanges.all); const componentResults = await componentStyleBundler.bundleAllFiles(true, true); - bundlingResult = BundlerContext.mergeResults([bundlingResult, ...componentResults]); + bundlingIndividualResults.push(...componentResults); } + let bundlingResult = BundlerContext.mergeResults(bundlingIndividualResults); + executionResult.addWarnings(bundlingResult.warnings); // Add used external component style referenced files to be watched @@ -173,6 +180,24 @@ export async function executeBuild( // Return if the bundling has errors if (bundlingResult.errors) { + // If Zone.js is used, augment top-level await errors with a more helpful message. + // esbuild's default error mentions "target environment" with browser versions, but + // the actual reason is that async/await is downleveled for Zone.js compatibility. + if (!isZonelessApp(options.polyfills)) { + for (const error of bundlingResult.errors) { + if (error.text?.startsWith(TOP_LEVEL_AWAIT_ERROR_TEXT)) { + error.notes ??= []; + error.notes.push({ + text: + 'Top-level await is not supported in applications that use Zone.js. ' + + 'Consider removing Zone.js or moving this code into an async function. \n' + + 'For more information about zoneless Angular applications, visit: https://angular.dev/guide/zoneless', + location: null, + }); + } + } + } + executionResult.addErrors(bundlingResult.errors); return executionResult; @@ -183,8 +208,8 @@ export async function executeBuild( if (options.optimizationOptions.scripts) { // Count lazy chunks (files not needed for initial load). // Advanced chunk optimization is most beneficial when there are multiple lazy chunks. - const { metafile, initialFiles } = bundlingResult; - const lazyChunksCount = Object.keys(metafile.outputs).filter( + const { metafiles, initialFiles } = bundlingResult; + const lazyChunksCount = Object.keys(metafiles.browser.outputs || {}).filter( (path) => path.endsWith('.js') && !initialFiles.has(path), ).length; @@ -263,14 +288,19 @@ export async function executeBuild( executionResult.setExternalMetadata(implicitBrowser, implicitServer, [...explicitExternal]); } - const { metafile, initialFiles, outputFiles } = bundlingResult; + const { + metafiles: { browser: browserMetafile, server: serverMetafile }, + initialFiles, + outputFiles, + } = bundlingResult; + const metafiles = [browserMetafile, serverMetafile]; executionResult.outputFiles.push(...outputFiles); // Analyze files for bundle budget failures if present let budgetFailures: BudgetCalculatorResult[] | undefined; if (options.budgets) { - const compatStats = generateBudgetStats(metafile, outputFiles, initialFiles); + const compatStats = generateBudgetStats(browserMetafile, outputFiles, initialFiles); budgetFailures = [...checkBudgets(options.budgets, compatStats, true)]; for (const { message, severity } of budgetFailures) { if (severity === 'error') { @@ -289,7 +319,7 @@ export async function executeBuild( // Check metafile for CommonJS module usage if optimizing scripts if (optimizationOptions.scripts) { - const messages = checkCommonJSModules(metafile, options.allowedCommonJsDependencies); + const messages = checkCommonJSModules(browserMetafile, options.allowedCommonJsDependencies); executionResult.addWarnings(messages); } @@ -302,7 +332,7 @@ export async function executeBuild( if (options.extractLicenses) { executionResult.addOutputFile( '3rdpartylicenses.txt', - await extractLicenses(metafile, workspaceRoot), + await extractLicenses(metafiles, workspaceRoot), BuildOutputFileType.Root, ); } @@ -325,13 +355,13 @@ export async function executeBuild( // Perform i18n translation inlining if enabled if (i18nOptions.shouldInline) { - const result = await inlineI18n(metafile, options, executionResult, initialFiles); + const result = await inlineI18n(browserMetafile, options, executionResult, initialFiles); executionResult.addErrors(result.errors); executionResult.addWarnings(result.warnings); executionResult.addPrerenderedRoutes(result.prerenderedRoutes); } else { const result = await executePostBundleSteps( - metafile, + browserMetafile, options, executionResult.outputFiles, executionResult.assetFiles, @@ -349,19 +379,29 @@ export async function executeBuild( executionResult.assetFiles.push(...result.additionalAssets); } - executionResult.addOutputFile( - 'prerendered-routes.json', - JSON.stringify({ routes: executionResult.prerenderedRoutes }, null, 2), - BuildOutputFileType.Root, - ); + if (serverEntryPoint) { + executionResult.addOutputFile( + 'prerendered-routes.json', + JSON.stringify({ routes: executionResult.prerenderedRoutes }, null, 2), + BuildOutputFileType.Root, + ); + } - // Write metafile if stats option is enabled + // Write metafiles if stats option is enabled if (options.stats) { executionResult.addOutputFile( - 'stats.json', - JSON.stringify(metafile, null, 2), + 'browser-stats.json', + JSON.stringify(browserMetafile, null, 2), BuildOutputFileType.Root, ); + + if (serverEntryPoint) { + executionResult.addOutputFile( + 'server-stats.json', + JSON.stringify(serverMetafile, null, 2), + BuildOutputFileType.Root, + ); + } } if (!jsonLogs && !options.quiet) { @@ -369,7 +409,7 @@ export async function executeBuild( rebuildState && executionResult.findChangedFiles(rebuildState.previousOutputInfo); executionResult.addLog( logBuildStats( - metafile, + metafiles, outputFiles, initialFiles, budgetFailures, diff --git a/packages/angular/build/src/builders/application/execute-post-bundle.ts b/packages/angular/build/src/builders/application/execute-post-bundle.ts index 198071c5a280..cf76a14d9030 100644 --- a/packages/angular/build/src/builders/application/execute-post-bundle.ts +++ b/packages/angular/build/src/builders/application/execute-post-bundle.ts @@ -36,7 +36,7 @@ import { OutputMode } from './schema'; /** * Run additional builds steps including SSG, AppShell, Index HTML file and Service worker generation. - * @param metafile An esbuild metafile object. + * @param browserMetafile An esbuild metafile object. * @param options The normalized application builder options used to create the build. * @param outputFiles The output files of an executed build. * @param assetFiles The assets of an executed build. @@ -45,7 +45,7 @@ import { OutputMode } from './schema'; */ // eslint-disable-next-line max-lines-per-function export async function executePostBundleSteps( - metafile: Metafile, + browserMetafile: Metafile, options: NormalizedApplicationBuildOptions, outputFiles: BuildOutputFile[], assetFiles: BuildOutputAsset[], @@ -131,7 +131,7 @@ export async function executePostBundleSteps( locale, baseHref, initialFilesPaths, - metafile, + browserMetafile, publicPath, ); @@ -214,7 +214,7 @@ export async function executePostBundleSteps( locale, baseHref, initialFilesPaths, - metafile, + browserMetafile, publicPath, ); diff --git a/packages/angular/build/src/builders/application/i18n.ts b/packages/angular/build/src/builders/application/i18n.ts index c83f1a29a30a..f212659a14be 100644 --- a/packages/angular/build/src/builders/application/i18n.ts +++ b/packages/angular/build/src/builders/application/i18n.ts @@ -8,6 +8,8 @@ import { BuilderContext } from '@angular-devkit/architect'; import type { Metafile } from 'esbuild'; +import assert from 'node:assert'; +import { readFile } from 'node:fs/promises'; import { join } from 'node:path'; import { ExecutionResult, @@ -18,6 +20,7 @@ import { I18nInliner } from '../../tools/esbuild/i18n-inliner'; import { maxWorkers } from '../../utils/environment-options'; import { loadTranslations } from '../../utils/i18n-options'; import { createTranslationLoader } from '../../utils/load-translations'; +import { createProjectResolver } from '../../utils/resolve-project'; import { executePostBundleSteps } from './execute-post-bundle'; import { NormalizedApplicationBuildOptions, getLocaleBaseHref } from './options'; @@ -48,6 +51,7 @@ export async function inlineI18n( outputFiles: executionResult.outputFiles, shouldOptimize: optimizationOptions.scripts, persistentCachePath: cacheOptions.enabled ? cacheOptions.path : undefined, + localizeVersion: i18nOptions.localizeVersion, }, maxWorkers, ); @@ -71,12 +75,29 @@ export async function inlineI18n( ); try { - for (const locale of i18nOptions.inlineLocales) { - // A locale specific set of files is returned from the inliner. - const localeInlineResult = await inliner.inlineForLocale( + const localesToInline = Array.from(i18nOptions.inlineLocales, (locale) => { + const localeDescription = i18nOptions.locales[locale]; + let translationIntegrity: string | undefined = ''; + for (const file of localeDescription.files) { + if (!file.integrity) { + translationIntegrity = undefined; + break; + } + translationIntegrity += (translationIntegrity ? '|' : '') + file.integrity; + } + + return { locale, - i18nOptions.locales[locale].translation, - ); + translation: localeDescription.translation, + translationIntegrity, + }; + }); + + const inlinedLocales = await inliner.inlineAll(localesToInline); + + for (const locale of i18nOptions.inlineLocales) { + const localeInlineResult = inlinedLocales.get(locale); + assert(localeInlineResult !== undefined, 'Inlined result must exist for locale: ' + locale); const localeOutputFiles = localeInlineResult.outputFiles; inlineResult.errors.push(...localeInlineResult.errors); inlineResult.warnings.push(...localeInlineResult.warnings); @@ -176,6 +197,15 @@ export async function loadActiveTranslations( context: BuilderContext, i18n: NormalizedApplicationBuildOptions['i18nOptions'], ) { + if (!i18n.localizeVersion) { + try { + const projectResolve = createProjectResolver(context.workspaceRoot); + const manifestPath = projectResolve('@angular/localize/package.json'); + const manifest = JSON.parse(await readFile(manifestPath, 'utf-8')) as { version?: string }; + i18n.localizeVersion = manifest.version; + } catch {} + } + // Load locale data and translations (if present) let loader; for (const [locale, desc] of Object.entries(i18n.locales)) { diff --git a/packages/angular/build/src/builders/application/options.ts b/packages/angular/build/src/builders/application/options.ts index b3c180843f70..abe3298aa093 100644 --- a/packages/angular/build/src/builders/application/options.ts +++ b/packages/angular/build/src/builders/application/options.ts @@ -126,6 +126,20 @@ interface InternalOptions { * Suppress build summary and stats table. */ quiet?: boolean; + + /** + * Disables esbuild code splitting for the browser code bundle. + * + * Splitting emits shared chunks whose exports are read across chunk boundaries as live ESM + * bindings. A module hoisted into a shared chunk is wrapped in a lazy initializer, so its exported + * value is only assigned once that initializer runs. Runners that load the generated output + * through a module runner rather than the browser's own ESM implementation do not reliably + * preserve those bindings, and an importing chunk can observe the export as `undefined`. + * + * Test bundles are never downloaded by a browser, so there is nothing for splitting to optimize + * there. Used exclusively for tests and shouldn't be used for other kinds of builds. + */ + disableCodeSplitting?: boolean; } /** Full set of options for `application` builder. */ @@ -439,6 +453,7 @@ export async function normalizeOptions( partialSSRBuild = false, externalRuntimeStyles, instrumentForCoverage, + disableCodeSplitting, } = options; // Return all the normalized options @@ -475,6 +490,7 @@ export async function normalizeOptions( watch, workspaceRoot, entryPoints, + disableCodeSplitting, optimizationOptions, outputOptions, outExtension, diff --git a/packages/angular/build/src/builders/application/setup-bundling.ts b/packages/angular/build/src/builders/application/setup-bundling.ts index bb53c0bae902..4380db080e87 100644 --- a/packages/angular/build/src/builders/application/setup-bundling.ts +++ b/packages/angular/build/src/builders/application/setup-bundling.ts @@ -19,7 +19,8 @@ import { import { BundlerContext } from '../../tools/esbuild/bundler-context'; import { createGlobalScriptsBundleOptions } from '../../tools/esbuild/global-scripts'; import { createGlobalStylesBundleOptions } from '../../tools/esbuild/global-styles'; -import { getSupportedNodeTargets } from '../../tools/esbuild/utils'; +import { MemoryLoadResultCache } from '../../tools/esbuild/load-result-cache'; +import { getSupportedNodeTargets } from '../../tools/esbuild/target'; import type { NormalizedApplicationBuildOptions } from './options'; /** @@ -94,11 +95,19 @@ export function setupBundlerContexts( // Global Stylesheets if (options.globalStyles.length > 0) { + const globalStylesCache = new MemoryLoadResultCache(); for (const initial of [true, false]) { const bundleOptions = createGlobalStylesBundleOptions(options, target, initial); if (bundleOptions) { otherContexts.push( - new BundlerContext(workspaceRoot, watch, bundleOptions, true, () => initial), + new BundlerContext( + workspaceRoot, + watch, + bundleOptions, + true, + () => initial, + globalStylesCache, + ), ); } } diff --git a/packages/angular/build/src/builders/application/tests/behavior/top-level-await-error_spec.ts b/packages/angular/build/src/builders/application/tests/behavior/top-level-await-error_spec.ts new file mode 100644 index 000000000000..b0220529ee28 --- /dev/null +++ b/packages/angular/build/src/builders/application/tests/behavior/top-level-await-error_spec.ts @@ -0,0 +1,67 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { buildApplication } from '../../index'; +import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setup'; + +describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { + describe('Behavior: "Top-level await error message"', () => { + it('should show a Zone.js-specific error when top-level await is used with Zone.js', async () => { + await harness.writeFile( + 'src/main.ts', + ` + // The export makes this file a module, which is required for top-level await. + export const value = await Promise.resolve('test'); + console.log(value); + `, + ); + + harness.useTarget('build', { + ...BASE_OPTIONS, + polyfills: ['zone.js'], + }); + + const { result, logs } = await harness.executeOnce({ outputLogsOnFailure: false }); + expect(result?.success).toBeFalse(); + expect(logs).toContain( + jasmine.objectContaining({ + message: jasmine.stringMatching( + 'Top-level await is not supported in applications that use Zone.js', + ), + }), + ); + }); + + it('should not show a Zone.js-specific error when top-level await is used without Zone.js', async () => { + await harness.writeFile( + 'src/main.ts', + ` + // The export makes this file a module, which is required for top-level await. + export const value = await Promise.resolve('test'); + console.log(value); + `, + ); + + harness.useTarget('build', { + ...BASE_OPTIONS, + polyfills: [], + }); + + const { result, logs } = await harness.executeOnce({ outputLogsOnFailure: false }); + expect(result?.success).toBeTrue(); + expect(logs).not.toContain( + jasmine.objectContaining({ + level: 'error', + message: jasmine.stringContaining( + 'Top-level await is not supported in applications that use Zone.js', + ), + }), + ); + }); + }); +}); diff --git a/packages/angular/build/src/builders/application/tests/options/stats-json_spec.ts b/packages/angular/build/src/builders/application/tests/options/stats-json_spec.ts new file mode 100644 index 000000000000..f38ae4996d16 --- /dev/null +++ b/packages/angular/build/src/builders/application/tests/options/stats-json_spec.ts @@ -0,0 +1,103 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { buildApplication } from '../../index'; +import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setup'; + +describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { + describe('Option: "statsJson"', () => { + it('generates only browser stats file containing valid metafile data when true', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + statsJson: true, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + harness.expectFile('dist/browser-stats.json').toExist(); + harness.expectFile('dist/server-stats.json').toNotExist(); + + const browserStats = JSON.parse(harness.readFile('dist/browser-stats.json')); + expect(browserStats.inputs).toBeDefined(); + expect(browserStats.outputs).toBeDefined(); + expect(Object.keys(browserStats.outputs).length).toBeGreaterThan(0); + }); + + it('does not generate stats files when false', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + statsJson: false, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + harness.expectFile('dist/browser-stats.json').toNotExist(); + harness.expectFile('dist/server-stats.json').toNotExist(); + }); + + it('does not generate stats files when not set', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + harness.expectFile('dist/browser-stats.json').toNotExist(); + harness.expectFile('dist/server-stats.json').toNotExist(); + }); + + describe('server build', () => { + beforeEach(async () => { + await harness.modifyFile('src/tsconfig.app.json', (content) => { + const tsConfig = JSON.parse(content); + tsConfig.files ??= []; + tsConfig.files.push('main.server.ts'); + + return JSON.stringify(tsConfig); + }); + }); + + it('generates separated browser and server stats files for an SSR build', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + server: 'src/main.server.ts', + ssr: true, + statsJson: true, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + harness.expectFile('dist/browser-stats.json').toExist(); + harness.expectFile('dist/server-stats.json').toExist(); + + const browserStats = JSON.parse(harness.readFile('dist/browser-stats.json')); + const serverStats = JSON.parse(harness.readFile('dist/server-stats.json')); + + const browserPaths = new Set(Object.keys(browserStats.outputs)); + const serverPaths = new Set(Object.keys(serverStats.outputs)); + + expect(serverPaths.size).toBeGreaterThan(0); + expect(browserPaths.size).toBeGreaterThan(0); + + for (const path of serverPaths) { + expect(browserPaths.has(path)) + .withContext(`Server output '${path}' should not appear in browser-stats.json`) + .toBeFalse(); + } + + for (const path of browserPaths) { + expect(serverPaths.has(path)) + .withContext(`Browser output '${path}' should not appear in server-stats.json`) + .toBeFalse(); + } + }); + }); + }); +}); diff --git a/packages/angular/build/src/builders/application/tests/options/subresource-integrity_spec.ts b/packages/angular/build/src/builders/application/tests/options/subresource-integrity_spec.ts index e846053e64c4..ca9212b0d3e3 100644 --- a/packages/angular/build/src/builders/application/tests/options/subresource-integrity_spec.ts +++ b/packages/angular/build/src/builders/application/tests/options/subresource-integrity_spec.ts @@ -238,5 +238,21 @@ describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { .withContext('importmap must precede the first module script tag') .toBeLessThan(moduleScriptIdx); }); + + it(`preserves integrity and crossorigin in autoCsp loader when autoCsp is also enabled`, async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + subresourceIntegrity: true, + security: { autoCsp: true }, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + const indexHtml = harness.readFile('dist/browser/index.html'); + expect(indexHtml).toMatch( + /const scripts = \[\[(?:'[^']+', 'module', false, false, "sha384-[^"]+", "anonymous"(?:, )?)+\]\];/, + ); + }); }); }); diff --git a/packages/angular/build/src/builders/dev-server/internal.ts b/packages/angular/build/src/builders/dev-server/internal.ts index 4e5e97e5cf14..96587acb5cff 100644 --- a/packages/angular/build/src/builders/dev-server/internal.ts +++ b/packages/angular/build/src/builders/dev-server/internal.ts @@ -13,7 +13,7 @@ export { getFeatureSupport, isZonelessApp } from '../../tools/esbuild/utils'; export { type IndexHtmlTransform } from '../../utils/index-file/index-html-generator'; export { purgeStaleBuildCache } from '../../utils/purge-cache'; export { getSupportedBrowsers } from '../../utils/supported-browsers'; -export { transformSupportedBrowsersToTargets } from '../../tools/esbuild/utils'; +export { transformSupportedBrowsersToTargets } from '../../tools/esbuild/target'; export { buildApplicationInternal } from '../../builders/application'; export type { ApplicationBuilderInternalOptions } from '../../builders/application/options'; export type { ExternalResultMetadata } from '../../tools/esbuild/bundler-execution-result'; diff --git a/packages/angular/build/src/builders/dev-server/vite/index.ts b/packages/angular/build/src/builders/dev-server/vite/index.ts index eeffb6531969..94192ec41b8c 100644 --- a/packages/angular/build/src/builders/dev-server/vite/index.ts +++ b/packages/angular/build/src/builders/dev-server/vite/index.ts @@ -389,15 +389,11 @@ export async function* serveWithVite( ? browserOptions.polyfills : [browserOptions.polyfills]; - // TODO(alanagius): This is a workaround for https://github.com/rolldown/rolldown/issues/10633 - const target = isZonelessApp(polyfills) ? ['es2022'] : ['es2016']; - - // Once the above issue is fixed, uncomment the below code. - // const target = transformSupportedBrowsersToTargets(browsers); - // if (!isZonelessApp(polyfills)) { - // // Rolldown doesn't have an option to support Zone.js/async-await, so we need to support es2016. - // target.push('es2016'); - // } + const target = transformSupportedBrowsersToTargets(browsers); + if (!isZonelessApp(polyfills)) { + // Rolldown doesn't have an option to support Zone.js/async-await, so we need to support es2016. + target.push('es2016'); + } let ssrMode: ServerSsrMode = ServerSsrMode.NoSsr; if ( diff --git a/packages/angular/build/src/builders/dev-server/vite/server.ts b/packages/angular/build/src/builders/dev-server/vite/server.ts index 868590edab95..b1826383fccb 100644 --- a/packages/angular/build/src/builders/dev-server/vite/server.ts +++ b/packages/angular/build/src/builders/dev-server/vite/server.ts @@ -22,6 +22,7 @@ import { } from '../../../tools/vite/plugins'; import { RolldownLoaderOption, getDepOptimizationConfig } from '../../../tools/vite/utils'; import { loadProxyConfiguration } from '../../../utils'; +import { initializeHash } from '../../../utils/hash'; import { type ApplicationBuilderInternalOptions, JavaScriptTransformer } from '../internal'; import type { NormalizedDevServerOptions } from '../options'; import { DevServerExternalResultMetadata, OutputAssetRecord, OutputFileRecord } from './utils'; @@ -147,6 +148,7 @@ export async function setupServer( indexHtmlTransformer?: (content: string) => Promise, thirdPartySourcemaps = false, ): Promise { + await initializeHash(); const { normalizePath } = (await import('vite' as string)) as typeof Vite; // Path will not exist on disk and only used to provide separate path for Vite requests diff --git a/packages/angular/build/src/builders/unit-test/options.ts b/packages/angular/build/src/builders/unit-test/options.ts index 1206bb2c1f21..7d57716214b1 100644 --- a/packages/angular/build/src/builders/unit-test/options.ts +++ b/packages/angular/build/src/builders/unit-test/options.ts @@ -10,7 +10,7 @@ import { type BuilderContext, targetFromTargetString } from '@angular-devkit/arc import { constants, promises as fs } from 'node:fs'; import path from 'node:path'; import { normalizeCacheOptions } from '../../utils/normalize-cache'; -import { canonicalizePath } from '../../utils/path'; +import { canonicalizePath, toPosixPath } from '../../utils/path'; import { getProjectRootPaths } from '../../utils/project-metadata'; import { isTTY } from '../../utils/tty'; import { Runner, type Schema as UnitTestBuilderOptions } from './schema'; @@ -143,7 +143,7 @@ export async function normalizeOptions( quiet: options.quiet ?? (process.env['CI'] ? false : true), providersFile: options.providersFile && path.join(workspaceRoot, options.providersFile), setupFiles: options.setupFiles - ? options.setupFiles.map((setupFile) => path.join(workspaceRoot, setupFile)) + ? options.setupFiles.map((setupFile) => toPosixPath(path.join(workspaceRoot, setupFile))) : [], dumpVirtualFiles: options.dumpVirtualFiles, listTests: options.listTests, diff --git a/packages/angular/build/src/builders/unit-test/runners/vitest/build-options.ts b/packages/angular/build/src/builders/unit-test/runners/vitest/build-options.ts index 7f0f67fc0c2e..3936f44b09fd 100644 --- a/packages/angular/build/src/builders/unit-test/runners/vitest/build-options.ts +++ b/packages/angular/build/src/builders/unit-test/runners/vitest/build-options.ts @@ -257,6 +257,12 @@ export async function getVitestBuildOptions( outputHashing: adjustOutputHashing(baseBuildOptions.outputHashing), optimization: false, entryPoints, + // Every spec file is its own entry point, so splitting hoists any module shared between two + // specs into a chunk whose exports are then read across a chunk boundary. Those reads rely on + // live ESM bindings, and a module placed in a shared chunk is only assigned its exported value + // when that chunk's lazy initializer runs, so an importing chunk can read `undefined`. Nothing + // downloads these bundles, so there is no benefit to weigh against that. + disableCodeSplitting: true, // Enable support for vitest browser prebundling. Excludes can be controlled with a runnerConfig // and the `optimizeDeps.exclude` option. externalPackages: true, diff --git a/packages/angular/build/src/builders/unit-test/test-discovery.ts b/packages/angular/build/src/builders/unit-test/test-discovery.ts index 7bad7079dc90..f2fc8c221646 100644 --- a/packages/angular/build/src/builders/unit-test/test-discovery.ts +++ b/packages/angular/build/src/builders/unit-test/test-discovery.ts @@ -6,11 +6,11 @@ * found in the LICENSE file at https://angular.dev/license */ -import { createHash } from 'node:crypto'; import { type PathLike, constants, promises as fs } from 'node:fs'; import os from 'node:os'; import { basename, dirname, extname, isAbsolute, join, relative } from 'node:path'; import { glob, isDynamicPattern } from 'tinyglobby'; +import { calculateHash, initializeHash } from '../../utils/hash'; import { toPosixPath } from '../../utils/path'; /** @@ -41,6 +41,7 @@ export async function findTests( workspaceRoot: string, projectSourceRoot: string, ): Promise { + await initializeHash(); const resolvedTestFiles = new Set(); const dynamicPatterns: string[] = []; @@ -194,7 +195,7 @@ function truncateName(name: string, originalPath: string): string { return name; } - const hash = createHash('sha256').update(originalPath).digest('hex').substring(0, 8); + const hash = calculateHash(originalPath).substring(0, 8); const availableLength = MAX_FILENAME_LENGTH - hash.length - 2; // 2 for '-' separators const prefixLength = Math.floor(availableLength / 2); const suffixLength = availableLength - prefixLength; diff --git a/packages/angular/build/src/builders/unit-test/test-discovery_spec.ts b/packages/angular/build/src/builders/unit-test/test-discovery_spec.ts index dcee1718a976..764924d9552b 100644 --- a/packages/angular/build/src/builders/unit-test/test-discovery_spec.ts +++ b/packages/angular/build/src/builders/unit-test/test-discovery_spec.ts @@ -6,9 +6,14 @@ * found in the LICENSE file at https://angular.dev/license */ +import { initializeHash } from '../../utils/hash'; import { generateNameFromPath, getTestEntrypoints } from './test-discovery'; describe('getTestEntrypoints', () => { + beforeAll(async () => { + await initializeHash(); + }); + const workspaceRoot = '/project'; const projectSourceRoot = '/project/src'; const options = { workspaceRoot, projectSourceRoot }; @@ -81,6 +86,10 @@ describe('getTestEntrypoints', () => { }); describe('generateNameFromPath', () => { + beforeAll(async () => { + await initializeHash(); + }); + const roots = ['/project/src/', '/project/']; it('should generate a dash-cased name from a simple path', () => { @@ -127,7 +136,7 @@ describe('generateNameFromPath', () => { expect(result.length).toBeLessThanOrEqual(128); expect(result).toBe( - 'a-very-long-path-that-definitely-exceeds-the-maximum-allowe-9cf40291-me-in-order-to-trigger-the-truncation-logic-in-the-function', + 'a-very-long-path-that-definitely-exceeds-the-maximum-allowe-4af8113d-me-in-order-to-trigger-the-truncation-logic-in-the-function', ); // eslint-disable-line max-len }); diff --git a/packages/angular/build/src/builders/unit-test/tests/behavior/vitest-barrel-side-effects_spec.ts b/packages/angular/build/src/builders/unit-test/tests/behavior/vitest-barrel-side-effects_spec.ts new file mode 100644 index 000000000000..7a59f09eeaa0 --- /dev/null +++ b/packages/angular/build/src/builders/unit-test/tests/behavior/vitest-barrel-side-effects_spec.ts @@ -0,0 +1,134 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { execute } from '../../index'; +import { BASE_OPTIONS, describeBuilder, UNIT_TEST_BUILDER_INFO } from '../setup'; + +describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => { + describe('Behavior: "Vitest barrel sideEffects"', () => { + // Regression test for https://github.com/angular/angular-cli/issues/33910. + it('should retain barrel-exported components when package.json has sideEffects: false', async () => { + harness.withBuilderTarget( + 'build', + async () => ({ success: true }), + { + project: 'src/lib/ng-package.json', + }, + { + builderName: '@angular/build:ng-packagr', + }, + ); + + await harness.modifyFile('tsconfig.json', (content) => { + const tsconfig = JSON.parse(content); + tsconfig.compilerOptions ??= {}; + tsconfig.compilerOptions.paths = { + '@lib': ['./src/lib/public-api.ts'], + '@lib/sub': ['./src/lib/sub/public-api.ts'], + }; + + return JSON.stringify(tsconfig); + }); + + await harness.writeFiles({ + 'src/lib/package.json': JSON.stringify({ + name: '@lib', + version: '0.0.1', + sideEffects: false, + }), + 'src/lib/ng-package.json': JSON.stringify({ + lib: { + entryFile: 'public-api.ts', + }, + }), + 'src/lib/public-api.ts': ` + export * from './components/public-api-components'; + export * from './other/other.component'; + `, + 'src/lib/components/public-api-components.ts': `export * from './foo/foo.component';`, + 'src/lib/components/foo/foo.component.ts': ` + import { Component } from '@angular/core'; + + @Component({ + selector: 'lib-foo', + standalone: true, + template: '

foo

', + }) + export class FooComponent {} + `, + 'src/lib/other/other.component.ts': ` + import { Component } from '@angular/core'; + + @Component({ + selector: 'lib-other', + standalone: true, + template: '

other

', + }) + export class OtherComponent {} + `, + 'src/lib/other/other.component.spec.ts': ` + import { TestBed } from '@angular/core/testing'; + import { describe, it, expect } from 'vitest'; + import { OtherComponent } from '@lib'; + + describe('OtherComponent', () => { + it('creates', () => { + TestBed.configureTestingModule({ + imports: [OtherComponent], + }); + const fixture = TestBed.createComponent(OtherComponent); + expect(fixture).toBeTruthy(); + }); + }); + `, + 'src/lib/sub/ng-package.json': JSON.stringify({ + lib: { + entryFile: 'public-api.ts', + }, + }), + 'src/lib/sub/public-api.ts': `export * from './bar/bar.component';`, + 'src/lib/sub/bar/bar.component.ts': ` + import { Component } from '@angular/core'; + import { FooComponent } from '@lib'; + + @Component({ + selector: 'lib-bar', + standalone: true, + imports: [FooComponent], + template: '', + }) + export class BarComponent {} + `, + 'src/lib/sub/bar/bar.component.spec.ts': ` + import { TestBed } from '@angular/core/testing'; + import { describe, it, expect } from 'vitest'; + import { BarComponent } from './bar.component'; + + describe('BarComponent', () => { + it('creates', () => { + TestBed.configureTestingModule({ + imports: [BarComponent], + }); + const fixture = TestBed.createComponent(BarComponent); + expect(fixture).toBeTruthy(); + }); + }); + `, + }); + + harness.useTarget('test', { + ...BASE_OPTIONS, + include: ['lib/**/*.spec.ts'], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBeTrue(); + }); + }); +}); diff --git a/packages/angular/build/src/builders/unit-test/tests/behavior/vitest-shared-chunk-init_spec.ts b/packages/angular/build/src/builders/unit-test/tests/behavior/vitest-shared-chunk-init_spec.ts new file mode 100644 index 000000000000..13870ba36eba --- /dev/null +++ b/packages/angular/build/src/builders/unit-test/tests/behavior/vitest-shared-chunk-init_spec.ts @@ -0,0 +1,172 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { execute } from '../../index'; +import { + BASE_OPTIONS, + describeBuilder, + UNIT_TEST_BUILDER_INFO, + setupApplicationTarget, +} from '../setup'; + +describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => { + describe('Behavior: "Vitest shared chunk initialization"', () => { + // Regression test for https://github.com/angular/angular-cli/issues/33728. + // + // Without `disableCodeSplitting`, esbuild hoists a module imported by more than one spec + // entry point into a shared chunk behind a lazy `__esm` initializer, and a class-field + // initializer in another chunk reads the exported value as `undefined` under the jsdom + // runner. All four trigger conditions are required and encoded below: + // 1. two spec entry points import the shared module (so it lands in a shared chunk); + // 2. a component in one entry reads the export during class-field initialization; + // 3. that component's spec file contains an `async` test callback (no `await` needed); + // 4. zone.js is in the polyfills (the `setupApplicationTarget` default), which downlevels + // async and makes esbuild emit the spec entry CommonJS-wrapped. + // + // NOTE: the failure this guards against is sensitive to inert content — adding a top-level + // side effect (even a `console.log`) to the shared or importing module below defused it + // during reduction. Mirror https://github.com/jonmarozick/ng-shared-chunk-repro when + // modifying these fixtures. + it('should provide shared-module exports to class-field initializers in async specs', async () => { + setupApplicationTarget(harness); + + harness.useTarget('test', { + ...BASE_OPTIONS, + }); + + // Keep the default project's spec deterministic; a third spec entry that does not touch + // the shared module does not affect the reproduction (verified in a fresh workspace). + await harness.writeFile( + 'src/app/app.component.spec.ts', + ` + import { describe, it, expect } from 'vitest'; + + describe('AppComponent placeholder', () => { + it('runs', () => { + expect(1 + 1).toBe(2); + }); + }); + `, + ); + + // The shared `const`. Reached from both spec entry points, so it is hoisted into a chunk + // shared between them. + await harness.writeFile( + 'src/environments/env-config.ts', + ` + export interface DealerConfig { + dealerId: string; + clientKey: string; + } + + export const DEALERS: DealerConfig[] = [ + { dealerId: 'dealer-one', clientKey: 'KEY-ONE' }, + { dealerId: 'dealer-two', clientKey: 'KEY-TWO' }, + { dealerId: 'dealer-three', clientKey: 'KEY-THREE' }, + { dealerId: 'dealer-four', clientKey: 'KEY-FOUR' }, + ]; + `, + ); + + // Reached by both spec entries, so it and env-config.ts land in the shared chunk. Reads the + // const inside a method — after module initialization — and is the passing control. + await harness.writeFile( + 'src/app/features/lead-generator/services/lead.service.ts', + ` + import { Injectable } from '@angular/core'; + + import { DEALERS } from '../../../../environments/env-config'; + + @Injectable({ providedIn: 'root' }) + export class LeadService { + resolveClientKey(dealerId: string, clientKey?: string): string { + return clientKey ?? DEALERS.find((d) => d.dealerId === dealerId)?.clientKey ?? ''; + } + } + `, + ); + + await harness.writeFile( + 'src/app/features/lead-generator/services/index.ts', + `export * from './lead.service';\n`, + ); + + // Spec entry point 1 — the second importer that causes the chunk to be shared at all. + await harness.writeFile( + 'src/app/features/lead-generator/services/lead.service.spec.ts', + ` + import { describe, it, expect } from 'vitest'; + import { TestBed } from '@angular/core/testing'; + + import { LeadService } from './lead.service'; + + describe('LeadService', () => { + it('reads DEALERS inside a method', () => { + TestBed.configureTestingModule({ providers: [LeadService] }); + + expect(TestBed.inject(LeadService).resolveClientKey('dealer-one')).toBe('KEY-ONE'); + }); + }); + `, + ); + + // In the other chunk; reads the shared export eagerly during class-field initialization. + await harness.writeFile( + 'src/app/features/lead-generator/lead-generator.container.ts', + ` + import { Component, inject } from '@angular/core'; + + import { LeadService } from './services'; + import { DEALERS, DealerConfig } from '../../../environments/env-config'; + + @Component({ + selector: 'app-lead-generator', + standalone: true, + template: '', + }) + export class LeadGeneratorContainer { + private readonly leadService = inject(LeadService); + + readonly dealers: DealerConfig[] = DEALERS; + readonly dealerOptions = this.dealers.map((d) => d.dealerId); + + hasService(): boolean { + return this.leadService != null; + } + } + `, + ); + + // Spec entry point 2 — the failing case without the fix. The `async` is load-bearing: + // zone.js makes the builder downlevel it, the spec then imports the `__async` helper, and + // esbuild emits this entry CommonJS-wrapped with the component module behind a lazy + // `__esm` initializer. No `await` is needed; a synchronous callback hides the defect. + await harness.writeFile( + 'src/app/features/lead-generator/lead-generator.container.spec.ts', + ` + import { describe, it, expect } from 'vitest'; + import { TestBed } from '@angular/core/testing'; + + import { LeadGeneratorContainer } from './lead-generator.container'; + + describe('LeadGeneratorContainer', () => { + it('reads DEALERS in a class-field initialiser', async () => { + const fixture = TestBed.createComponent(LeadGeneratorContainer); + + expect(fixture.componentInstance.dealers.length).toBe(4); + }); + }); + `, + ); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBeTrue(); + }); + }); +}); diff --git a/packages/angular/build/src/private.ts b/packages/angular/build/src/private.ts index f1eb9dccb459..315636decae2 100644 --- a/packages/angular/build/src/private.ts +++ b/packages/angular/build/src/private.ts @@ -34,8 +34,8 @@ export { // Tools export type { ExternalResultMetadata } from './tools/esbuild/bundler-execution-result'; export { emitFilesToDisk } from './tools/esbuild/utils'; -export { transformSupportedBrowsersToTargets } from './tools/esbuild/utils'; -export { SassWorkerImplementation } from './tools/sass/sass-service'; +export { transformSupportedBrowsersToTargets } from './tools/esbuild/target'; +export { SassWorkerImplementation } from './tools/sass/sass-worker-implementation'; export { SourceFileCache } from './tools/esbuild/angular/source-file-cache'; export { Cache } from './tools/esbuild/cache'; diff --git a/packages/angular/build/src/tools/angular/angular-host.ts b/packages/angular/build/src/tools/angular/angular-host.ts index 874d66fe2b41..9322b5683dc5 100644 --- a/packages/angular/build/src/tools/angular/angular-host.ts +++ b/packages/angular/build/src/tools/angular/angular-host.ts @@ -8,16 +8,15 @@ import type * as ng from '@angular/compiler-cli'; import assert from 'node:assert'; -import { createHash } from 'node:crypto'; import nodePath from 'node:path'; import type ts from 'typescript'; +import { calculateHash } from '../../utils/hash'; export type AngularCompilerOptions = ng.CompilerOptions; export type AngularCompilerHost = ng.CompilerHost; export interface AngularHostOptions { fileReplacements?: Record; - sourceFileCache?: Map; modifiedFiles?: Set; externalStylesheets?: Map; transformStylesheet( @@ -46,7 +45,7 @@ export function ensureSourceFileVersions(program: ts.Program): void { for (const file of files) { if (file.version === undefined) { - file.version = createHash('sha256').update(file.text).digest('hex'); + file.version = calculateHash(file.text); } } @@ -165,6 +164,7 @@ export function createAngularCompilerHost( compilerOptions: AngularCompilerOptions, hostOptions: AngularHostOptions, packageJsonCache: ts.PackageJsonInfoCache | undefined, + sourceFileCache?: Map, ): AngularCompilerHost { // Create TypeScript compiler host const host: AngularCompilerHost = typescript.createIncrementalCompilerHost(compilerOptions); @@ -227,7 +227,7 @@ export function createAngularCompilerHost( // For external stylesheets, create a unique identifier and store the mapping let externalId = hostOptions.externalStylesheets.get(resolvedPath); if (externalId === undefined) { - externalId = createHash('sha256').update(resolvedPath).digest('hex'); + externalId = calculateHash(resolvedPath); hostOptions.externalStylesheets.set(resolvedPath, externalId); } @@ -254,8 +254,8 @@ export function createAngularCompilerHost( } // Augment TypeScript Host with source file caching if provided - if (hostOptions.sourceFileCache) { - augmentHostWithCaching(host, hostOptions.sourceFileCache); + if (sourceFileCache) { + augmentHostWithCaching(host, sourceFileCache); } return host; diff --git a/packages/angular/build/src/tools/angular/compilation/angular-compilation.ts b/packages/angular/build/src/tools/angular/compilation/angular-compilation.ts index 00a17ccc453e..4c17bb6e60de 100644 --- a/packages/angular/build/src/tools/angular/compilation/angular-compilation.ts +++ b/packages/angular/build/src/tools/angular/compilation/angular-compilation.ts @@ -19,6 +19,28 @@ export interface EmitFileResult { dependencies?: readonly string[]; } +export interface FileTransformResult { + contents: string; + watchFiles?: readonly string[]; +} + +export interface AngularCompilationOptions { + allowJs?: boolean; + isolatedModules?: boolean; + sourceMap?: boolean; + inlineSourceMap?: boolean; + _useTypeScriptTranspilation?: boolean; + [key: string]: unknown; +} + +export interface AngularCompilationResult { + compilerOptions: AngularCompilationOptions; + referencedFiles: readonly string[]; + externalStylesheets?: ReadonlyMap; + templateUpdates?: ReadonlyMap; + componentResourcesDependencies?: ReadonlyMap; +} + export enum DiagnosticModes { None = 0, Option = 1 << 0, @@ -70,24 +92,25 @@ export abstract class AngularCompilation { tsconfig: string, hostOptions: AngularHostOptions, compilerOptionsTransformer?: (compilerOptions: ng.CompilerOptions) => ng.CompilerOptions, - ): Promise<{ - affectedFiles: ReadonlySet; - compilerOptions: ng.CompilerOptions; - referencedFiles: readonly string[]; - externalStylesheets?: ReadonlyMap; - templateUpdates?: ReadonlyMap; - componentResourcesDependencies?: ReadonlyMap; - }>; - - abstract emitAffectedFiles(): Iterable | Promise>; - - protected abstract collectDiagnostics( + ): Promise; + + emitAffectedFiles(): Iterable | Promise> { + return []; + } + + transformFile?(filename: string, content: string): Promise; + + protected collectDiagnostics?( modes: DiagnosticModes, ): Iterable | Promise>; async diagnoseFiles( modes = DiagnosticModes.All, ): Promise<{ errors?: PartialMessage[]; warnings?: PartialMessage[] }> { + if (!this.collectDiagnostics) { + return {}; + } + const result: { errors?: PartialMessage[]; warnings?: PartialMessage[] } = {}; // Avoid loading typescript until actually needed. @@ -95,7 +118,12 @@ export abstract class AngularCompilation { const typescript = await AngularCompilation.loadTypescript(); await profileAsync('NG_DIAGNOSTICS_TOTAL', async () => { - for (const diagnostic of await this.collectDiagnostics(modes)) { + const diagnostics = await this.collectDiagnostics?.(modes); + if (!diagnostics) { + return; + } + + for (const diagnostic of diagnostics) { const message = convertTypeScriptDiagnostic(typescript, diagnostic); if (diagnostic.category === typescript.DiagnosticCategory.Error) { (result.errors ??= []).push(message); diff --git a/packages/angular/build/src/tools/angular/compilation/angular-compilation_spec.ts b/packages/angular/build/src/tools/angular/compilation/angular-compilation_spec.ts new file mode 100644 index 000000000000..a9e93b701e5f --- /dev/null +++ b/packages/angular/build/src/tools/angular/compilation/angular-compilation_spec.ts @@ -0,0 +1,109 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import type { AngularHostOptions } from '../angular-host'; +import { + AngularCompilation, + AngularCompilationResult, + NoopCompilation, + createAngularCompilation, +} from './index'; + +describe('AngularCompilation', () => { + class CustomTransformCompilation extends AngularCompilation { + override async initialize(): Promise { + return { + compilerOptions: { allowJs: false }, + referencedFiles: ['/src/main.ts'], + }; + } + + override async transformFile(filename: string, content: string) { + if (filename.endsWith('.ts')) { + return { + contents: content + '\n// transformed', + watchFiles: ['/src/dep.ts'], + }; + } + + return null; + } + } + + it('allows implementing on-demand transformFile without collectDiagnostics', async () => { + const compilation = new CustomTransformCompilation(); + const initResult = await compilation.initialize(); + + expect(initResult.referencedFiles).toEqual(['/src/main.ts']); + expect(initResult.compilerOptions.allowJs).toBe(false); + + const transformResult = await compilation.transformFile?.( + '/src/main.ts', + 'console.log("hello");', + ); + expect(transformResult).toEqual({ + contents: 'console.log("hello");\n// transformed', + watchFiles: ['/src/dep.ts'], + }); + + const diagnostics = await compilation.diagnoseFiles(); + expect(diagnostics).toEqual({}); + }); + + describe('NoopCompilation', () => { + it('initializes with empty referencedFiles and compiler options', async () => { + const compilation = new NoopCompilation(); + const mockHostOptions = {} as AngularHostOptions; + const result = await compilation.initialize('tsconfig.json', mockHostOptions, (opts) => ({ + ...opts, + customOption: true, + })); + + expect(result.referencedFiles).toEqual([]); + expect(result.compilerOptions['customOption']).toBe(true); + }); + + it('throws when calling collectDiagnostics or emitAffectedFiles', () => { + const compilation = new NoopCompilation(); + expect(() => + (compilation as unknown as { collectDiagnostics(): unknown }).collectDiagnostics(), + ).toThrowError('Not available when using noop compilation.'); + expect(() => compilation.emitAffectedFiles()).toThrowError( + 'Not available when using noop compilation.', + ); + }); + }); + + describe('createAngularCompilation', () => { + it('creates JitCompilation when mode is "jit"', async () => { + const compilation = await createAngularCompilation('jit', true, false); + expect(compilation.constructor.name).toBe('JitCompilation'); + }); + + it('creates AotCompilation when mode is "aot"', async () => { + const compilation = await createAngularCompilation('aot', true, false); + expect(compilation.constructor.name).toBe('AotCompilation'); + }); + + it('creates JitCompilation when mode is boolean true (legacy)', async () => { + const compilation = await createAngularCompilation(true, true, false); + expect(compilation.constructor.name).toBe('JitCompilation'); + }); + + it('creates AotCompilation when mode is boolean false (legacy)', async () => { + const compilation = await createAngularCompilation(false, true, false); + expect(compilation.constructor.name).toBe('AotCompilation'); + }); + + it('throws when mode is "transform"', async () => { + await expectAsync(createAngularCompilation('transform', true, false)).toBeRejectedWithError( + 'Transform compilation mode is not supported.', + ); + }); + }); +}); diff --git a/packages/angular/build/src/tools/angular/compilation/aot-compilation.ts b/packages/angular/build/src/tools/angular/compilation/aot-compilation.ts index cd4e1cdae7dd..42df6a40e778 100644 --- a/packages/angular/build/src/tools/angular/compilation/aot-compilation.ts +++ b/packages/angular/build/src/tools/angular/compilation/aot-compilation.ts @@ -11,6 +11,7 @@ import assert from 'node:assert'; import { relative } from 'node:path'; import ts from 'typescript'; import { useTypeChecking } from '../../../utils/environment-options'; +import { toPosixPath } from '../../../utils/path'; import { profileAsync, profileSync } from '../../esbuild/profiling'; import { AngularHostOptions, @@ -20,7 +21,12 @@ import { import { replaceBootstrap } from '../transformers/jit-bootstrap-transformer'; import { lazyRoutesTransformer } from '../transformers/lazy-routes-transformer'; import { createWorkerTransformer } from '../transformers/web-worker-transformer'; -import { AngularCompilation, DiagnosticModes, EmitFileResult } from './angular-compilation'; +import { + AngularCompilation, + AngularCompilationResult, + DiagnosticModes, + EmitFileResult, +} from './angular-compilation'; import { collectHmrCandidates } from './hmr-candidates'; import { printSourceFileWithMap } from './typescript-printer'; @@ -50,6 +56,7 @@ class AngularCompilationState { export class AotCompilation extends AngularCompilation { #state?: AngularCompilationState; + readonly #sourceFiles = new Map(); constructor(private readonly browserOnlyBuild: boolean) { super(); @@ -59,14 +66,7 @@ export class AotCompilation extends AngularCompilation { tsconfig: string, hostOptions: AngularHostOptions, compilerOptionsTransformer?: (compilerOptions: ng.CompilerOptions) => ng.CompilerOptions, - ): Promise<{ - affectedFiles: ReadonlySet; - compilerOptions: ng.CompilerOptions; - referencedFiles: readonly string[]; - externalStylesheets?: ReadonlyMap; - templateUpdates?: ReadonlyMap; - componentResourcesDependencies?: ReadonlyMap; - }> { + ): Promise { // Dynamically load the Angular compiler CLI package const { NgtscProgram, OptimizeFor } = await AngularCompilation.loadCompilerCli(); @@ -99,27 +99,37 @@ export class AotCompilation extends AngularCompilation { let staleSourceFiles; let clearPackageJsonCache = false; - if (hostOptions.modifiedFiles && this.#state) { + if (hostOptions.modifiedFiles) { for (const modifiedFile of hostOptions.modifiedFiles) { - // Clear package.json cache if a node modules file was modified - if (!clearPackageJsonCache && modifiedFile.includes('node_modules')) { - clearPackageJsonCache = true; - packageJsonCache?.clear(); - } + this.#sourceFiles.delete(toPosixPath(modifiedFile)); + + if (this.#state) { + // Clear package.json cache if a node modules file was modified + if (!clearPackageJsonCache && modifiedFile.includes('node_modules')) { + clearPackageJsonCache = true; + packageJsonCache?.clear(); + } - // Collect stale source files for HMR analysis of inline component resources - if (useHmr) { - const sourceFile = this.#state.typeScriptProgram.getSourceFile(modifiedFile); - if (sourceFile) { - staleSourceFiles ??= new Map(); - staleSourceFiles.set(modifiedFile, sourceFile); + // Collect stale source files for HMR analysis of inline component resources + if (useHmr) { + const sourceFile = this.#state.typeScriptProgram.getSourceFile(modifiedFile); + if (sourceFile) { + staleSourceFiles ??= new Map(); + staleSourceFiles.set(modifiedFile, sourceFile); + } } } } } // Create Angular compiler host - const host = createAngularCompilerHost(ts, compilerOptions, hostOptions, packageJsonCache); + const host = createAngularCompilerHost( + ts, + compilerOptions, + hostOptions, + packageJsonCache, + this.#sourceFiles, + ); // Create the Angular specific program that contains the Angular compiler const angularProgram = profileSync( @@ -231,7 +241,6 @@ export class AotCompilation extends AngularCompilation { ); return { - affectedFiles, compilerOptions, referencedFiles, externalStylesheets: hostOptions.externalStylesheets, @@ -240,7 +249,7 @@ export class AotCompilation extends AngularCompilation { }; } - *collectDiagnostics(modes: DiagnosticModes): Iterable { + protected override *collectDiagnostics(modes: DiagnosticModes): Iterable { assert(this.#state, 'Angular compilation must be initialized prior to collecting diagnostics.'); const { affectedFiles, @@ -313,7 +322,7 @@ export class AotCompilation extends AngularCompilation { } } - emitAffectedFiles(): Iterable { + override emitAffectedFiles(): Iterable { assert(this.#state, 'Angular compilation must be initialized prior to emitting files.'); const { affectedFiles, @@ -454,6 +463,12 @@ export class AotCompilation extends AngularCompilation { return emittedFiles.values(); } + + override async update(files: Set): Promise { + for (const file of files) { + this.#sourceFiles.delete(toPosixPath(file)); + } + } } function findAffectedFiles( diff --git a/packages/angular/build/src/tools/angular/compilation/factory.ts b/packages/angular/build/src/tools/angular/compilation/factory.ts index ebfa7aa7edc4..3944979f1ee5 100644 --- a/packages/angular/build/src/tools/angular/compilation/factory.ts +++ b/packages/angular/build/src/tools/angular/compilation/factory.ts @@ -9,26 +9,35 @@ import { useParallelTs } from '../../../utils/environment-options'; import type { AngularCompilation } from './angular-compilation'; +export type AngularCompilationMode = 'aot' | 'jit' | 'transform'; + /** * Creates an Angular compilation object that can be used to perform Angular application - * compilation either for AOT or JIT mode. By default a parallel compilation is created + * compilation either for AOT, JIT, or on-demand transform mode. By default a parallel compilation is created * that uses a Node.js worker thread. - * @param jit True, for Angular JIT compilation; False, for Angular AOT compilation. + * @param mode True or 'jit' for JIT mode; False or 'aot' for AOT compilation; 'transform' for on-demand transformation. * @param browserOnlyBuild True, for browser only builds; False, for browser and server builds. + * @param parallel True to execute compilation in a worker thread. * @returns An instance of an Angular compilation object. */ export async function createAngularCompilation( - jit: boolean, + mode: boolean | AngularCompilationMode, browserOnlyBuild: boolean, parallel: boolean = useParallelTs, ): Promise { + if (mode === 'transform') { + throw new Error('Transform compilation mode is not supported.'); + } + + const isJit = mode === true || mode === 'jit'; + if (parallel) { const { ParallelCompilation } = await import('./parallel-compilation'); - return new ParallelCompilation(jit, browserOnlyBuild); + return new ParallelCompilation(isJit, browserOnlyBuild); } - if (jit) { + if (isJit) { const { JitCompilation } = await import('./jit-compilation'); return new JitCompilation(browserOnlyBuild); diff --git a/packages/angular/build/src/tools/angular/compilation/index.ts b/packages/angular/build/src/tools/angular/compilation/index.ts index d2611e0d156d..213f55cac326 100644 --- a/packages/angular/build/src/tools/angular/compilation/index.ts +++ b/packages/angular/build/src/tools/angular/compilation/index.ts @@ -6,6 +6,13 @@ * found in the LICENSE file at https://angular.dev/license */ -export { AngularCompilation, DiagnosticModes } from './angular-compilation'; -export { createAngularCompilation } from './factory'; +export { + AngularCompilation, + type AngularCompilationOptions, + type AngularCompilationResult, + DiagnosticModes, + type EmitFileResult, + type FileTransformResult, +} from './angular-compilation'; +export { createAngularCompilation, type AngularCompilationMode } from './factory'; export { NoopCompilation } from './noop-compilation'; diff --git a/packages/angular/build/src/tools/angular/compilation/jit-compilation.ts b/packages/angular/build/src/tools/angular/compilation/jit-compilation.ts index 5d920d961ba1..955c90502cb0 100644 --- a/packages/angular/build/src/tools/angular/compilation/jit-compilation.ts +++ b/packages/angular/build/src/tools/angular/compilation/jit-compilation.ts @@ -9,12 +9,18 @@ import type * as ng from '@angular/compiler-cli'; import assert from 'node:assert'; import ts from 'typescript'; +import { toPosixPath } from '../../../utils/path'; import { profileSync } from '../../esbuild/profiling'; import { AngularHostOptions, createAngularCompilerHost } from '../angular-host'; import { createJitResourceTransformer } from '../transformers/jit-resource-transformer'; import { lazyRoutesTransformer } from '../transformers/lazy-routes-transformer'; import { createWorkerTransformer } from '../transformers/web-worker-transformer'; -import { AngularCompilation, DiagnosticModes, EmitFileResult } from './angular-compilation'; +import { + AngularCompilation, + AngularCompilationResult, + DiagnosticModes, + EmitFileResult, +} from './angular-compilation'; class JitCompilationState { constructor( @@ -28,6 +34,7 @@ class JitCompilationState { export class JitCompilation extends AngularCompilation { #state?: JitCompilationState; + readonly #sourceFiles = new Map(); constructor(private readonly browserOnlyBuild: boolean) { super(); @@ -37,11 +44,7 @@ export class JitCompilation extends AngularCompilation { tsconfig: string, hostOptions: AngularHostOptions, compilerOptionsTransformer?: (compilerOptions: ng.CompilerOptions) => ng.CompilerOptions, - ): Promise<{ - affectedFiles: ReadonlySet; - compilerOptions: ng.CompilerOptions; - referencedFiles: readonly string[]; - }> { + ): Promise { // Dynamically load the Angular compiler CLI package const { constructorParametersDownlevelTransform } = await import('@angular/compiler-cli/private/tooling'); @@ -55,8 +58,20 @@ export class JitCompilation extends AngularCompilation { const compilerOptions = compilerOptionsTransformer?.(originalCompilerOptions) ?? originalCompilerOptions; + if (hostOptions.modifiedFiles) { + for (const modifiedFile of hostOptions.modifiedFiles) { + this.#sourceFiles.delete(toPosixPath(modifiedFile)); + } + } + // Create Angular compiler host - const host = createAngularCompilerHost(ts, compilerOptions, hostOptions, undefined); + const host = createAngularCompilerHost( + ts, + compilerOptions, + hostOptions, + undefined, + this.#sourceFiles, + ); // Create the TypeScript Program const typeScriptProgram = profileSync('TS_CREATE_PROGRAM', () => @@ -69,10 +84,6 @@ export class JitCompilation extends AngularCompilation { ), ); - const affectedFiles = profileSync('TS_FIND_AFFECTED', () => - findAffectedFiles(typeScriptProgram), - ); - this.#state = new JitCompilationState( host, typeScriptProgram, @@ -85,10 +96,10 @@ export class JitCompilation extends AngularCompilation { .getSourceFiles() .map((sourceFile) => sourceFile.fileName); - return { affectedFiles, compilerOptions, referencedFiles }; + return { compilerOptions, referencedFiles }; } - *collectDiagnostics(modes: DiagnosticModes): Iterable { + protected override *collectDiagnostics(modes: DiagnosticModes): Iterable { assert(this.#state, 'Compilation must be initialized prior to collecting diagnostics.'); const { typeScriptProgram } = this.#state; @@ -110,7 +121,7 @@ export class JitCompilation extends AngularCompilation { } } - emitAffectedFiles(): Iterable { + override emitAffectedFiles(): Iterable { assert(this.#state, 'Compilation must be initialized prior to emitting files.'); const { compilerHost, @@ -156,17 +167,10 @@ export class JitCompilation extends AngularCompilation { return emittedFiles; } -} - -function findAffectedFiles( - builder: ts.EmitAndSemanticDiagnosticsBuilderProgram, -): Set { - const affectedFiles = new Set(); - let result; - while ((result = builder.getSemanticDiagnosticsOfNextAffectedFile())) { - affectedFiles.add(result.affected as ts.SourceFile); + override async update(files: Set): Promise { + for (const file of files) { + this.#sourceFiles.delete(toPosixPath(file)); + } } - - return affectedFiles; } diff --git a/packages/angular/build/src/tools/angular/compilation/noop-compilation.ts b/packages/angular/build/src/tools/angular/compilation/noop-compilation.ts index e2c597b4d315..a2bb722d10a8 100644 --- a/packages/angular/build/src/tools/angular/compilation/noop-compilation.ts +++ b/packages/angular/build/src/tools/angular/compilation/noop-compilation.ts @@ -7,33 +7,28 @@ */ import type * as ng from '@angular/compiler-cli'; -import type ts from 'typescript'; import { AngularHostOptions } from '../angular-host'; -import { AngularCompilation } from './angular-compilation'; +import { AngularCompilation, AngularCompilationResult } from './angular-compilation'; export class NoopCompilation extends AngularCompilation { async initialize( tsconfig: string, hostOptions: AngularHostOptions, compilerOptionsTransformer?: (compilerOptions: ng.CompilerOptions) => ng.CompilerOptions, - ): Promise<{ - affectedFiles: ReadonlySet; - compilerOptions: ng.CompilerOptions; - referencedFiles: readonly string[]; - }> { + ): Promise { // Load the compiler configuration and transform as needed const { options: originalCompilerOptions } = await this.loadConfiguration(tsconfig); const compilerOptions = compilerOptionsTransformer?.(originalCompilerOptions) ?? originalCompilerOptions; - return { affectedFiles: new Set(), compilerOptions, referencedFiles: [] }; + return { compilerOptions, referencedFiles: [] }; } - collectDiagnostics(): never { + protected override collectDiagnostics(): never { throw new Error('Not available when using noop compilation.'); } - emitAffectedFiles(): never { + override emitAffectedFiles(): never { throw new Error('Not available when using noop compilation.'); } } diff --git a/packages/angular/build/src/tools/angular/compilation/parallel-compilation.ts b/packages/angular/build/src/tools/angular/compilation/parallel-compilation.ts index a0dd58179ba2..f9a504ee6fc0 100644 --- a/packages/angular/build/src/tools/angular/compilation/parallel-compilation.ts +++ b/packages/angular/build/src/tools/angular/compilation/parallel-compilation.ts @@ -10,11 +10,15 @@ import type { CompilerOptions } from '@angular/compiler-cli'; import type { PartialMessage } from 'esbuild'; import { createRequire } from 'node:module'; import { MessageChannel } from 'node:worker_threads'; -import type { SourceFile } from 'typescript'; import { WorkerPool } from '../../../utils/worker-pool'; import { mergeCumulativeDurations } from '../../esbuild/profiling'; import type { AngularHostOptions } from '../angular-host'; -import { AngularCompilation, DiagnosticModes, EmitFileResult } from './angular-compilation'; +import { + AngularCompilation, + AngularCompilationResult, + DiagnosticModes, + EmitFileResult, +} from './angular-compilation'; /** * An Angular compilation which uses a Node.js Worker thread to load and execute @@ -26,6 +30,7 @@ import { AngularCompilation, DiagnosticModes, EmitFileResult } from './angular-c */ export class ParallelCompilation extends AngularCompilation { readonly #worker: WorkerPool; + #webWorkerChannel?: MessageChannel; constructor( private readonly jit: boolean, @@ -43,16 +48,11 @@ export class ParallelCompilation extends AngularCompilation { }); } - override initialize( + override async initialize( tsconfig: string, hostOptions: AngularHostOptions, compilerOptionsTransformer?: (compilerOptions: CompilerOptions) => CompilerOptions, - ): Promise<{ - affectedFiles: ReadonlySet; - compilerOptions: CompilerOptions; - referencedFiles: readonly string[]; - externalStylesheets?: ReadonlyMap; - }> { + ): Promise { const stylesheetChannel = new MessageChannel(); // The request identifier is required because Angular can issue multiple concurrent requests stylesheetChannel.port1.on( @@ -65,9 +65,12 @@ export class ParallelCompilation extends AngularCompilation { }, ); + this.#webWorkerChannel?.port1.close(); + // The web worker processing is a synchronous operation and uses shared memory combined with // the Atomics API to block execution here until a response is received. const webWorkerChannel = new MessageChannel(); + this.#webWorkerChannel = webWorkerChannel; const webWorkerSignal = new Int32Array(new SharedArrayBuffer(4)); webWorkerChannel.port1.on('message', ({ workerFile, containingFile }) => { try { @@ -90,31 +93,44 @@ export class ParallelCompilation extends AngularCompilation { const transformedOptions = compilerOptionsTransformer?.(compilerOptions) ?? compilerOptions; optionsChannel.port1.postMessage({ transformedOptions }); } catch (error) { - webWorkerChannel.port1.postMessage({ error }); + optionsChannel.port1.postMessage({ error }); } finally { Atomics.store(optionsSignal, 0, 1); Atomics.notify(optionsSignal, 0); } }); - // Execute the initialize function in the worker thread - return this.#worker.run( - { - fileReplacements: hostOptions.fileReplacements, - tsconfig, - jit: this.jit, - browserOnlyBuild: this.browserOnlyBuild, - stylesheetPort: stylesheetChannel.port2, - optionsPort: optionsChannel.port2, - optionsSignal, - webWorkerPort: webWorkerChannel.port2, - webWorkerSignal, - }, - { - name: 'initialize', - transferList: [stylesheetChannel.port2, optionsChannel.port2, webWorkerChannel.port2], - }, - ); + let success = false; + try { + // Execute the initialize function in the worker thread + const result = await this.#worker.run( + { + fileReplacements: hostOptions.fileReplacements, + tsconfig, + jit: this.jit, + browserOnlyBuild: this.browserOnlyBuild, + stylesheetPort: stylesheetChannel.port2, + optionsPort: optionsChannel.port2, + optionsSignal, + webWorkerPort: webWorkerChannel.port2, + webWorkerSignal, + }, + { + name: 'initialize', + transferList: [stylesheetChannel.port2, optionsChannel.port2, webWorkerChannel.port2], + }, + ); + success = true; + + return result; + } finally { + stylesheetChannel.port1.close(); + optionsChannel.port1.close(); + if (!success) { + this.#webWorkerChannel?.port1.close(); + this.#webWorkerChannel = undefined; + } + } } /** @@ -136,8 +152,13 @@ export class ParallelCompilation extends AngularCompilation { return result; } - override emitAffectedFiles(): Promise> { - return this.#worker.run(undefined, { name: 'emit' }); + override async emitAffectedFiles(): Promise> { + try { + return await this.#worker.run(undefined, { name: 'emit' }); + } finally { + this.#webWorkerChannel?.port1.close(); + this.#webWorkerChannel = undefined; + } } override update(files: Set): Promise { @@ -145,6 +166,9 @@ export class ParallelCompilation extends AngularCompilation { } override close() { + this.#webWorkerChannel?.port1.close(); + this.#webWorkerChannel = undefined; + return this.#worker.destroy(); } } diff --git a/packages/angular/build/src/tools/angular/compilation/parallel-worker.ts b/packages/angular/build/src/tools/angular/compilation/parallel-worker.ts index 9e41b7940989..b736e710fa7d 100644 --- a/packages/angular/build/src/tools/angular/compilation/parallel-worker.ts +++ b/packages/angular/build/src/tools/angular/compilation/parallel-worker.ts @@ -10,9 +10,13 @@ import type { PartialMessage } from 'esbuild'; import assert from 'node:assert'; import { randomUUID } from 'node:crypto'; import { type MessagePort, receiveMessageOnPort } from 'node:worker_threads'; -import { SourceFileCache } from '../../esbuild/angular/source-file-cache'; +import { initializeHash } from '../../../utils/hash'; import { getAndClearCumulativeDurations } from '../../esbuild/profiling'; -import type { AngularCompilation, DiagnosticModes } from './angular-compilation'; +import type { + AngularCompilation, + AngularCompilationResult, + DiagnosticModes, +} from './angular-compilation'; import { AotCompilation } from './aot-compilation'; import { JitCompilation } from './jit-compilation'; @@ -29,96 +33,122 @@ export interface InitRequest { } let compilation: AngularCompilation | undefined; - -const sourceFileCache = new SourceFileCache(); - -export async function initialize(request: InitRequest) { - compilation ??= request.jit - ? new JitCompilation(request.browserOnlyBuild) - : new AotCompilation(request.browserOnlyBuild); - - const stylesheetRequests = new Map void, (reason: Error) => void]>(); - request.stylesheetPort.on('message', ({ requestId, value, error }) => { - if (error) { - stylesheetRequests.get(requestId)?.[1](error); - } else { - stylesheetRequests.get(requestId)?.[0](value); - } - }); - - const { - compilerOptions, - referencedFiles, - externalStylesheets, - templateUpdates, - componentResourcesDependencies, - } = await compilation.initialize( - request.tsconfig, - { - fileReplacements: request.fileReplacements, - sourceFileCache, - modifiedFiles: sourceFileCache.modifiedFiles, - transformStylesheet(data, containingFile, stylesheetFile, order, className) { - const requestId = randomUUID(); - const resultPromise = new Promise((resolve, reject) => - stylesheetRequests.set(requestId, [resolve, reject]), - ); - - request.stylesheetPort.postMessage({ - requestId, - data, - containingFile, - stylesheetFile, - order, - className, - }); - - return resultPromise; +let activeWebWorkerPort: MessagePort | undefined; + +const modifiedFiles = new Set(); + +export async function initialize(request: InitRequest): Promise { + activeWebWorkerPort?.close(); + activeWebWorkerPort = request.webWorkerPort; + + const currentModifiedFiles = new Set(modifiedFiles); + modifiedFiles.clear(); + + let success = false; + try { + await initializeHash(); + compilation ??= request.jit + ? new JitCompilation(request.browserOnlyBuild) + : new AotCompilation(request.browserOnlyBuild); + + const stylesheetRequests = new Map< + string, + [(value: string) => void, (reason: Error) => void] + >(); + request.stylesheetPort.on('message', ({ requestId, value, error }) => { + const handlers = stylesheetRequests.get(requestId); + if (handlers) { + stylesheetRequests.delete(requestId); + if (error) { + handlers[1](error); + } else { + handlers[0](value); + } + } + }); + + const { + compilerOptions, + referencedFiles, + externalStylesheets, + templateUpdates, + componentResourcesDependencies, + } = await compilation.initialize( + request.tsconfig, + { + fileReplacements: request.fileReplacements, + modifiedFiles: currentModifiedFiles, + transformStylesheet(data, containingFile, stylesheetFile, order, className) { + const requestId = randomUUID(); + const resultPromise = new Promise((resolve, reject) => + stylesheetRequests.set(requestId, [resolve, reject]), + ); + + request.stylesheetPort.postMessage({ + requestId, + data, + containingFile, + stylesheetFile, + order, + className, + }); + + return resultPromise; + }, + processWebWorker(workerFile, containingFile) { + Atomics.store(request.webWorkerSignal, 0, 0); + request.webWorkerPort.postMessage({ workerFile, containingFile }); + + Atomics.wait(request.webWorkerSignal, 0, 0); + const result = receiveMessageOnPort(request.webWorkerPort)?.message; + + if (result?.error) { + throw result.error; + } + + return result?.workerCodeFile ?? workerFile; + }, }, - processWebWorker(workerFile, containingFile) { - Atomics.store(request.webWorkerSignal, 0, 0); - request.webWorkerPort.postMessage({ workerFile, containingFile }); + (compilerOptions) => { + Atomics.store(request.optionsSignal, 0, 0); + request.optionsPort.postMessage(compilerOptions); - Atomics.wait(request.webWorkerSignal, 0, 0); - const result = receiveMessageOnPort(request.webWorkerPort)?.message; + Atomics.wait(request.optionsSignal, 0, 0); + const result = receiveMessageOnPort(request.optionsPort)?.message; if (result?.error) { throw result.error; } - return result?.workerCodeFile ?? workerFile; + return result?.transformedOptions ?? compilerOptions; }, - }, - (compilerOptions) => { - Atomics.store(request.optionsSignal, 0, 0); - request.optionsPort.postMessage(compilerOptions); - - Atomics.wait(request.optionsSignal, 0, 0); - const result = receiveMessageOnPort(request.optionsPort)?.message; - - if (result?.error) { - throw result.error; - } - - return result?.transformedOptions ?? compilerOptions; - }, - ); - - return { - externalStylesheets, - templateUpdates, - referencedFiles, - // TODO: Expand? `allowJs`, `isolatedModules`, `sourceMap`, `inlineSourceMap` are the only fields needed currently. - compilerOptions: { - allowJs: compilerOptions.allowJs, - isolatedModules: compilerOptions.isolatedModules, - sourceMap: compilerOptions.sourceMap, - inlineSourceMap: compilerOptions.inlineSourceMap, - _useTypeScriptTranspilation: compilerOptions['_useTypeScriptTranspilation'] as - boolean | undefined, - }, - componentResourcesDependencies, - }; + ); + + success = true; + + return { + externalStylesheets, + templateUpdates, + referencedFiles, + // TODO: Expand? `allowJs`, `isolatedModules`, `sourceMap`, `inlineSourceMap` are the only fields needed currently. + compilerOptions: { + allowJs: compilerOptions.allowJs, + isolatedModules: compilerOptions.isolatedModules, + sourceMap: compilerOptions.sourceMap, + inlineSourceMap: compilerOptions.inlineSourceMap, + _useTypeScriptTranspilation: compilerOptions['_useTypeScriptTranspilation'] as + boolean | undefined, + }, + componentResourcesDependencies, + }; + } finally { + request.stylesheetPort.close(); + request.optionsPort.close(); + if (!success) { + activeWebWorkerPort?.close(); + activeWebWorkerPort = undefined; + } + } } export async function diagnose(modes: DiagnosticModes): Promise<{ @@ -140,11 +170,19 @@ export async function diagnose(modes: DiagnosticModes): Promise<{ export async function emit() { assert(compilation); - const files = await compilation.emitAffectedFiles(); + try { + const files = await compilation.emitAffectedFiles(); - return [...files]; + return [...files]; + } finally { + activeWebWorkerPort?.close(); + activeWebWorkerPort = undefined; + } } -export function update(files: Set): void { - sourceFileCache.invalidate(files); +export async function update(files: Set): Promise { + for (const file of files) { + modifiedFiles.add(file); + } + await compilation?.update?.(files); } diff --git a/packages/angular/build/src/tools/angular/linker/oxc-linker.ts b/packages/angular/build/src/tools/angular/linker/oxc-linker.ts index 467a1332cd97..26133a6b5611 100644 --- a/packages/angular/build/src/tools/angular/linker/oxc-linker.ts +++ b/packages/angular/build/src/tools/angular/linker/oxc-linker.ts @@ -6,19 +6,13 @@ * found in the LICENSE file at https://angular.dev/license */ -import type { EncodedSourceMap } from '@ampproject/remapping'; -import remapping from '@ampproject/remapping'; import { ConsoleLogger, LogLevel } from '@angular/compiler-cli'; -import type { DeclarationScope } from '@angular/compiler-cli/linker'; -import { FileLinker, LinkerEnvironment, needsLinking } from '@angular/compiler-cli/linker'; +import { type DeclarationScope, FileLinker, LinkerEnvironment } from '@angular/compiler-cli/linker'; import type { AbsoluteFsPath, ReadonlyFileSystem, } from '@angular/compiler-cli/src/ngtsc/file_system'; -import type { CallExpression, Node } from '@oxc-project/types'; -import MagicString from 'magic-string'; -import { parseSync, visitorKeys } from 'oxc-parser'; -import { loadInputSourceMap } from '../../../utils/source-map'; +import type { CallExpression } from '@oxc-project/types'; import { OxcAstHost } from './oxc-ast-host'; import { StringAstFactory } from './string-ast-factory'; @@ -51,136 +45,52 @@ const noopFileSystem: ReadonlyFileSystem = { relative: (_from: string, to: string) => to, } as unknown as ReadonlyFileSystem; -const SHARED_LOGGER = new ConsoleLogger(LogLevel.info); - -const SHARED_AST_HOST = new OxcAstHost(); -const SHARED_DECLARATION_SCOPE = new InlineDeclarationScope(); - -/** - * Recursively traverses ESTree AST nodes with subtree pruning. - * When `onCallExpression` returns `true` for a linked `CallExpression`, - * child traversal into `callee` and `arguments` is skipped. - * - * Why subtree pruning is safe for the linker: - * - Angular partial declarations (`ɵɵngDeclareComponent`, `ɵɵngDeclareDirective`, - * etc.) are never nested inside each other. - * - Once a declaration `CallExpression` is linked and replaced, there can never be - * another partial declaration within its metadata argument object. Pruning its - * subtree avoids traversing hundreds of unnecessary metadata argument nodes per - * component. - */ -function visitNode( - node: Node | Node[] | null | undefined, - onCallExpression: (node: CallExpression) => boolean, -): void { - if (node === null || node === undefined || typeof node !== 'object') { - return; - } - - if (Array.isArray(node)) { - for (let i = 0; i < node.length; i++) { - visitNode(node[i], onCallExpression); - } - - return; - } - - const nodeType = node.type; - if (!nodeType) { - return; - } - - if (nodeType === 'CallExpression') { - if (onCallExpression(node)) { - // Subtree pruning: partial declarations cannot be nested, so skip child traversal. - return; - } - } - - const keys = visitorKeys[nodeType]; - if (keys) { - for (let i = 0; i < keys.length; i++) { - const child = (node as unknown as Record)[keys[i]]; - if (child !== undefined && child !== null) { - visitNode(child, onCallExpression); - } - } - } -} - -export interface OxcLinkerOptions { - sourcemap?: boolean; - jit?: boolean; - skipCheck?: boolean; -} +let SHARED_LOGGER: ConsoleLogger; +let SHARED_AST_HOST: OxcAstHost; +let SHARED_DECLARATION_SCOPE: InlineDeclarationScope; /** - * Executes Angular partial declaration linking on the specified JavaScript file - * using `oxc-parser` and `magic-string`. - * - * @param filename The full path to the file. - * @param code The source code content. - * @param options Linker options (sourcemap, jit, skipCheck). - * @returns An object containing the transformed code and optional source map. + * Manages Angular partial declaration linking using Oxc AST nodes. */ -export function linkWithOxc(filename: string, code: string, options: OxcLinkerOptions = {}) { - if (!options.skipCheck && !needsLinking(filename, code)) { - return { code, map: undefined }; +export class OxcLinker { + readonly #fileLinker: FileLinker; + + constructor(filename: string, code: string, jit = false) { + SHARED_LOGGER ??= new ConsoleLogger(LogLevel.info); + SHARED_AST_HOST ??= new OxcAstHost(); + SHARED_DECLARATION_SCOPE ??= new InlineDeclarationScope(); + + const astFactory = new StringAstFactory(code); + const linkerEnvironment = LinkerEnvironment.create( + noopFileSystem, + SHARED_LOGGER, + SHARED_AST_HOST, + astFactory, + { linkerJitMode: jit, sourceMapping: false }, + ); + + this.#fileLinker = new FileLinker(linkerEnvironment, filename as AbsoluteFsPath, code); } - const astFactory = new StringAstFactory(code); - - const linkerEnvironment = LinkerEnvironment.create( - noopFileSystem, - SHARED_LOGGER, - SHARED_AST_HOST, - astFactory, - { linkerJitMode: options.jit ?? false, sourceMapping: false }, - ); - - const fileLinker = new FileLinker(linkerEnvironment, filename as AbsoluteFsPath, code); - const { program } = parseSync(filename, code, { range: true }); - - let s: MagicString | undefined; - let hasLinked = false; - - visitNode(program, (node) => { + /** + * Attempts to link an Angular partial declaration CallExpression. + * + * @param node The CallExpression AST node to check and link. + * @returns The linked code string if the node is a partial declaration, or undefined otherwise. + */ + linkCallExpression(node: CallExpression): string | undefined { const calleeName = SHARED_AST_HOST.getSymbolName(node.callee); - if (calleeName && fileLinker.isPartialDeclaration(calleeName)) { - const args = SHARED_AST_HOST.parseArguments(node); - const linkedCode = fileLinker.linkPartialDeclaration( - calleeName, - args, - SHARED_DECLARATION_SCOPE, - ); - - s ??= new MagicString(code); - s.overwrite(node.start, node.end, linkedCode as string); - hasLinked = true; - - return true; + if (!calleeName || !this.#fileLinker.isPartialDeclaration(calleeName)) { + return undefined; } - return false; - }); + const args = SHARED_AST_HOST.parseArguments(node); + const linkedCode = this.#fileLinker.linkPartialDeclaration( + calleeName, + args, + SHARED_DECLARATION_SCOPE, + ); - if (!hasLinked || !s) { - return { code, map: undefined }; + return linkedCode as string; } - - let map: string | undefined; - if (options.sourcemap) { - const rawMap = s.generateMap({ hires: true, source: filename }); - const inputMap = loadInputSourceMap(filename, code); - if (inputMap) { - map = remapping([rawMap as EncodedSourceMap, inputMap], () => null).toString(); - } else { - map = rawMap.toString(); - } - } - - return { - code: s.toString(), - map, - }; } diff --git a/packages/angular/build/src/tools/angular/linker/oxc-linker_spec.ts b/packages/angular/build/src/tools/angular/linker/oxc-linker_spec.ts index 148c67f9dce3..fa62c1c5ece1 100644 --- a/packages/angular/build/src/tools/angular/linker/oxc-linker_spec.ts +++ b/packages/angular/build/src/tools/angular/linker/oxc-linker_spec.ts @@ -6,12 +6,12 @@ * found in the LICENSE file at https://angular.dev/license */ -import { linkWithOxc } from './oxc-linker'; +import { transform } from '../../oxc/oxc-transform'; -describe('linkWithOxc', () => { +describe('oxc-linker', () => { it('should not modify code that does not need linking', () => { const input = 'const x = 1;'; - const result = linkWithOxc('test.js', input); + const result = transform('test.js', input, { link: true, advancedOptimizations: false }); expect(result.code).toBe(input); expect(result.map).toBeUndefined(); }); @@ -29,7 +29,7 @@ describe('linkWithOxc', () => { }); `; - const result = linkWithOxc('test.js', input); + const result = transform('test.js', input, { link: true, advancedOptimizations: false }); expect(result.code).toContain('i0.ɵɵdefineDirective'); expect(result.code).not.toContain('i0.ɵɵngDeclareDirective'); }); @@ -49,8 +49,31 @@ describe('linkWithOxc', () => { }); `; - const result = linkWithOxc('test.js', input); + const result = transform('test.js', input, { link: true, advancedOptimizations: false }); expect(result.code).toContain('i0.ɵɵdefineComponent'); expect(result.code).not.toContain('i0.ɵɵngDeclareComponent'); }); + + it('should generate a decoded sourcemap when sourcemap option is enabled', () => { + const input = ` + import * as i0 from "@angular/core"; + export class MyDirective {} + MyDirective.ɵdir = i0.ɵɵngDeclareDirective({ + minVersion: "12.0.0", + version: "14.0.0", + ngImport: i0, + type: MyDirective, + selector: "[my-dir]" + }); + `; + + const result = transform('test.js', input, { + link: true, + advancedOptimizations: false, + sourcemap: true, + }); + expect(result.map).toBeDefined(); + expect(result.map?.version).toBe(3); + expect(result.map?.sources).toContain('test.js'); + }); }); diff --git a/packages/angular/build/src/tools/esbuild/angular/compiler-plugin.ts b/packages/angular/build/src/tools/esbuild/angular/compiler-plugin.ts index 131547f78366..b0ff0593cecc 100644 --- a/packages/angular/build/src/tools/esbuild/angular/compiler-plugin.ts +++ b/packages/angular/build/src/tools/esbuild/angular/compiler-plugin.ts @@ -18,10 +18,10 @@ import type { PluginBuild, } from 'esbuild'; import assert from 'node:assert'; -import { createHash } from 'node:crypto'; import { readFile } from 'node:fs/promises'; import * as path from 'node:path'; import { maxWorkers, useTypeChecking } from '../../../utils/environment-options'; +import { calculateHash, initializeHash } from '../../../utils/hash'; import { AngularHostOptions } from '../../angular/angular-host'; import { AngularCompilation, DiagnosticModes, NoopCompilation } from '../../angular/compilation'; import { type PersistentCacheStore, createPersistentCacheStore } from '../cache'; @@ -149,6 +149,7 @@ export function createCompilerPlugin( // eslint-disable-next-line max-lines-per-function build.onStart(async () => { + await initializeHash(); angularCompilationContext.markAsInProgress(); const result: OnStartResult = { @@ -186,7 +187,6 @@ export function createCompilerPlugin( const hostOptions: AngularHostOptions = { fileReplacements: pluginOptions.fileReplacements, modifiedFiles, - sourceFileCache: pluginOptions.sourceFileCache, async transformStylesheet(data, containingFile, stylesheetFile, order, className) { let stylesheetResult; let resultSource = stylesheetFile ?? containingFile; @@ -205,11 +205,7 @@ export function createCompilerPlugin( // invalid the output and force a full page reload for HMR cases. The containing file and order // of the style within the containing file is used. pluginOptions.externalRuntimeStyles - ? createHash('sha256') - .update(containingFile) - .update((order ?? 0).toString()) - .update(className ?? '') - .digest('hex') + ? calculateHash(`${containingFile}${order ?? 0}${className ?? ''}`) : undefined, ); // Adjust result source for inline styles. @@ -365,24 +361,26 @@ export function createCompilerPlugin( } } - // Update TypeScript file output cache for all affected files - try { - await profileAsync('NG_EMIT_TS', async () => { - for (const { filename, contents } of await compilation.emitAffectedFiles()) { - typeScriptFileCache.set(path.normalize(filename), contents); - } - }); - } catch (error) { - (result.errors ??= []).push({ - text: 'Angular compilation emit failed.', - location: null, - notes: [ - { - text: error instanceof Error ? (error.stack ?? error.message) : `${error}`, - location: null, - }, - ], - }); + // Update TypeScript file output cache for all affected files if not using on-demand transforms + if (!compilation.transformFile) { + try { + await profileAsync('NG_EMIT_TS', async () => { + for (const { filename, contents } of await compilation.emitAffectedFiles()) { + typeScriptFileCache.set(path.normalize(filename), contents); + } + }); + } catch (error) { + (result.errors ??= []).push({ + text: 'Angular compilation emit failed.', + location: null, + notes: [ + { + text: error instanceof Error ? (error.stack ?? error.message) : `${error}`, + location: null, + }, + ], + }); + } } const diagnostics = await compilation.diagnoseFiles( @@ -437,6 +435,35 @@ export function createCompilerPlugin( // cache is later stored to disk, then the options that affect transform output // would need to be added to the key as well as a check for any change of content. let contents = typeScriptFileCache.get(request); + let directContents: string | undefined; + + if (contents === undefined && compilation.transformFile) { + try { + directContents = await readFile(request, 'utf-8'); + const transformResult = await compilation.transformFile(request, directContents); + if (transformResult) { + contents = transformResult.contents; + if (transformResult.watchFiles) { + referencedFileTracker.add(request, transformResult.watchFiles); + } + } + } catch (error) { + return { + errors: [ + { + text: 'Angular compilation transform failed.', + location: { file: request }, + notes: [ + { + text: error instanceof Error ? (error.stack ?? error.message) : `${error}`, + location: null, + }, + ], + }, + ], + }; + } + } if (contents === undefined) { // If the Angular compilation had errors the file may not have been emitted. @@ -455,11 +482,11 @@ export function createCompilerPlugin( // Evaluate whether the file requires the Angular compiler transpilation. // If not, issue a warning but allow bundler to process the file (no type-checking). - const directContents = await readFile(request, 'utf-8'); + directContents ??= await readFile(request, 'utf-8'); if (!requiresAngularCompiler(directContents)) { return { warnings: [createMissingFileDiagnostic(request, args.path, diangosticRoot, false)], - contents, + contents: directContents, loader: 'ts', resolveDir: path.dirname(request), }; diff --git a/packages/angular/build/src/tools/esbuild/angular/component-stylesheets.ts b/packages/angular/build/src/tools/esbuild/angular/component-stylesheets.ts index 79008d140729..dd1b3b704150 100644 --- a/packages/angular/build/src/tools/esbuild/angular/component-stylesheets.ts +++ b/packages/angular/build/src/tools/esbuild/angular/component-stylesheets.ts @@ -7,11 +7,12 @@ */ import assert from 'node:assert'; -import { createHash } from 'node:crypto'; import path from 'node:path'; +import { createContentHash } from '../../../utils/hash'; import { BundleContextResult, BundlerContext } from '../bundler-context'; import { type BuildOutputFile, BuildOutputFileType } from '../bundler-files'; import { MemoryCache } from '../cache'; +import { MemoryLoadResultCache } from '../load-result-cache'; import { BundleStylesheetOptions, createStylesheetBundleOptions, @@ -30,11 +31,12 @@ export type ComponentStylesheetResult = BundleContextResult & { export class ComponentStylesheetBundler { readonly #fileContexts = new MemoryCache(); readonly #inlineContexts = new MemoryCache(); + readonly #loadCache = new MemoryLoadResultCache(); /** - * * @param options An object containing the stylesheet bundling options. - * @param cache A load result cache to use when bundling. + * @param defaultInlineLanguage The default language to use for inline component styles. + * @param incremental True if incremental watch mode is enabled. */ constructor( private readonly options: BundleStylesheetOptions, @@ -54,29 +56,38 @@ export class ComponentStylesheetBundler { externalId?: string | boolean, direct?: boolean, ): Promise { - const bundlerContext = await this.#fileContexts.getOrCreate(entry, () => { - return new BundlerContext(this.options.workspaceRoot, this.incremental, (loadCache) => { - const buildOptions = createStylesheetBundleOptions(this.options, loadCache); - if (externalId) { - assert( - typeof externalId === 'string', - 'Initial external component stylesheets must have a string identifier', - ); - - buildOptions.entryPoints = { [externalId]: entry }; - buildOptions.entryNames = '[name]'; - delete buildOptions.publicPath; - } else { - buildOptions.entryPoints = [entry]; - } + entry = path.normalize(entry); - // Angular encapsulation does not support nesting - // See: https://github.com/angular/angular/issues/58996 - buildOptions.supported ??= {}; - buildOptions.supported['nesting'] = false; + const bundlerContext = await this.#fileContexts.getOrCreate(entry, () => { + return new BundlerContext( + this.options.workspaceRoot, + this.incremental, + (loadCache) => { + const buildOptions = createStylesheetBundleOptions(this.options, loadCache); + if (externalId) { + assert( + typeof externalId === 'string', + 'Initial external component stylesheets must have a string identifier', + ); + + buildOptions.entryPoints = { [externalId]: entry }; + buildOptions.entryNames = '[name]'; + delete buildOptions.publicPath; + } else { + buildOptions.entryPoints = [entry]; + } - return buildOptions; - }); + // Angular encapsulation does not support nesting + // See: https://github.com/angular/angular/issues/58996 + buildOptions.supported ??= {}; + buildOptions.supported['nesting'] = false; + + return buildOptions; + }, + /* useContext */ false, + /* initialFilter */ undefined, + this.#loadCache, + ); }); return this.extractResult( @@ -101,60 +112,68 @@ export class ComponentStylesheetBundler { language = this.defaultInlineLanguage, externalId?: string, ): Promise { + filename = path.normalize(filename); + // Use a hash of the inline stylesheet content to ensure a consistent identifier. External stylesheets will resolve // to the actual stylesheet file path. - // TODO: Consider xxhash instead for hashing - const id = createHash('sha256') - .update(data) - .update(externalId ?? '') - .digest('hex'); + const hasher = createContentHash(); + hasher.update(data); + hasher.update(externalId ?? ''); + const id = hasher.digest(); const entry = [language, id, filename].join(';'); const bundlerContext = await this.#inlineContexts.getOrCreate(entry, () => { const namespace = 'angular:styles/component'; - return new BundlerContext(this.options.workspaceRoot, this.incremental, (loadCache) => { - const buildOptions = createStylesheetBundleOptions(this.options, loadCache, { - [entry]: data, - }); - if (externalId) { - buildOptions.entryPoints = { [externalId]: `${namespace};${entry}` }; - buildOptions.entryNames = '[name]'; - delete buildOptions.publicPath; - } else { - buildOptions.entryPoints = [`${namespace};${entry}`]; - } + return new BundlerContext( + this.options.workspaceRoot, + this.incremental, + (loadCache) => { + const buildOptions = createStylesheetBundleOptions(this.options, loadCache, { + [entry]: data, + }); + if (externalId) { + buildOptions.entryPoints = { [externalId]: `${namespace};${entry}` }; + buildOptions.entryNames = '[name]'; + delete buildOptions.publicPath; + } else { + buildOptions.entryPoints = [`${namespace};${entry}`]; + } - // Angular encapsulation does not support nesting - // See: https://github.com/angular/angular/issues/58996 - buildOptions.supported ??= {}; - buildOptions.supported['nesting'] = false; - - buildOptions.plugins.push({ - name: 'angular-component-styles', - setup(build) { - build.onResolve({ filter: /^angular:styles\/component;/ }, (args) => { - if (args.kind !== 'entry-point') { - return null; - } - - return { - path: entry, - namespace, - }; - }); - build.onLoad({ filter: /^css;/, namespace }, () => { - return { - contents: data, - loader: 'css', - resolveDir: path.dirname(filename), - }; - }); - }, - }); - - return buildOptions; - }); + // Angular encapsulation does not support nesting + // See: https://github.com/angular/angular/issues/58996 + buildOptions.supported ??= {}; + buildOptions.supported['nesting'] = false; + + buildOptions.plugins.push({ + name: 'angular-component-styles', + setup(build) { + build.onResolve({ filter: /^angular:styles\/component;/ }, (args) => { + if (args.kind !== 'entry-point') { + return null; + } + + return { + path: entry, + namespace, + }; + }); + build.onLoad({ filter: /^css;/, namespace }, () => { + return { + contents: data, + loader: 'css', + resolveDir: path.dirname(filename), + }; + }); + }, + }); + + return buildOptions; + }, + /* useContext */ false, + /* initialFilter */ undefined, + this.#loadCache, + ); }); // Extract the result of the bundling from the output files @@ -171,12 +190,20 @@ export class ComponentStylesheetBundler { * @param files The group of files that have been modified * @returns An array of file based stylesheet entries if any were invalidated; otherwise, undefined. */ - invalidate(files: Iterable): string[] | undefined { + invalidate(files: Iterable | ReadonlySet): string[] | undefined { if (!this.incremental) { return; } - const normalizedFiles = [...files].map(path.normalize); + const normalizedFiles = new Set(); + for (const file of files) { + const normalized = path.normalize(file); + normalizedFiles.add(normalized); + if (!path.isAbsolute(normalized)) { + normalizedFiles.add(path.normalize(path.join(this.options.workspaceRoot, normalized))); + } + } + let entries: string[] | undefined; for (const [entry, bundler] of this.#fileContexts.entries()) { @@ -185,8 +212,17 @@ export class ComponentStylesheetBundler { entries.push(entry); } } - for (const bundler of this.#inlineContexts.values()) { - bundler.invalidate(normalizedFiles); + for (const [entry, bundler] of this.#inlineContexts.entries()) { + // Entry is format: [language, id, filename].join(';') + const firstSemi = entry.indexOf(';'); + const secondSemi = firstSemi !== -1 ? entry.indexOf(';', firstSemi + 1) : -1; + const filename = secondSemi !== -1 ? entry.slice(secondSemi + 1) : ''; + if (filename && normalizedFiles.has(path.normalize(filename))) { + this.#inlineContexts.delete(entry); + void bundler.dispose(); + } else { + bundler.invalidate(normalizedFiles); + } } return entries; @@ -205,6 +241,7 @@ export class ComponentStylesheetBundler { const contexts = [...this.#fileContexts.values(), ...this.#inlineContexts.values()]; this.#fileContexts.clear(); this.#inlineContexts.clear(); + this.#loadCache.clear(); await Promise.allSettled(contexts.map((context) => context.dispose())); } @@ -259,14 +296,18 @@ export class ComponentStylesheetBundler { } } - const metafile = result.metafile; - // Remove entryPoint fields from outputs to prevent the internal component styles from being - // treated as initial files. Also mark the entry as a component resource for stat reporting. - Object.values(metafile.outputs).forEach((output) => { - delete output.entryPoint; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (output as any)['ng-component'] = true; - }); + // Clone metafile to prevent mutation of the cached result by downstream plugins + const metafile = { + inputs: { ...result.metafile.inputs }, + outputs: Object.fromEntries( + Object.entries(result.metafile.outputs).map(([key, output]) => { + const cloned = { ...output, ['ng-component']: true }; + delete cloned.entryPoint; + + return [key, cloned]; + }), + ), + }; return { errors, @@ -276,6 +317,7 @@ export class ComponentStylesheetBundler { metafile, referencedFiles, externalImports: result.externalImports, + platform: result.platform, initialFiles: new Map(), }; } diff --git a/packages/angular/build/src/tools/esbuild/angular/source-file-cache.ts b/packages/angular/build/src/tools/esbuild/angular/source-file-cache.ts index a408650a4f4f..136fbb651ba2 100644 --- a/packages/angular/build/src/tools/esbuild/angular/source-file-cache.ts +++ b/packages/angular/build/src/tools/esbuild/angular/source-file-cache.ts @@ -6,32 +6,24 @@ * found in the LICENSE file at https://angular.dev/license */ -import { platform } from 'node:os'; import * as path from 'node:path'; -import type ts from 'typescript'; import { MemoryLoadResultCache } from '../load-result-cache'; -const USING_WINDOWS = platform() === 'win32'; -const WINDOWS_SEP_REGEXP = new RegExp(`\\${path.win32.sep}`, 'g'); - -export class SourceFileCache extends Map { +export class SourceFileCache { readonly modifiedFiles = new Set(); readonly typeScriptFileCache = new Map(); readonly loadResultCache = new MemoryLoadResultCache(); referencedFiles?: readonly string[]; - constructor(readonly persistentCachePath?: string) { - super(); - } + constructor(readonly persistentCachePath?: string) {} /** * Releases all cached content. The cached data is only needed for incremental * rebuilds and can include the emitted contents of every TypeScript file in the * program. The cache is repopulated if a build is performed after this is called. */ - override clear(): void { - super.clear(); + clear(): void { this.modifiedFiles.clear(); this.typeScriptFileCache.clear(); this.loadResultCache.clear(); @@ -50,13 +42,6 @@ export class SourceFileCache extends Map { file = path.normalize(file); invalid = this.loadResultCache.invalidate(file) || invalid; invalid = extraWatchFiles.has(file) || invalid; - - // Normalize separators to allow matching TypeScript Host paths - if (USING_WINDOWS) { - file = file.replace(WINDOWS_SEP_REGEXP, path.posix.sep); - } - - invalid = this.delete(file) || invalid; this.modifiedFiles.add(file); } diff --git a/packages/angular/build/src/tools/esbuild/angular/source-file-cache_spec.ts b/packages/angular/build/src/tools/esbuild/angular/source-file-cache_spec.ts new file mode 100644 index 000000000000..1a3b2c402618 --- /dev/null +++ b/packages/angular/build/src/tools/esbuild/angular/source-file-cache_spec.ts @@ -0,0 +1,34 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { SourceFileCache } from './source-file-cache'; + +describe('SourceFileCache', () => { + let cache: SourceFileCache; + + beforeEach(() => { + cache = new SourceFileCache(); + }); + + it('should clean up all caches on clear()', async () => { + cache.typeScriptFileCache.set('/test/app.component.ts', 'console.log("test");'); + cache.modifiedFiles.add('/test/app.component.ts'); + cache.referencedFiles = ['/test/app.component.ts']; + await cache.loadResultCache.put('file:/test/app.component.ts', { + contents: 'test', + loader: 'js', + }); + + cache.clear(); + + expect(cache.typeScriptFileCache.size).toBe(0); + expect(cache.modifiedFiles.size).toBe(0); + expect(cache.referencedFiles).toBeUndefined(); + expect(cache.loadResultCache.get('file:/test/app.component.ts')).toBeUndefined(); + }); +}); diff --git a/packages/angular/build/src/tools/esbuild/application-code-bundle.ts b/packages/angular/build/src/tools/esbuild/application-code-bundle.ts index 37ff846c7400..afb03d436d29 100644 --- a/packages/angular/build/src/tools/esbuild/application-code-bundle.ts +++ b/packages/angular/build/src/tools/esbuild/application-code-bundle.ts @@ -8,18 +8,17 @@ import type { BuildOptions, Plugin } from 'esbuild'; import assert from 'node:assert'; -import { createHash } from 'node:crypto'; import { extname, relative } from 'node:path'; import type { NormalizedApplicationBuildOptions } from '../../builders/application/options'; import { Platform } from '../../builders/application/schema'; import { allowMangle } from '../../utils/environment-options'; +import { calculateHash } from '../../utils/hash'; import { toPosixPath } from '../../utils/path'; import { SERVER_APP_ENGINE_MANIFEST_FILENAME, SERVER_APP_MANIFEST_FILENAME, SERVER_GENERATED_EXTERNALS, } from '../../utils/server-rendering/manifest'; -import { AngularCompilation, NoopCompilation } from '../angular/compilation'; import { AngularCompilationContext } from './angular/compilation-state'; import { createCompilerPlugin } from './angular/compiler-plugin'; import { ComponentStylesheetBundler } from './angular/component-stylesheets'; @@ -71,6 +70,15 @@ export function createBrowserCodeBundleOptions( supported: getFeatureSupport(zoneless), }; + if (options.disableCodeSplitting) { + // Splitting emits shared chunks that are read across chunk boundaries as live ESM bindings, + // which the unit-test runners' module loading does not reliably preserve. + buildOptions.splitting = false; + // In unit test builds, package.json "sideEffects": false annotations can cause esbuild + // to incorrectly elide statically-referenced barrel module bodies across multiple entry points. + buildOptions.ignoreAnnotations = true; + } + buildOptions.plugins ??= []; buildOptions.plugins.push( createWasmPlugin({ allowAsync: zoneless, cache: loadCache }), @@ -566,7 +574,7 @@ function getEsBuildCommonOptions(options: NormalizedApplicationBuildOptions): Bu '', ); - footer = { js: `/**i18n:${createHash('sha256').update(i18nHash).digest('hex')}*/` }; + footer = { js: `/**i18n:${calculateHash(i18nHash)}*/` }; } // Core conditions that are always included @@ -689,20 +697,12 @@ function getEsBuildCommonPolyfillsOptions( } // Add Angular's global locale data if i18n options are present. - // Locale data should go first so that project provided polyfill code can augment if needed. let needLocaleDataPlugin = false; if (i18nOptions.shouldInline) { // Remove localize polyfill when i18n inline transformation have been applied to all the packages. polyfills = polyfills.filter((path) => !path.startsWith('@angular/localize')); - - // Add locale data for all active locales - // TODO: Inject each individually within the inlining process itself - for (const locale of i18nOptions.inlineLocales) { - polyfills.unshift(`angular:locale/data:${locale}`); - } - needLocaleDataPlugin = true; } else if (i18nOptions.hasDefinedSourceLocale) { - // When not inlining and a source local is present, use the source locale data directly + // When not inlining and a source locale is present, use the source locale data directly polyfills.unshift(`angular:locale/data:${i18nOptions.sourceLocale}`); needLocaleDataPlugin = true; } @@ -710,7 +710,7 @@ function getEsBuildCommonPolyfillsOptions( buildOptions.plugins.unshift(createAngularLocaleDataPlugin()); } - if (polyfills.length === 0) { + if (polyfills.length === 0 && !i18nOptions.shouldInline && !i18nOptions.hasDefinedSourceLocale) { return; } diff --git a/packages/angular/build/src/tools/esbuild/budget-stats.ts b/packages/angular/build/src/tools/esbuild/budget-stats.ts index a9c32778b3db..a14d9a24f1b6 100644 --- a/packages/angular/build/src/tools/esbuild/budget-stats.ts +++ b/packages/angular/build/src/tools/esbuild/budget-stats.ts @@ -30,12 +30,12 @@ export function generateBudgetStats( }; for (const { path: file, size, type } of outputFiles) { - if (!file.endsWith('.js') && !file.endsWith('.css')) { + // Exclude server bundles + if (type === BuildOutputFileType.ServerApplication || type === BuildOutputFileType.ServerRoot) { continue; } - // Exclude server bundles - if (type === BuildOutputFileType.ServerApplication || type === BuildOutputFileType.ServerRoot) { + if (!file.endsWith('.js') && !file.endsWith('.css')) { continue; } diff --git a/packages/angular/build/src/tools/esbuild/bundler-context.ts b/packages/angular/build/src/tools/esbuild/bundler-context.ts index d3f3ca567a0f..b335a1160ba6 100644 --- a/packages/angular/build/src/tools/esbuild/bundler-context.ts +++ b/packages/angular/build/src/tools/esbuild/bundler-context.ts @@ -17,7 +17,7 @@ import { context, } from 'esbuild'; import assert from 'node:assert'; -import { basename, extname, join, relative } from 'node:path'; +import { basename, extname, isAbsolute, join, normalize, relative } from 'node:path'; import { SERVER_GENERATED_EXTERNALS } from '../../utils/server-rendering/manifest'; import { type BuildOutputFile, @@ -33,11 +33,27 @@ export type BundleContextResult = errors: undefined; warnings: Message[]; metafile: Metafile; + platform: 'browser' | 'server'; + outputFiles: BuildOutputFile[]; + initialFiles: Map; + externalImports: Set; + externalConfiguration?: string[]; + }; + +export type BundleMergedContextResult = + | { errors: Message[]; warnings: Message[] } + | { + errors: undefined; + warnings: Message[]; + metafiles: { + browser: Metafile; + server: Metafile; + }; outputFiles: BuildOutputFile[]; initialFiles: Map; externalImports: { - server?: Set; - browser?: Set; + server: Set; + browser: Set; }; externalConfiguration?: string[]; }; @@ -63,16 +79,18 @@ export class BundlerContext { #disposed = false; #optionsFactory: BundlerOptionsFactory; #shouldCacheResult: boolean; - #loadCache?: MemoryLoadResultCache; + #loadCache?: LoadResultCache; readonly watchFiles = new Set(); constructor( private workspaceRoot: string, private incremental: boolean, options: BuildOptions | BundlerOptionsFactory, - private alwaysUseContext = false, + private useContext = incremental, private initialFilter?: (initial: Readonly) => boolean, + sharedLoadCache?: LoadResultCache, ) { + this.#loadCache = sharedLoadCache; // To cache the results an option factory is needed to capture the full set of dependencies this.#shouldCacheResult = incremental && typeof options === 'function'; this.#optionsFactory = (...args) => { @@ -86,11 +104,11 @@ export class BundlerContext { }; } - static async bundleAll( + static bundleAll( contexts: Iterable, - changedFiles?: Iterable, - ): Promise { - const individualResults = await Promise.all( + changedFiles?: Iterable | ReadonlySet, + ): Promise { + return Promise.all( [...contexts].map((context) => { if (changedFiles) { context.invalidate(changedFiles); @@ -99,19 +117,13 @@ export class BundlerContext { return context.bundle(); }), ); - - return BundlerContext.mergeResults(individualResults); } - static mergeResults(results: BundleContextResult[]): BundleContextResult { - // Return directly if only one result - if (results.length === 1) { - return results[0]; - } - + static mergeResults(results: BundleContextResult[]): BundleMergedContextResult { let errors: Message[] | undefined; const warnings: Message[] = []; - const metafile: Metafile = { inputs: {}, outputs: {} }; + const browserMetafile: Metafile = { inputs: {}, outputs: {} }; + const serverMetafile: Metafile = { inputs: {}, outputs: {} }; const initialFiles = new Map(); const externalImportsBrowser = new Set(); const externalImportsServer = new Set(); @@ -126,17 +138,20 @@ export class BundlerContext { continue; } - // Combine metafiles used for the stats option as well as bundle budgets and console output + const platformIsBrowser = result.platform === 'browser'; + + // Combine metafiles used for the bundle budgets and console output if (result.metafile) { + const metafile = platformIsBrowser ? browserMetafile : serverMetafile; Object.assign(metafile.inputs, result.metafile.inputs); Object.assign(metafile.outputs, result.metafile.outputs); } - result.initialFiles.forEach((value, key) => initialFiles.set(key, value)); + const externalImports = platformIsBrowser ? externalImportsBrowser : externalImportsServer; + result.externalImports?.forEach((value) => externalImports.add(value)); + result.initialFiles.forEach((value, key) => initialFiles.set(key, value)); outputFiles.push(...result.outputFiles); - result.externalImports.browser?.forEach((value) => externalImportsBrowser.add(value)); - result.externalImports.server?.forEach((value) => externalImportsServer.add(value)); if (result.externalConfiguration) { externalConfiguration ??= new Set(); @@ -153,13 +168,16 @@ export class BundlerContext { return { errors, warnings, - metafile, initialFiles, outputFiles, externalImports: { browser: externalImportsBrowser, server: externalImportsServer, }, + metafiles: { + browser: browserMetafile, + server: serverMetafile, + }, externalConfiguration: externalConfiguration ? [...externalConfiguration] : undefined, }; } @@ -203,7 +221,7 @@ export class BundlerContext { async #performBundle(): Promise { // Create esbuild options if not present if (this.#esbuildOptions === undefined) { - if (this.incremental) { + if (this.incremental && !this.#loadCache) { this.#loadCache = new MemoryLoadResultCache(); } this.#esbuildOptions = this.#optionsFactory(this.#loadCache); @@ -218,7 +236,7 @@ export class BundlerContext { if (this.#esbuildContext) { // Rebuild using the existing incremental build context result = await this.#esbuildContext.rebuild(); - } else if (this.incremental || this.alwaysUseContext) { + } else if (this.useContext) { // Create a build context and perform the build. // Context creation does not perform a build. const esbuildContext = await context(this.#esbuildOptions); @@ -242,23 +260,12 @@ export class BundlerContext { // Build failures will throw an exception which contains errors/warnings if (isEsBuildFailure(failure)) { this.#addErrorsToWatch(failure); + this.#addLoadCacheFilesToWatch(); return failure; } else { throw failure; } - } finally { - if (this.incremental) { - // When incremental always add any files from the load result cache - if (this.#loadCache) { - for (const file of this.#loadCache.watchFiles) { - if (!isInternalAngularFile(file)) { - // watch files are fully resolved paths - this.watchFiles.add(file); - } - } - } - } } // Update files that should be watched. @@ -267,18 +274,38 @@ export class BundlerContext { if (this.incremental) { // Add input files except virtual angular files which do not exist on disk for (const input of Object.keys(result.metafile.inputs)) { - if (isInternalAngularFile(input) || isInternalBundlerFile(input)) { - continue; + const isInternal = isInternalAngularFile(input) || isInternalBundlerFile(input); + + // Input file paths are always relative to the workspace root unless already absolute + const normalizedAbsoluteInput = isAbsolute(input) + ? normalize(input) + : join(this.workspaceRoot, input); + + if (!isInternal) { + this.watchFiles.add(normalizedAbsoluteInput); } - // Input file paths are always relative to the workspace root - this.watchFiles.add(join(this.workspaceRoot, input)); + if (this.#loadCache) { + const cachedLoad = await (this.#loadCache.get(input) ?? + this.#loadCache.get(input.replace(';', ':')) ?? + this.#loadCache.get('file:' + normalizedAbsoluteInput)); + if (cachedLoad?.watchFiles) { + for (const file of cachedLoad.watchFiles) { + if (!isInternalAngularFile(file)) { + this.watchFiles.add( + isAbsolute(file) ? normalize(file) : join(this.workspaceRoot, file), + ); + } + } + } + } } } // Return if the build encountered any errors if (result.errors.length) { this.#addErrorsToWatch(result); + this.#addLoadCacheFilesToWatch(); return { errors: result.errors, @@ -416,9 +443,8 @@ export class BundlerContext { ...result, outputFiles, initialFiles, - externalImports: { - [isPlatformServer ? 'server' : 'browser']: externalImports, - }, + externalImports, + platform: isPlatformServer ? 'server' : 'browser', externalConfiguration, errors: undefined, }; @@ -428,12 +454,22 @@ export class BundlerContext { for (const error of result.errors) { let file = error.location?.file; if (file && !isInternalAngularFile(file)) { - this.watchFiles.add(join(this.workspaceRoot, file)); + this.watchFiles.add(isAbsolute(file) ? normalize(file) : join(this.workspaceRoot, file)); } for (const note of error.notes) { file = note.location?.file; if (file && !isInternalAngularFile(file)) { - this.watchFiles.add(join(this.workspaceRoot, file)); + this.watchFiles.add(isAbsolute(file) ? normalize(file) : join(this.workspaceRoot, file)); + } + } + } + } + + #addLoadCacheFilesToWatch(): void { + if (this.incremental && this.#loadCache) { + for (const file of this.#loadCache.watchFiles) { + if (!isInternalAngularFile(file)) { + this.watchFiles.add(isAbsolute(file) ? normalize(file) : join(this.workspaceRoot, file)); } } } @@ -446,19 +482,72 @@ export class BundlerContext { * to be stored. * @returns True, if the result was invalidated; False, otherwise. */ - invalidate(files: Iterable): boolean { + invalidate(files: Iterable | ReadonlySet): boolean { if (!this.incremental) { return false; } + let candidateFiles: ReadonlySet; + if (files instanceof Set) { + let isCandidateReady = true; + for (const file of files) { + if ( + file !== normalize(file) || + (!isAbsolute(file) && !files.has(normalize(join(this.workspaceRoot, file)))) + ) { + isCandidateReady = false; + break; + } + } + + if (isCandidateReady) { + candidateFiles = files; + } else { + const normalizedFiles = new Set(); + for (const file of files) { + const normalized = normalize(file); + normalizedFiles.add(normalized); + if (!isAbsolute(normalized)) { + normalizedFiles.add(normalize(join(this.workspaceRoot, normalized))); + } + } + candidateFiles = normalizedFiles; + } + } else { + const normalizedFiles = new Set(); + for (const file of files) { + const normalized = normalize(file); + normalizedFiles.add(normalized); + if (!isAbsolute(normalized)) { + normalizedFiles.add(normalize(join(this.workspaceRoot, normalized))); + } + } + candidateFiles = normalizedFiles; + } + let invalid = false; - for (const file of files) { + for (const file of candidateFiles) { if (this.#loadCache?.invalidate(file)) { invalid = true; - continue; } + } - invalid ||= this.watchFiles.has(file); + if (!invalid) { + if (this.watchFiles.size < candidateFiles.size) { + for (const file of this.watchFiles) { + if (candidateFiles.has(file)) { + invalid = true; + break; + } + } + } else { + for (const file of candidateFiles) { + if (this.watchFiles.has(file)) { + invalid = true; + break; + } + } + } } if (invalid) { diff --git a/packages/angular/build/src/tools/esbuild/bundler-context_spec.ts b/packages/angular/build/src/tools/esbuild/bundler-context_spec.ts new file mode 100644 index 000000000000..213354389122 --- /dev/null +++ b/packages/angular/build/src/tools/esbuild/bundler-context_spec.ts @@ -0,0 +1,113 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { BundlerContext } from './bundler-context'; +import { MemoryLoadResultCache } from './load-result-cache'; + +describe('BundlerContext', () => { + describe('invalidate', () => { + it('should return false when incremental is disabled', () => { + const context = new BundlerContext('/workspace', /* incremental */ false, () => ({ + entryPoints: ['main.js'], + })); + context.watchFiles.add('/workspace/src/app.css'); + + expect(context.invalidate(['/workspace/src/app.css'])).toBeFalse(); + }); + + it('should return true when a watch file matches changed files as an array', () => { + const context = new BundlerContext('/workspace', /* incremental */ true, () => ({ + entryPoints: ['main.js'], + })); + context.watchFiles.add('/workspace/src/app.css'); + + expect(context.invalidate(['/workspace/src/app.css'])).toBeTrue(); + }); + + it('should return true when a watch file matches changed files as a ReadonlySet', () => { + const context = new BundlerContext('/workspace', /* incremental */ true, () => ({ + entryPoints: ['main.js'], + })); + context.watchFiles.add('/workspace/src/app.css'); + + const changedSet: ReadonlySet = new Set(['/workspace/src/app.css']); + expect(context.invalidate(changedSet)).toBeTrue(); + }); + + it('should return false when changed files do not intersect with watchFiles', () => { + const context = new BundlerContext('/workspace', /* incremental */ true, () => ({ + entryPoints: ['main.js'], + })); + context.watchFiles.add('/workspace/src/app.css'); + + expect(context.invalidate(['/workspace/src/other.css'])).toBeFalse(); + }); + + it('should correctly handle relative changed file paths', () => { + const context = new BundlerContext('/workspace', /* incremental */ true, () => ({ + entryPoints: ['main.js'], + })); + context.watchFiles.add('/workspace/src/app.css'); + + expect(context.invalidate(['src/app.css'])).toBeTrue(); + }); + + it('should correctly handle relative paths inside a ReadonlySet', () => { + const context = new BundlerContext('/workspace', /* incremental */ true, () => ({ + entryPoints: ['main.js'], + })); + context.watchFiles.add('/workspace/src/app.css'); + + const set: ReadonlySet = new Set(['src/app.css']); + expect(context.invalidate(set)).toBeTrue(); + }); + + it('should invalidate shared load cache when files change', async () => { + const loadCache = new MemoryLoadResultCache(); + await loadCache.put('file:/workspace/src/app.css', { + contents: 'body {}', + loader: 'css', + watchFiles: ['/workspace/src/app.css'], + }); + + const context = new BundlerContext( + '/workspace', + /* incremental */ true, + () => ({ entryPoints: ['main.js'] }), + /* useContext */ false, + /* initialFilter */ undefined, + loadCache, + ); + + expect(loadCache.get('file:/workspace/src/app.css')).toBeDefined(); + expect(context.invalidate(['/workspace/src/app.css'])).toBeTrue(); + expect(loadCache.get('file:/workspace/src/app.css')).toBeUndefined(); + }); + + it('should work when watchFiles is smaller than changed files', () => { + const context = new BundlerContext('/workspace', /* incremental */ true, () => ({ + entryPoints: ['main.js'], + })); + context.watchFiles.add('/workspace/src/file10.css'); + + const changedFiles = Array.from({ length: 100 }, (_, i) => `/workspace/src/file${i}.css`); + expect(context.invalidate(new Set(changedFiles))).toBeTrue(); + }); + + it('should work when changed files is smaller than watchFiles', () => { + const context = new BundlerContext('/workspace', /* incremental */ true, () => ({ + entryPoints: ['main.js'], + })); + for (let i = 0; i < 100; i++) { + context.watchFiles.add(`/workspace/src/file${i}.css`); + } + + expect(context.invalidate(new Set(['/workspace/src/file50.css']))).toBeTrue(); + }); + }); +}); diff --git a/packages/angular/build/src/tools/esbuild/bundler-files.ts b/packages/angular/build/src/tools/esbuild/bundler-files.ts index ac33d471a395..168c41e65940 100644 --- a/packages/angular/build/src/tools/esbuild/bundler-files.ts +++ b/packages/angular/build/src/tools/esbuild/bundler-files.ts @@ -7,7 +7,7 @@ */ import type { OutputFile } from 'esbuild'; -import { createHash } from 'node:crypto'; +import { calculateHash } from '../../utils/hash'; export interface InitialFileRecord { entrypoint: boolean; @@ -63,9 +63,7 @@ export function createOutputFile( return this.contents.byteLength; }, get hash(): string { - cachedHash ??= createHash('sha256') - .update(cachedText ?? this.contents) - .digest('hex'); + cachedHash ??= calculateHash(cachedText ?? this.contents); return cachedHash; }, @@ -97,7 +95,7 @@ export function createOutputFile( return cachedText; }, get hash(): string { - cachedHash ??= createHash('sha256').update(this.contents).digest('hex'); + cachedHash ??= calculateHash(this.contents); return cachedHash; }, diff --git a/packages/angular/build/src/tools/esbuild/cache.ts b/packages/angular/build/src/tools/esbuild/cache.ts index 2bb6afcb725f..5f1cd0d2927b 100644 --- a/packages/angular/build/src/tools/esbuild/cache.ts +++ b/packages/angular/build/src/tools/esbuild/cache.ts @@ -195,6 +195,15 @@ export class Cache = CacheStore> { await this.store.set(namespacedKey, value); } + /** + * Clears internal state for a specific namespaced key (requests, write counts, and pending gets). + */ + protected deleteInternal(namespacedKey: string): void { + this.#requests.delete(namespacedKey); + this.#writeCounts.delete(namespacedKey); + this.#pendingGets.delete(namespacedKey); + } + /** * Clears the base class internal state (requests, write counts, and pending gets). */ @@ -213,6 +222,18 @@ export class MemoryCache extends Cache> { super(new Map()); } + /** + * Removes the specified key from the cache instance. + * @param key The key to remove. + * @returns True if an element in the Map existed and has been removed, or false if the element does not exist. + */ + delete(key: string): boolean { + const namespacedKey = this.withNamespace(key); + this.deleteInternal(namespacedKey); + + return this.store.delete(namespacedKey); + } + /** * Removes all entries from the cache instance. */ diff --git a/packages/angular/build/src/tools/esbuild/cache_spec.ts b/packages/angular/build/src/tools/esbuild/cache_spec.ts index a30852836eff..a81bfbd25acd 100644 --- a/packages/angular/build/src/tools/esbuild/cache_spec.ts +++ b/packages/angular/build/src/tools/esbuild/cache_spec.ts @@ -141,4 +141,21 @@ describe('MemoryCache', () => { const val2 = await cache.getOrCreate('key', () => 'should-not-run'); expect(val2).toBe('override-value'); }); + + it('should delete a cached key and allow creating a new value', async () => { + await cache.put('key', 'value-1'); + expect(await cache.get('key')).toBe('value-1'); + + const deleted = cache.delete('key'); + expect(deleted).toBeTrue(); + expect(await cache.get('key')).toBeUndefined(); + + // Subsequent getOrCreate should call creator + const newValue = await cache.getOrCreate('key', () => 'value-2'); + expect(newValue).toBe('value-2'); + }); + + it('should return false when deleting a non-existent key', () => { + expect(cache.delete('non-existent')).toBeFalse(); + }); }); diff --git a/packages/angular/build/src/tools/esbuild/i18n-inliner-worker.ts b/packages/angular/build/src/tools/esbuild/i18n-inliner-worker.ts index bfec753f6b2d..af9d9dce134b 100644 --- a/packages/angular/build/src/tools/esbuild/i18n-inliner-worker.ts +++ b/packages/angular/build/src/tools/esbuild/i18n-inliner-worker.ts @@ -7,11 +7,14 @@ */ import remapping, { type DecodedSourceMap, type SourceMapInput } from '@ampproject/remapping'; +import type { Node } from '@oxc-project/types'; import { MagicString } from 'magic-string'; import assert from 'node:assert'; import { deserialize } from 'node:v8'; import { workerData } from 'node:worker_threads'; -import { Visitor, parseSync } from 'oxc-parser'; +import { parseSync, visitorKeys } from 'oxc-parser'; +import { loadLocaleData } from './i18n-locale-plugin'; +import { createSharedTranslationProxy } from './i18n-translation-reader'; /** * The options passed to the inliner for each file request @@ -30,10 +33,10 @@ interface InlineFileRequest { /** * The serialized translation messages for the locale that should be used during the inlining - * process of the file. A Blob is used so that the messages are shared with the Worker by - * reference instead of being copied into it for every request. + * process of the file. A SharedArrayBuffer or Blob is used so that the messages are shared with + * the Worker by reference instead of being copied into it for every request. */ - translation?: Blob; + translation?: Blob | SharedArrayBuffer; } /** @@ -57,52 +60,152 @@ interface InlineCodeRequest { /** * The serialized translation messages for the locale that should be used during the inlining - * process of the file. A Blob is used so that the messages are shared with the Worker by - * reference instead of being copied into it for every request. + * process of the file. A SharedArrayBuffer or Blob is used so that the messages are shared with + * the Worker by reference instead of being copied into it for every request. */ - translation?: Blob; + translation?: Blob | SharedArrayBuffer; +} + +/** + * The options passed to the inliner for a batch file request + */ +interface InlineFileBatchRequest { + /** + * The filename that should be processed. The data for the file is provided to the Worker + * during Worker initialization. + */ + filename: string; + + /** + * The locale specifiers or locale objects that should be used during the inlining process of the file. + */ + locales: (string | { locale: string; translation?: Blob | SharedArrayBuffer })[]; + + /** + * Whether the file data should be treated as ephemeral and not cached long-term in the Worker. + * Typically true when all remaining locales for the file are processed in a single batch. + */ + ephemeral?: boolean; + + /** + * The list of active locales in the current inlining window. Any cached translation dictionaries + * not present in this list will be evicted from the Worker's memory cache. + */ + activeLocales?: string[]; +} + +/** + * The result for a single locale within a batch file request. + */ +interface InlineLocaleResult { + locale: string; + code: string; + map?: string; + messages: { type: 'error' | 'warning'; message: string }[]; +} + +/** + * The response returned from a batch file request. + */ +interface InlineFileBatchResult { + file: string; + results: InlineLocaleResult[]; } // Extract the application files and common options used for inline requests from the Worker context -const { files, missingTranslation, shouldOptimize } = (workerData || {}) as { +const { files, missingTranslation, translations } = (workerData || {}) as { files: ReadonlyMap; missingTranslation: 'error' | 'warning' | 'ignore'; - shouldOptimize: boolean; + translations?: ReadonlyMap; }; /** - * The translation messages deserialized for the locale most recently requested of this Worker. - * Locales are inlined one at a time, so retaining only the active locale is enough to avoid - * deserializing the messages once per file while holding at most one set of messages in memory. + * Cached file data including code and extracted localization metadata. */ -let activeTranslation: { locale: string; messages: Promise> } | undefined; +interface CachedFileData { + code: string; + metadata: FileLocalizeMetadata; +} + +/** + * Cache of file data promises keyed by filename. + */ +const fileDataCache = new Map>(); + +/** + * Cache of deserialized translation messages keyed by locale. + */ +const deserializedTranslations = new Map>>(); + +/** + * Retrieves the file data for a filename, loading and extracting localization metadata. + * If `cache` is true, the result is cached in `fileDataCache` across requests in this Worker. + * If `cache` is false (ephemeral), the result is not retained in `fileDataCache`, allowing it + * to be garbage-collected once the batch request finishes. + * + * @param filename The name of the file to load. + * @param cache Whether to cache the loaded file data in the Worker's long-term cache. + * @returns The cached or newly extracted code and localization metadata. + */ +function loadFileData(filename: string, cache = true): Promise { + const existing = fileDataCache.get(filename); + if (existing) { + return existing; + } + + const fileDataPromise = (async () => { + const data = files.get(filename); + assert(data !== undefined, `Invalid inline request for file '${filename}'.`); + + const code = await data.text(); + const metadata = extractLocalizeMetadata(filename, code); + + return { code, metadata }; + })(); + + if (cache) { + fileDataPromise.catch(() => { + fileDataCache.delete(filename); + }); + fileDataCache.set(filename, fileDataPromise); + } + + return fileDataPromise; +} /** - * Deserializes the translation messages for an inline request, reusing the result for any + * Deserializes or wraps the translation messages for a locale, reusing the result for any * subsequent request that targets the same locale. - * @param request An inline request containing the locale and its serialized messages. + * @param locale The locale identifier. + * @param translation Optional serialized translation messages (SharedArrayBuffer or Blob). * @returns The translation messages, or undefined if the locale has no translations. */ function loadTranslation( - request: InlineFileRequest | InlineCodeRequest, + locale: string, + translation?: Blob | SharedArrayBuffer, ): Promise> | undefined { - const { locale, translation } = request; - if (!translation) { + const translationData = translation ?? translations?.get(locale); + if (!translationData) { return undefined; } - if (activeTranslation?.locale !== locale) { - activeTranslation = { - locale, - // Deserializing within the stored promise ensures that concurrent requests for a locale - // share the one deserialization instead of each performing their own. - messages: translation + let messagesPromise = deserializedTranslations.get(locale); + if (!messagesPromise) { + if (translationData instanceof Blob) { + messagesPromise = translationData .arrayBuffer() - .then((buffer) => deserialize(new Uint8Array(buffer)) as Record), - }; + .then((buffer) => deserialize(new Uint8Array(buffer)) as Record) + .catch((error) => { + deserializedTranslations.delete(locale); + throw error; + }); + } else { + messagesPromise = Promise.resolve(createSharedTranslationProxy(translationData)); + } + deserializedTranslations.set(locale, messagesPromise); } - return activeTranslation.messages; + return messagesPromise; } /** @@ -113,17 +216,20 @@ function loadTranslation( * @returns An object containing the inlined file and optional map content. */ export default async function inlineFile(request: InlineFileRequest) { - const data = files.get(request.filename); + const { code, metadata } = await loadFileData(request.filename, true); - assert(data !== undefined, `Invalid inline request for file '${request.filename}'.`); + // Sourcemaps are parsed on demand per request rather than cached long-term to prevent + // monotonic memory growth as a worker processes multiple files across the build. + const rawMap = await files.get(request.filename + '.map')?.text(); + const map = rawMap ? (JSON.parse(rawMap) as SourceMapInput) : undefined; - const code = await data.text(); - const map = await files.get(request.filename + '.map')?.text(); - const result = await transformWithOxc( + const result = await inlineLocalize( code, - map && (JSON.parse(map) as SourceMapInput), - request, - await loadTranslation(request), + map, + metadata, + request.locale, + await loadTranslation(request.locale, request.translation), + request.filename, ); return { @@ -134,6 +240,59 @@ export default async function inlineFile(request: InlineFileRequest) { }; } +/** + * Inlines multiple locales and translations into a JavaScript file that contains `$localize` usage. + * + * @param request An InlineFileBatchRequest object representing the options for inlining. + * @returns An object containing the inlined results for each requested locale. + */ +export async function inlineFileBatch( + request: InlineFileBatchRequest, +): Promise { + if (request.activeLocales) { + const activeSet = new Set(request.activeLocales); + for (const locale of deserializedTranslations.keys()) { + if (!activeSet.has(locale)) { + deserializedTranslations.delete(locale); + } + } + } + + const { code, metadata } = await loadFileData(request.filename, !request.ephemeral); + + // Parse the sourcemap once for the entire batch. + // It will naturally be garbage-collected after this batch action returns. + const rawMap = await files.get(request.filename + '.map')?.text(); + const map = rawMap ? (JSON.parse(rawMap) as SourceMapInput) : undefined; + + const results = await Promise.all( + request.locales.map(async (entry) => { + const locale = typeof entry === 'string' ? entry : entry.locale; + const translation = typeof entry === 'string' ? undefined : entry.translation; + const result = await inlineLocalize( + code, + map, + metadata, + locale, + await loadTranslation(locale, translation), + request.filename, + ); + + return { + locale, + code: result.code, + map: result.map, + messages: result.diagnostics.messages, + }; + }), + ); + + return { + file: request.filename, + results, + }; +} + /** * Inlines the provided locale and translation into JavaScript code that contains `$localize` usage. * This function is a secondary entry primarily for use with component HMR update modules. @@ -142,11 +301,14 @@ export default async function inlineFile(request: InlineFileRequest) { * @returns An object containing the inlined code. */ export async function inlineCode(request: InlineCodeRequest) { - const result = await transformWithOxc( + const metadata = extractLocalizeMetadata(request.filename, request.code); + const result = await inlineLocalize( request.code, undefined, - request, - await loadTranslation(request), + metadata, + request.locale, + await loadTranslation(request.locale, request.translation), + request.filename, ); return { @@ -181,81 +343,219 @@ async function loadLocalizeTools(): Promise { } /** - * Transforms a JavaScript file using OXC and Magic-String to inline the request locale and translation. - * @param code A string containing the JavaScript code to transform. - * @param map A sourcemap object for the provided JavaScript code. - * @param options The inline request options to use. - * @param translation The translation messages to inline, or undefined for an untranslated locale. - * @returns An object containing the code, map, and diagnostics from the transformation. + * Traverses ESTree AST nodes in post-order (bottom-up) without recursion. + * Bottom-up traversal ensures that nested `$localize` expressions are transformed and + * written to MagicString before outer containing templates are evaluated. + * + * @param root The root AST node to traverse. + * @param onExit Callback invoked on each AST node in post-order. */ -async function transformWithOxc( - code: string, - map: SourceMapInput | undefined, - options: InlineFileRequest, - translation: Record | undefined, -) { - const { program } = parseSync(options.filename, code, { +function walkAstPostOrder(root: Node, onExit: (node: Node) => void): void { + const traverseStack: Node[] = [root]; + const postOrderNodes: Node[] = []; + + while (traverseStack.length > 0) { + const current = traverseStack.pop(); + if (!current) { + continue; + } + + postOrderNodes.push(current); + + const keys = visitorKeys[current.type]; + if (!keys) { + continue; + } + + for (let i = 0; i < keys.length; i++) { + const child = (current as unknown as Record)[keys[i]]; + if (!child) { + continue; + } + + if (Array.isArray(child)) { + for (const item of child) { + if (item) { + traverseStack.push(item); + } + } + } else { + traverseStack.push(child); + } + } + } + + // Process collected nodes in reverse order to achieve bottom-up (post-order) traversal + for (let i = postOrderNodes.length - 1; i >= 0; i--) { + onExit(postOrderNodes[i]); + } +} + +/** + * Metadata for a `$localize` tagged template expression extracted from the AST. + */ +interface LocalizeCallSite { + start: number; + end: number; + messageParts: TemplateStringsArray; + expressions: { start: number; end: number }[]; +} + +/** + * Metadata extracted from a JavaScript file AST needed for localization inlining. + */ +interface FileLocalizeMetadata { + callSites: LocalizeCallSite[]; + localeInsertSites: { start: number; end: number }[]; + diagnostics?: string[]; +} + +/** + * Extracts localization call sites and locale insertion points from JavaScript code using OXC. + * + * @param filename The name of the file being processed. + * @param code The JavaScript source code. + * @returns The extracted localization metadata. + */ +function extractLocalizeMetadata(filename: string, code: string): FileLocalizeMetadata { + const { program } = parseSync(filename, code, { sourceType: 'unambiguous', }); if (!program) { - throw new Error(`Unknown error occurred parsing file "${options.filename}" with OXC.`); + throw new Error(`Unknown error occurred parsing file "${filename}" with OXC.`); } - const magicString = new MagicString(code); - const { Diagnostics, translate } = await loadLocalizeTools(); - const diagnostics = new Diagnostics(); + const callSites: LocalizeCallSite[] = []; + const localeInsertSites: { start: number; end: number }[] = []; + let diagnostics: string[] | undefined; - const visitor = new Visitor({ - Literal(node) { + walkAstPostOrder(program, (node) => { + if (node.type === 'Literal') { if (typeof node.value === 'string' && node.value === '___NG_LOCALE_INSERT___') { - magicString.overwrite(node.start, node.end, JSON.stringify(options.locale)); + localeInsertSites.push({ start: node.start, end: node.end }); } - }, - 'TaggedTemplateExpression:exit'(node) { + } else if (node.type === 'TaggedTemplateExpression') { if (node.tag.type === 'Identifier' && node.tag.name === '$localize') { - const cooked = node.quasi.quasis.map((q) => q.value.cooked); - const raw = node.quasi.quasis.map((q) => q.value.raw); - const messageParts = Object.assign(cooked, { raw }) as unknown as TemplateStringsArray; - - const [translatedParts, translatedSubstitutions] = translate( - diagnostics, - translation || {}, - messageParts, - node.quasi.expressions.map((_, index) => index), - translation === undefined ? 'ignore' : missingTranslation, - ); - - // Reconstruct the new template/string literal replacement - let replacement: string; - if (translatedSubstitutions.length === 0) { - replacement = JSON.stringify(translatedParts[0]); - } else { - replacement = '`'; - for (let i = 0; i < translatedParts.length; i++) { - const escapedPart = JSON.stringify(translatedParts[i]) - .slice(1, -1) - .replace(/\\"/g, '"') - .replace(/`/g, '\\`') - .replace(/\$\{/g, '\\${'); - replacement += escapedPart; - - if (i < translatedSubstitutions.length) { - const originalIndex = translatedSubstitutions[i]; - const exprNode = node.quasi.expressions[originalIndex]; - const exprCode = magicString.slice(exprNode.start, exprNode.end); - replacement += '${' + exprCode + '}'; - } + const cooked: string[] = []; + const raw: string[] = []; + let hasMalformedEscape = false; + + for (const q of node.quasi.quasis) { + if (q.value.cooked === null || q.value.cooked === undefined) { + hasMalformedEscape = true; + (diagnostics ??= []).push( + `Malformed escape sequence in $localize template literal in file "${filename}".`, + ); + break; } - replacement += '`'; + cooked.push(q.value.cooked); + raw.push(q.value.raw); } - magicString.overwrite(node.start, node.end, replacement); + if (!hasMalformedEscape) { + const messageParts = Object.assign(cooked, { raw }); + const expressions = node.quasi.expressions.map((expr) => ({ + start: expr.start, + end: expr.end, + })); + + callSites.push({ + start: node.start, + end: node.end, + messageParts, + expressions, + }); + } } - }, + } }); - visitor.visit(program); + return { callSites, localeInsertSites, diagnostics }; +} + +/** + * Inlines translations into code using previously extracted localization metadata. + * + * @param code The source code to transform. + * @param map Optional source map for the source code. + * @param metadata Extracted localization metadata. + * @param locale The target locale identifier. + * @param translation The translation messages dictionary, or undefined for untranslated locale. + * @param filename The name of the file being transformed. + * @returns The transformed code, optional remapped source map, and diagnostics. + */ +async function inlineLocalize( + code: string, + map: SourceMapInput | undefined, + metadata: FileLocalizeMetadata, + locale: string, + translation: Record | undefined, + filename: string, +) { + const magicString = new MagicString(code); + const { Diagnostics, translate } = await loadLocalizeTools(); + const diagnostics = new Diagnostics(); + + if (metadata.diagnostics) { + for (const message of metadata.diagnostics) { + diagnostics.error(message); + } + } + + if (metadata.localeInsertSites.length > 0) { + const localeData = await loadLocaleData(locale); + if (localeData.error) { + diagnostics.error(localeData.error); + } else if (localeData.warning) { + diagnostics.warn(localeData.warning); + } + let injected = false; + for (const site of metadata.localeInsertSites) { + magicString.overwrite( + site.start, + site.end, + JSON.stringify(locale) + (localeData.code && !injected ? `;\n${localeData.code}\n;` : ''), + ); + injected = true; + } + } + + for (const callSite of metadata.callSites) { + const [translatedParts, translatedSubstitutions] = translate( + diagnostics, + translation || {}, + callSite.messageParts, + callSite.expressions.map((_, index) => index), + translation === undefined ? 'ignore' : missingTranslation, + ); + + // Reconstruct the new template/string literal replacement + let replacement: string; + if (translatedSubstitutions.length === 0) { + replacement = JSON.stringify(translatedParts[0]); + } else { + replacement = '`'; + for (let i = 0; i < translatedParts.length; i++) { + const escapedPart = JSON.stringify(translatedParts[i]) + .slice(1, -1) + .replace(/\\"/g, '"') + .replace(/`/g, '\\`') + .replace(/\$\{/g, '\\${'); + replacement += escapedPart; + + if (i < translatedSubstitutions.length) { + const originalIndex = translatedSubstitutions[i]; + const expr = callSite.expressions[originalIndex]; + const exprCode = magicString.slice(expr.start, expr.end); + replacement += '${' + exprCode + '}'; + } + } + replacement += '`'; + } + + magicString.overwrite(callSite.start, callSite.end, replacement); + } const outputCode = magicString.toString(); let outputMap; @@ -264,7 +564,7 @@ async function transformWithOxc( // inputs. Encoding the mappings only for remapping to immediately decode them again doubles // the peak memory of the largest structure involved in inlining a file. const rawMap = magicString.generateDecodedMap({ - source: options.filename, + source: filename, includeContent: true, hires: 'boundary', }); diff --git a/packages/angular/build/src/tools/esbuild/i18n-inliner.ts b/packages/angular/build/src/tools/esbuild/i18n-inliner.ts index 071097315e5b..41de55de666f 100644 --- a/packages/angular/build/src/tools/esbuild/i18n-inliner.ts +++ b/packages/angular/build/src/tools/esbuild/i18n-inliner.ts @@ -7,12 +7,13 @@ */ import assert from 'node:assert'; -import { createHash } from 'node:crypto'; import { extname, join } from 'node:path'; import { serialize } from 'node:v8'; +import { calculateHash, createContentHash, initializeHash } from '../../utils/hash'; import { WorkerPool } from '../../utils/worker-pool'; import { type BuildOutputFile, BuildOutputFileType, createOutputFile } from './bundler-files'; -import { type PersistentCacheStore, createPersistentCacheStore } from './cache'; +import { type Cache, type PersistentCacheStore, createPersistentCacheStore } from './cache'; +import { encodeTranslationToBuffer } from './i18n-translation-encoder'; /** * A keyword used to indicate if a JavaScript file may require inlining of translations. @@ -20,18 +21,34 @@ import { type PersistentCacheStore, createPersistentCacheStore } from './cache'; */ const LOCALIZE_KEYWORD = '$localize'; +/** + * The maximum number of locales to process concurrently in a single sliding window. + * This caps peak worker memory while maintaining multi-locale batching throughput. + */ +const DEFAULT_LOCALE_WINDOW_SIZE = 8; + /** * Serializes the translation messages for a locale for transfer to an inliner Worker. * - * A Blob is used because cloning one shares its data by reference, whereas sending the messages - * themselves copies them into a Worker for every request that carries them. A locale can contain - * tens of thousands of messages, which makes that copy the dominant cost of inlining a locale. + * A SharedArrayBuffer is preferred because it enables zero-copy shared memory access + * and on-demand string decoding across all worker threads. Falls back to a Blob when + * SharedArrayBuffer is unavailable. * * @param translation The translation messages for a locale, if the locale has any. - * @returns A Blob containing the serialized messages, or undefined if the locale has none. + * @returns A SharedArrayBuffer or Blob containing the serialized messages, or undefined if none. */ -function serializeTranslation(translation: Record | undefined): Blob | undefined { - return translation && new Blob([serialize(translation)]); +function serializeTranslation( + translation: Record | undefined, +): SharedArrayBuffer | Blob | undefined { + if (!translation) { + return undefined; + } + + if (typeof SharedArrayBuffer !== 'undefined') { + return encodeTranslationToBuffer(translation); + } + + return new Blob([serialize(translation)]); } /** @@ -42,6 +59,81 @@ export interface I18nInlinerOptions { outputFiles: BuildOutputFile[]; shouldOptimize?: boolean; persistentCachePath?: string; + localizeVersion?: string; + translations?: ReadonlyMap; +} + +/** + * Options for inlining a specific locale. + */ +export interface LocaleInlineOptions { + /** + * The locale specifier string. + */ + locale: string; + + /** + * The translation messages for the locale, or undefined for the source/untranslated locale. + */ + translation?: Record; + + /** + * An optional content integrity hash of the translation file(s) for fast cache key calculation. + */ + translationIntegrity?: string; +} + +/** + * Result of inlining for a specific locale. + */ +export interface LocaleInlineResult { + outputFiles: BuildOutputFile[]; + errors: string[]; + warnings: string[]; +} + +/** + * Transformation result for a single file and locale combination. + */ +interface TransformedFileResult { + file: string; + code: string; + map?: string; + messages: { type: 'error' | 'warning'; message: string }[]; +} + +/** + * Represents an in-flight asynchronous cache lookup for a single (file x locale) transformation. + */ +interface CacheCheckItem { + /** + * The relative file path of the JavaScript file to transform. + */ + filename: string; + + /** + * The locale specifier being targeted for translation. + */ + locale: string; + + /** + * The computed cache key hash, or undefined if persistent caching is not configured. + */ + cacheKey: string | undefined; + + /** + * A promise that resolves to the cached transform result, or null if uncached or on lookup failure. + */ + cachedResult: Promise; +} + +/** + * An uncached transformation request entry for a file within a specific locale. + */ +interface UncachedLocaleEntry { + locale: string; + cacheKey?: string; + translation?: Blob | SharedArrayBuffer; } /** @@ -53,7 +145,8 @@ export interface I18nInlinerOptions { export class I18nInliner { #cacheInitFailed = false; #workerPool: WorkerPool; - #cache: PersistentCacheStore | undefined; + #cacheStore: PersistentCacheStore | undefined; + #cache: Cache | undefined; readonly #localizeFiles: ReadonlyMap; readonly #unmodifiedFiles: Array; @@ -62,7 +155,7 @@ export class I18nInliner { maxThreads?: number, ) { this.#unmodifiedFiles = []; - const { outputFiles, shouldOptimize, missingTranslation } = options; + const { outputFiles, shouldOptimize, missingTranslation, translations } = options; const files = new Map(); const pendingMaps = []; @@ -114,6 +207,7 @@ export class I18nInliner { workerData: { missingTranslation, shouldOptimize, + translations, // A Blob is an immutable data structure that allows sharing the data between workers // without copying until the data is actually used within a Worker. This is useful here // since each file may not actually be processed in each Worker and the Blob avoids @@ -126,113 +220,272 @@ export class I18nInliner { } /** - * Performs inlining of translations for the provided locale and translations. The files that - * are processed originate from the files passed to the class constructor and filter by presence - * of the localize function keyword. - * @param locale The string representing the locale to inline. - * @param translation The translation messages to use when inlining. - * @returns A promise that resolves to an array of OutputFiles representing a translated result. + * Performs inlining of translations across multiple locales in parallel. + * + * An adaptive 2D task-partitioning algorithm distributes (files x locales) work units + * across all worker threads while caching AST metadata and sourcemaps in worker memory. + * + * @param locales The locales and translations to inline. + * @returns A map of locale names to their inlined output files and diagnostics. */ - async inlineForLocale( - locale: string, - translation: Record | undefined, - ): Promise<{ outputFiles: BuildOutputFile[]; errors: string[]; warnings: string[] }> { + async inlineAll( + locales: Iterable, + ): Promise> { await this.initCache(); - const { shouldOptimize, missingTranslation } = this.options; + const { shouldOptimize, missingTranslation, localizeVersion } = this.options; + const localeList = Array.from(locales); - // Serialized once here and then shared by the request for every file of this locale - const translationBlob = serializeTranslation(translation); + if (localeList.length === 0) { + return new Map(); + } - // Request inlining for each file that contains localize calls - const requests = []; + const fileResultsByLocale = new Map>(); + for (const { locale } of localeList) { + fileResultsByLocale.set(locale, new Map()); + } - let fileCacheKeyBase: Uint8Array | undefined; + const filenames = Array.from(this.#localizeFiles.keys()).filter( + (name) => !name.endsWith('.map'), + ); - for (const [filename, file] of this.#localizeFiles) { - let cacheKey: string | undefined; - if (filename.endsWith('.map')) { - continue; + // Process locales in sliding windows to cap peak worker memory + for (let i = 0; i < localeList.length; i += DEFAULT_LOCALE_WINDOW_SIZE) { + const windowLocales = localeList.slice(i, i + DEFAULT_LOCALE_WINDOW_SIZE); + const activeLocales = windowLocales.map((item) => item.locale); + const isLastWindow = i + DEFAULT_LOCALE_WINDOW_SIZE >= localeList.length; + + // Pre-calculate cache key bases and serialized Blobs for each locale in this window + const localeCacheBases = new Map(); + const localeBlobs = new Map(); + + for (const { locale, translation, translationIntegrity } of windowLocales) { + localeBlobs.set(locale, serializeTranslation(translation)); + + if (this.#cacheStore) { + localeCacheBases.set( + locale, + calculateHash( + JSON.stringify({ + locale, + translation: translationIntegrity || translation, + missingTranslation, + shouldOptimize, + localizeVersion, + }), + ), + ); + } } - let cacheResultPromise = Promise.resolve(null); - if (this.#cache) { - // The options are digested here so that each file's key is derived from a fixed number - // of bytes. Hashing the options directly would re-hash the full set of messages, which - // can be several megabytes, once for every file. - fileCacheKeyBase ??= createHash('sha256') - .update(JSON.stringify({ locale, translation, missingTranslation, shouldOptimize })) - .digest(); - - // NOTE: If additional options are added, this may need to be updated. - // TODO: Consider xxhash or similar instead of SHA256 - cacheKey = createHash('sha256') - .update(file.hash) - .update(filename) - .update(fileCacheKeyBase) - .digest('hex'); - - // Failure to get the value should not fail the transform - cacheResultPromise = this.#cache.get(cacheKey).catch(() => null); + const cacheChecks: CacheCheckItem[] = []; + + for (const filename of filenames) { + const file = this.#localizeFiles.get(filename); + assert(file !== undefined, 'Localize file must exist: ' + filename); + + for (const { locale } of windowLocales) { + let cacheKey: string | undefined; + let cachedResultPromise: Promise = Promise.resolve(null); + + if (this.#cache) { + const fileCacheKeyBase = localeCacheBases.get(locale); + assert(fileCacheKeyBase !== undefined, 'Cache base must exist for locale: ' + locale); + + const hasher = createContentHash(); + hasher.update(file.hash); + hasher.update(filename); + hasher.update(fileCacheKeyBase); + cacheKey = hasher.digest(); + + cachedResultPromise = this.#cache + .get(cacheKey) + .then((val) => val ?? null) + .catch(() => null); + } + + cacheChecks.push({ + filename, + locale, + cacheKey, + cachedResult: cachedResultPromise, + }); + } } - const fileResult = cacheResultPromise.then(async (cachedResult) => { - if (cachedResult) { - return cachedResult; + // Await all cache checks for this window + const resolvedChecks = await Promise.all( + cacheChecks.map(async (item) => ({ + ...item, + result: await item.cachedResult, + })), + ); + + // Group uncached items by filename for this window + const uncachedByFile = new Map(); + + for (const item of resolvedChecks) { + if (item.result) { + // Cache hit: store directly in locale file results + fileResultsByLocale.get(item.locale)?.set(item.filename, item.result); + } else { + // Cache miss: needs worker processing + let fileEntries = uncachedByFile.get(item.filename); + if (!fileEntries) { + fileEntries = []; + uncachedByFile.set(item.filename, fileEntries); + } + fileEntries.push({ + locale: item.locale, + cacheKey: item.cacheKey, + translation: localeBlobs.get(item.locale), + }); } + } + + // Adaptive 2D Sharding for uncached tasks in this window + if (uncachedByFile.size > 0) { + await this.#processUncachedBatches( + uncachedByFile, + windowLocales.length, + fileResultsByLocale, + activeLocales, + isLastWindow, + ); + } + } + + // Assemble final results in deterministic order per locale + const resultsByLocale = new Map(); + + for (const { locale } of localeList) { + const fileResults = fileResultsByLocale.get(locale); + const outputFiles: BuildOutputFile[] = []; + const errors: string[] = []; + const warnings: string[] = []; - const result = await this.#workerPool.run({ - filename, - locale, - translation: translationBlob, - }); - if (this.#cache && cacheKey) { - try { - // Failure to set the value should not fail the transform - await this.#cache.set(cacheKey, result); - } catch {} + if (fileResults) { + for (const filename of filenames) { + const fileResult = fileResults.get(filename); + if (!fileResult) { + continue; + } + + const type = this.#localizeFiles.get(filename)?.type; + assert(type !== undefined, 'localized file should always have a type: ' + filename); + + outputFiles.push(createOutputFile(filename, fileResult.code, type)); + if (fileResult.map) { + outputFiles.push(createOutputFile(filename + '.map', fileResult.map, type)); + } + + for (const message of fileResult.messages) { + if (message.type === 'error') { + errors.push(message.message); + } else { + warnings.push(message.message); + } + } } + } - return result; - }); + // Include cloned unmodified files for every locale + outputFiles.push(...this.#unmodifiedFiles.map((file) => file.clone())); - requests.push(fileResult); + resultsByLocale.set(locale, { + outputFiles, + errors, + warnings, + }); } - // Wait for all file requests to complete - const rawResults = await Promise.all(requests); + return resultsByLocale; + } - // Convert raw results to output file objects and include all unmodified files - const errors: string[] = []; - const warnings: string[] = []; - const outputFiles = [ - ...rawResults.flatMap(({ file, code, map, messages }) => { - const type = this.#localizeFiles.get(file)?.type; - assert(type !== undefined, 'localized file should always have a type' + file); - - const resultFiles = [createOutputFile(file, code, type)]; - if (map) { - resultFiles.push(createOutputFile(file + '.map', map, type)); - } + async #processUncachedBatches( + uncachedByFile: Map, + localeCount: number, + fileResultsByLocale: Map>, + activeLocales?: string[], + isLastWindow = true, + ): Promise { + const workerCount = this.#workerPool.maxThreads || 1; + const targetTaskCount = Math.max(uncachedByFile.size, workerCount * 2); + const localesPerBatch = Math.max( + 1, + Math.ceil(localeCount / (targetTaskCount / (uncachedByFile.size || 1))), + ); - for (const message of messages) { - if (message.type === 'error') { - errors.push(message.message); - } else { - warnings.push(message.message); + const workerTasks: Promise[] = []; + + for (const [filename, entries] of uncachedByFile) { + const ephemeral = isLastWindow && entries.length <= localesPerBatch; + for (let i = 0; i < entries.length; i += localesPerBatch) { + const batchEntries = entries.slice(i, i + localesPerBatch); + const task = (async () => { + const batchResult = (await this.#workerPool.run( + { + filename, + locales: batchEntries.map((e) => ({ + locale: e.locale, + translation: e.translation, + })), + ephemeral, + activeLocales, + }, + { name: 'inlineFileBatch' }, + )) as { + file: string; + results: Array; + }; + + const cachePromises: Promise[] = []; + for (const res of batchResult.results) { + const matchingEntry = batchEntries.find((e) => e.locale === res.locale); + const cacheKey = matchingEntry?.cacheKey; + + if (this.#cache && cacheKey) { + cachePromises.push( + this.#cache.put(cacheKey, { + file: filename, + code: res.code, + map: res.map, + messages: res.messages, + }), + ); + } + + fileResultsByLocale.get(res.locale)?.set(filename, res); } - } + await Promise.allSettled(cachePromises); + })(); - return resultFiles; - }), - ...this.#unmodifiedFiles.map((file) => file.clone()), - ]; + workerTasks.push(task); + } + } - return { - outputFiles, - errors, - warnings, - }; + await Promise.all(workerTasks); + } + + /** + * Performs inlining of translations for the provided locale and translations. The files that + * are processed originate from the files passed to the class constructor and filter by presence + * of the localize function keyword. + * @param locale The string representing the locale to inline. + * @param translation The translation messages to use when inlining. + * @param translationIntegrity An optional integrity value for the translation messages to use for caching. + * @returns A promise that resolves to an array of OutputFiles representing a translated result. + */ + async inlineForLocale( + locale: string, + translation: Record | undefined, + translationIntegrity?: string, + ): Promise { + const results = await this.inlineAll([{ locale, translation, translationIntegrity }]); + const result = results.get(locale); + assert(result !== undefined, `Result for locale '${locale}' should be present.`); + + return result; } async inlineTemplateUpdate( @@ -283,7 +536,7 @@ export class I18nInliner { * @returns A void promise that resolves when closing is complete. */ async close(): Promise { - await Promise.allSettled([this.#cache?.close(), this.#workerPool.destroy()]); + await Promise.allSettled([this.#cacheStore?.close(), this.#workerPool.destroy()]); } /** @@ -293,7 +546,7 @@ export class I18nInliner { * @returns A promise that resolves once the cache initialization process is complete. */ private async initCache(): Promise { - if (this.#cache || this.#cacheInitFailed) { + if (this.#cacheStore || this.#cacheInitFailed) { return; } @@ -305,7 +558,12 @@ export class I18nInliner { // Initialize a persistent cache for i18n transformations. try { - this.#cache = await createPersistentCacheStore(join(persistentCachePath, 'angular-i18n')); + const [, cacheStore] = await Promise.all([ + initializeHash(), + createPersistentCacheStore(join(persistentCachePath, 'angular-i18n')), + ]); + this.#cacheStore = cacheStore; + this.#cache = cacheStore.createCache('transforms'); } catch { this.#cacheInitFailed = true; diff --git a/packages/angular/build/src/tools/esbuild/i18n-inliner_spec.ts b/packages/angular/build/src/tools/esbuild/i18n-inliner_spec.ts index e3e7320e391d..46d1cb4145d4 100644 --- a/packages/angular/build/src/tools/esbuild/i18n-inliner_spec.ts +++ b/packages/angular/build/src/tools/esbuild/i18n-inliner_spec.ts @@ -7,6 +7,10 @@ */ import { transform } from 'esbuild'; +import fs from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; +import { initializeHash } from '../../utils/hash'; import { type BuildOutputFile, BuildOutputFileType, createOutputFile } from './bundler-files'; import { I18nInliner } from './i18n-inliner'; @@ -40,6 +44,10 @@ function findFile(outputFiles: BuildOutputFile[], path: string): BuildOutputFile describe('I18nInliner', () => { let inliner: I18nInliner | undefined; + beforeAll(async () => { + await initializeHash(); + }); + // A single thread is used throughout so that every file of every locale is inlined by the same // Worker. Any translation state that a Worker retains between requests is then observable. function createInliner(outputFiles: BuildOutputFile[]): I18nInliner { @@ -48,6 +56,10 @@ describe('I18nInliner', () => { return inliner; } + beforeAll(async () => { + await initializeHash(); + }); + afterEach(async () => { await inliner?.close(); inliner = undefined; @@ -191,6 +203,81 @@ describe('I18nInliner', () => { }); }); + it('safely inlines translations containing special characters, quotes, and newlines', async () => { + const { outputFiles, errors, warnings } = await createInliner([ + browserFile('main.js', GREETING_SOURCE), + ]).inlineForLocale('fr', { + greeting: translationFor('Bonjour "mon ami" \\ \' \n '), + }); + + expect(errors).toEqual([]); + expect(warnings).toEqual([]); + expect(findFile(outputFiles, 'main.js').text).toBe( + 'export const greeting = "Bonjour \\"mon ami\\" \\\\ \' \\n ";\n', + ); + }); + + it('inlines translations containing placeholders', async () => { + const source = 'export const welcome = (name) => $localize`:@@welcome:Hello ${name}!`;\n'; + const { outputFiles, errors, warnings } = await createInliner([ + browserFile('main.js', source), + ]).inlineForLocale('fr', { + welcome: { + messageParts: ['Bonjour ', ' !'], + placeholderNames: ['PH'], + text: 'Bonjour {$PH} !', + }, + }); + + expect(errors).toEqual([]); + expect(warnings).toEqual([]); + expect(findFile(outputFiles, 'main.js').text).toBe( + 'export const welcome = (name) => `Bonjour ${name} !`;\n', + ); + }); + + it('inlines multiple localize calls within the same file', async () => { + const source = + 'export const a = $localize`:@@greeting:Hello`;\nexport const b = $localize`:@@farewell:Goodbye`;\n'; + const { outputFiles, errors, warnings } = await createInliner([ + browserFile('main.js', source), + ]).inlineForLocale('fr', { + greeting: translationFor('Bonjour'), + farewell: translationFor('Au revoir'), + }); + + expect(errors).toEqual([]); + expect(warnings).toEqual([]); + expect(findFile(outputFiles, 'main.js').text).toBe( + 'export const a = "Bonjour";\nexport const b = "Au revoir";\n', + ); + }); + + it('inlines translations across multiple files using multiple worker threads in parallel', async () => { + inliner = new I18nInliner( + { + missingTranslation: 'warning', + outputFiles: [ + browserFile('main.js', GREETING_SOURCE), + browserFile('chunk1.js', GREETING_SOURCE), + browserFile('chunk2.js', GREETING_SOURCE), + browserFile('chunk3.js', GREETING_SOURCE), + ], + }, + 4, + ); + + const { outputFiles, errors, warnings } = await inliner.inlineForLocale('fr', { + greeting: translationFor('Bonjour'), + }); + + expect(errors).toEqual([]); + expect(warnings).toEqual([]); + for (const name of ['main.js', 'chunk1.js', 'chunk2.js', 'chunk3.js']) { + expect(findFile(outputFiles, name).text).toBe('export const greeting = "Bonjour";\n'); + } + }); + it('leaves files without localize calls unmodified', async () => { const { outputFiles } = await createInliner([ browserFile('main.js', GREETING_SOURCE), @@ -199,4 +286,354 @@ describe('I18nInliner', () => { expect(findFile(outputFiles, 'other.js').text).toBe('export const answer = 42;\n'); }); + + it('inlines nested $localize calls in post-order', async () => { + const source = + 'export const msg = $localize`:@@outer:You selected ${$localize`:@@inner:Apple`} for delivery.`;\n'; + const { outputFiles, errors, warnings } = await createInliner([ + browserFile('main.js', source), + ]).inlineForLocale('fr', { + inner: translationFor('Pomme'), + outer: { + messageParts: ['Vous avez sélectionné ', ' pour la livraison.'], + placeholderNames: ['PH'], + text: 'Vous avez sélectionné {$PH} pour la livraison.', + }, + }); + + expect(errors).toEqual([]); + expect(warnings).toEqual([]); + expect(findFile(outputFiles, 'main.js').text).toBe( + 'export const msg = `Vous avez sélectionné ${"Pomme"} pour la livraison.`;\n', + ); + }); + + it('reports an error diagnostic when a $localize template has a malformed escape sequence', async () => { + const source = 'export const msg = $localize`:@@id:\\unicode:`;\n'; + const { errors } = await createInliner([browserFile('main.js', source)]).inlineForLocale( + 'fr', + {}, + ); + + expect(errors).toEqual([ + 'Malformed escape sequence in $localize template literal in file "main.js".', + ]); + }); + + it('inlines the translations of a locale when translationIntegrity is provided', async () => { + const { outputFiles, errors, warnings } = await createInliner([ + browserFile('main.js', GREETING_SOURCE), + ]).inlineForLocale('fr', { greeting: translationFor('Bonjour') }, 'sha256-test-integrity'); + + expect(errors).toEqual([]); + expect(warnings).toEqual([]); + expect(findFile(outputFiles, 'main.js').text).toContain('"Bonjour"'); + expect(findFile(outputFiles, 'main.js').text).not.toContain('$localize'); + }); + + it('inlines the translations of a locale when localizeVersion is configured in options', async () => { + inliner = new I18nInliner( + { + missingTranslation: 'warning', + outputFiles: [browserFile('main.js', GREETING_SOURCE)], + localizeVersion: '20.2.0', + }, + 1, + ); + + const { outputFiles, errors, warnings } = await inliner.inlineForLocale( + 'fr', + { greeting: translationFor('Bonjour') }, + 'sha256-test-integrity', + ); + + expect(errors).toEqual([]); + expect(warnings).toEqual([]); + expect(findFile(outputFiles, 'main.js').text).toContain('"Bonjour"'); + expect(findFile(outputFiles, 'main.js').text).not.toContain('$localize'); + }); + + it('inlines multiple locales in parallel via inlineAll', async () => { + const localeInliner = createInliner([ + browserFile('main.js', GREETING_SOURCE), + browserFile('chunk.js', GREETING_SOURCE), + browserFile('other.js', 'export const answer = 42;\n'), + ]); + + const results = await localeInliner.inlineAll([ + { locale: 'fr', translation: { greeting: translationFor('Bonjour') } }, + { locale: 'de', translation: { greeting: translationFor('Hallo') } }, + { locale: 'es', translation: { greeting: translationFor('Hola') } }, + { locale: 'en-US', translation: undefined }, + ]); + + expect(results.size).toBe(4); + + const fr = results.get('fr'); + expect(fr).toBeDefined(); + expect(fr?.errors).toEqual([]); + expect(fr?.warnings).toEqual([]); + expect(findFile(fr?.outputFiles ?? [], 'main.js').text).toContain('"Bonjour"'); + expect(findFile(fr?.outputFiles ?? [], 'chunk.js').text).toContain('"Bonjour"'); + expect(findFile(fr?.outputFiles ?? [], 'other.js').text).toBe('export const answer = 42;\n'); + + const de = results.get('de'); + expect(de).toBeDefined(); + expect(de?.errors).toEqual([]); + expect(findFile(de?.outputFiles ?? [], 'main.js').text).toContain('"Hallo"'); + + const es = results.get('es'); + expect(es).toBeDefined(); + expect(es?.errors).toEqual([]); + expect(findFile(es?.outputFiles ?? [], 'main.js').text).toContain('"Hola"'); + + const en = results.get('en-US'); + expect(en).toBeDefined(); + expect(en?.errors).toEqual([]); + expect(findFile(en?.outputFiles ?? [], 'main.js').text).toContain('"Hello"'); + }); + + it('inlines multiple locales with sourcemaps in parallel via inlineAll', async () => { + const { code, map } = await transform(GREETING_SOURCE, { + sourcefile: 'greeting.ts', + loader: 'ts', + sourcemap: 'external', + }); + + const localeInliner = createInliner([ + browserFile('main.js', code), + browserFile('main.js.map', map), + browserFile('other.js', 'export const answer = 42;\n'), + ]); + + const results = await localeInliner.inlineAll([ + { locale: 'fr', translation: { greeting: translationFor('Bonjour') } }, + { locale: 'de', translation: { greeting: translationFor('Hallo') } }, + { locale: 'en-US', translation: undefined }, + ]); + + expect(results.size).toBe(3); + + for (const [locale, greeting] of [ + ['fr', 'Bonjour'], + ['de', 'Hallo'], + ['en-US', 'Hello'], + ] as const) { + const localeResult = results.get(locale); + expect(localeResult).toBeDefined(); + expect(localeResult?.errors).toEqual([]); + expect(localeResult?.warnings).toEqual([]); + + const mainJs = findFile(localeResult?.outputFiles ?? [], 'main.js'); + expect(mainJs.text).toContain(`"${greeting}"`); + + const mainMap = findFile(localeResult?.outputFiles ?? [], 'main.js.map'); + const outputMap = JSON.parse(mainMap.text) as { + version: number; + sources: string[]; + mappings: string; + }; + expect(outputMap.version).toBe(3); + expect(outputMap.sources).toContain('greeting.ts'); + expect(outputMap.mappings.length).toBeGreaterThan(0); + + const otherJs = findFile(localeResult?.outputFiles ?? [], 'other.js'); + expect(otherJs.text).toBe('export const answer = 42;\n'); + } + }); + + it('inlines multiple locales with partial cache hits and misses via inlineAll', async () => { + const cacheDir = await fs.mkdtemp(path.join(os.tmpdir(), 'i18n-cache-test-')); + + try { + const initialInliner = new I18nInliner( + { + missingTranslation: 'warning', + outputFiles: [ + browserFile('main.js', GREETING_SOURCE), + browserFile('other.js', 'export const answer = 42;\n'), + ], + persistentCachePath: cacheDir, + }, + 2, + ); + + // Pre-populate cache for 'fr' + await initialInliner.inlineForLocale( + 'fr', + { greeting: translationFor('Bonjour') }, + 'integrity-fr-1', + ); + await initialInliner.close(); + + // Create new inliner with same cache path, inlining cached 'fr' alongside uncached 'de' and 'es' + inliner = new I18nInliner( + { + missingTranslation: 'warning', + outputFiles: [ + browserFile('main.js', GREETING_SOURCE), + browserFile('other.js', 'export const answer = 42;\n'), + ], + persistentCachePath: cacheDir, + }, + 2, + ); + + const results = await inliner.inlineAll([ + { + locale: 'fr', + translation: { greeting: translationFor('Bonjour') }, + translationIntegrity: 'integrity-fr-1', + }, + { + locale: 'de', + translation: { greeting: translationFor('Hallo') }, + translationIntegrity: 'integrity-de-1', + }, + { + locale: 'es', + translation: { greeting: translationFor('Hola') }, + translationIntegrity: 'integrity-es-1', + }, + ]); + + expect(results.size).toBe(3); + + const fr = results.get('fr'); + expect(fr?.errors).toEqual([]); + expect(findFile(fr?.outputFiles ?? [], 'main.js').text).toContain('"Bonjour"'); + expect(findFile(fr?.outputFiles ?? [], 'other.js').text).toBe('export const answer = 42;\n'); + + const de = results.get('de'); + expect(de?.errors).toEqual([]); + expect(findFile(de?.outputFiles ?? [], 'main.js').text).toContain('"Hallo"'); + + const es = results.get('es'); + expect(es?.errors).toEqual([]); + expect(findFile(es?.outputFiles ?? [], 'main.js').text).toContain('"Hola"'); + + // Verify deterministic file order matching input order + for (const localeResult of results.values()) { + expect(localeResult.outputFiles.map((f) => f.path)).toEqual(['main.js', 'other.js']); + } + } finally { + await fs.rm(cacheDir, { recursive: true, force: true }); + } + }); + + it('inlines across sliding windows when locale count exceeds window size', async () => { + const locales = Array.from({ length: 20 }, (_, i) => ({ + locale: `locale-${i}`, + translation: { greeting: translationFor(`Hello ${i}`) }, + })); + + const inliner = new I18nInliner( + { + missingTranslation: 'warning', + outputFiles: [browserFile('main.js', GREETING_SOURCE)], + }, + 2, + ); + + const results = await inliner.inlineAll(locales); + + expect(results.size).toBe(20); + for (let i = 0; i < 20; i++) { + const localeResult = results.get(`locale-${i}`); + expect(localeResult?.errors).toEqual([]); + expect(findFile(localeResult?.outputFiles ?? [], 'main.js').text).toContain(`"Hello ${i}"`); + } + }); + + it('injects locale data alongside ___NG_LOCALE_INSERT___ for non-English locales', async () => { + const source = `(globalThis.$localize ??= {}).locale = "___NG_LOCALE_INSERT___";\n${GREETING_SOURCE}`; + const localeInliner = createInliner([browserFile('polyfills.js', source)]); + + const results = await localeInliner.inlineAll([ + { locale: 'fr', translation: { greeting: translationFor('Bonjour') } }, + { locale: 'en-US', translation: undefined }, + ]); + + const fr = results.get('fr'); + expect(fr?.errors).toEqual([]); + const frPolyfills = findFile(fr?.outputFiles ?? [], 'polyfills.js').text; + expect(frPolyfills).toContain('(globalThis.$localize ??= {}).locale = "fr";'); + expect(frPolyfills).toContain('ng.common.locales'); + + const en = results.get('en-US'); + expect(en?.errors).toEqual([]); + const enPolyfills = findFile(en?.outputFiles ?? [], 'polyfills.js').text; + expect(enPolyfills).toContain('(globalThis.$localize ??= {}).locale = "en-US";'); + expect(enPolyfills).not.toContain('ng.common.locales'); + }); + + it('warns and uses parent locale data when a subtag locale is not directly available', async () => { + const source = `(globalThis.$localize ??= {}).locale = "___NG_LOCALE_INSERT___";\n${GREETING_SOURCE}`; + const localeInliner = createInliner([browserFile('polyfills.js', source)]); + + const results = await localeInliner.inlineAll([ + { locale: 'fr-ZZ', translation: { greeting: translationFor('Bonjour') } }, + ]); + + const frZZ = results.get('fr-ZZ'); + expect(frZZ?.errors).toEqual([]); + expect(frZZ?.warnings).toEqual([ + "Locale data for 'fr-ZZ' cannot be found. Using locale data for 'fr'.", + ]); + const polyfills = findFile(frZZ?.outputFiles ?? [], 'polyfills.js').text; + expect(polyfills).toContain('(globalThis.$localize ??= {}).locale = "fr-ZZ";'); + expect(polyfills).toContain('ng.common.locales'); + }); + + it('warns and includes no locale data when locale data cannot be found', async () => { + const source = `(globalThis.$localize ??= {}).locale = "___NG_LOCALE_INSERT___";\n${GREETING_SOURCE}`; + const localeInliner = createInliner([browserFile('polyfills.js', source)]); + + const results = await localeInliner.inlineAll([ + { locale: 'xx-YY', translation: { greeting: translationFor('Test') } }, + ]); + + const xxYY = results.get('xx-YY'); + expect(xxYY?.errors).toEqual([]); + expect(xxYY?.warnings).toEqual([ + "Locale data for 'xx-YY' cannot be found. No locale data will be included for this locale.", + ]); + const polyfills = findFile(xxYY?.outputFiles ?? [], 'polyfills.js').text; + expect(polyfills).toContain('(globalThis.$localize ??= {}).locale = "xx-YY";'); + expect(polyfills).not.toContain('ng.common.locales'); + }); + + it('reports an error diagnostic when an invalid or unsupported locale is provided', async () => { + const source = `(globalThis.$localize ??= {}).locale = "___NG_LOCALE_INSERT___";\n${GREETING_SOURCE}`; + const localeInliner = createInliner([browserFile('polyfills.js', source)]); + + const results = await localeInliner.inlineAll([ + { locale: 'invalid locale tag', translation: { greeting: translationFor('Test') } }, + ]); + + const invalid = results.get('invalid locale tag'); + expect(invalid?.errors).toEqual([ + 'Invalid or unsupported locale provided in configuration: "invalid locale tag"', + ]); + }); + + it('injects locale data script only once when multiple ___NG_LOCALE_INSERT___ sites are present', async () => { + const source = + `(globalThis.$localize ??= {}).locale = "___NG_LOCALE_INSERT___";\n` + + `const secondary = "___NG_LOCALE_INSERT___";\n${GREETING_SOURCE}`; + const localeInliner = createInliner([browserFile('polyfills.js', source)]); + + const results = await localeInliner.inlineAll([ + { locale: 'fr', translation: { greeting: translationFor('Bonjour') } }, + ]); + + const fr = results.get('fr'); + expect(fr?.errors).toEqual([]); + const frPolyfills = findFile(fr?.outputFiles ?? [], 'polyfills.js').text; + expect(frPolyfills).toContain('(globalThis.$localize ??= {}).locale = "fr";'); + expect(frPolyfills).toContain('const secondary = "fr";'); + + const iifeMatches = frPolyfills.match(/function\s*\(global/g); + expect(iifeMatches?.length).toBe(1); + }); }); diff --git a/packages/angular/build/src/tools/esbuild/i18n-locale-plugin.ts b/packages/angular/build/src/tools/esbuild/i18n-locale-plugin.ts index 603c65677b00..8ffddf19adb7 100644 --- a/packages/angular/build/src/tools/esbuild/i18n-locale-plugin.ts +++ b/packages/angular/build/src/tools/esbuild/i18n-locale-plugin.ts @@ -6,7 +6,8 @@ * found in the LICENSE file at https://angular.dev/license */ -import type { Plugin, ResolveResult } from 'esbuild'; +import type { Plugin } from 'esbuild'; +import { readFile } from 'node:fs/promises'; import { createProjectResolver } from '../../utils/resolve-project'; /** @@ -19,6 +20,124 @@ export const LOCALE_DATA_NAMESPACE = 'angular:locale/data'; */ export const LOCALE_DATA_BASE_MODULE = '@angular/common/locales/global'; +/** + * Result of resolving locale data for a given locale tag. + */ +export interface LocaleDataResolution { + path?: string; + warning?: string; + error?: string; +} + +/** + * Result of loading locale data for a given locale tag. + */ +export interface LoadedLocaleData { + code?: string; + warning?: string; + error?: string; +} + +const localeDataCache = new Map>(); + +/** + * Resolves the path to the Angular locale data file for a given locale tag. + * + * @param rawLocaleTag The raw locale identifier (e.g. "fr-CA", "de", "en-US"). + * @param projectResolve A function that attempts to resolve a path string to an absolute file path. + * @returns Resolution result with file path, or warning/error diagnostics if applicable. + */ +export function resolveLocaleDataPath( + rawLocaleTag: string, + projectResolve: (potentialPath: string) => string | undefined, +): LocaleDataResolution { + let partialLocaleTag: string; + try { + const locale = new Intl.Locale(rawLocaleTag); + partialLocaleTag = locale.baseName; + } catch { + return { + error: `Invalid or unsupported locale provided in configuration: "${rawLocaleTag}"`, + }; + } + + let exact = true; + while (partialLocaleTag) { + // Angular embeds the `en`/`en-US` locale into the framework and it does not need to be included again here. + if (partialLocaleTag === 'en' || partialLocaleTag === 'en-US') { + return {}; + } + + const potentialPath = `${LOCALE_DATA_BASE_MODULE}/${partialLocaleTag}`; + try { + const resolvedPath = projectResolve(potentialPath); + if (resolvedPath) { + return { + path: resolvedPath, + warning: exact + ? undefined + : `Locale data for '${rawLocaleTag}' cannot be found. Using locale data for '${partialLocaleTag}'.`, + }; + } + } catch {} + + // Remove the last subtag and try again with a less specific locale. + const parts = partialLocaleTag.split('-'); + partialLocaleTag = parts.slice(0, -1).join('-'); + exact = false; + } + + return { + warning: `Locale data for '${rawLocaleTag}' cannot be found. No locale data will be included for this locale.`, + }; +} + +/** + * Loads the Angular global locale data script for a specified locale tag. + * + * @param rawLocaleTag The raw locale identifier (e.g. "fr-CA", "de", "en-US"). + * @param projectRoot Optional project root for module resolution. + * @returns A promise resolving to the loaded locale data script code and any diagnostic warnings. + */ +export function loadLocaleData( + rawLocaleTag: string, + projectRoot?: string, +): Promise { + let cached = localeDataCache.get(rawLocaleTag); + if (!cached) { + cached = (async () => { + const projectResolve = createProjectResolver(projectRoot ?? process.cwd()); + const resolution = resolveLocaleDataPath(rawLocaleTag, (potentialPath) => { + try { + return projectResolve(potentialPath); + } catch { + return undefined; + } + }); + + if (resolution.error) { + return { error: resolution.error }; + } + + if (resolution.path) { + try { + const code = await readFile(resolution.path, 'utf8'); + + return { code, warning: resolution.warning }; + } catch (e) { + return { error: `Failed to read locale data file: ${(e as Error).message}` }; + } + } + + return { warning: resolution.warning }; + })(); + + localeDataCache.set(rawLocaleTag, cached); + } + + return cached; +} + /** * Creates an esbuild plugin that resolves Angular locale data files from `@angular/common`. * @@ -29,124 +148,44 @@ export function createAngularLocaleDataPlugin(): Plugin { name: 'angular-locale-data', setup(build): void { build.onResolve({ filter: /^angular:locale\/data:/ }, async ({ path }) => { - // Extract the locale from the path const rawLocaleTag = path.split(':', 3)[2]; + const { absWorkingDir } = build.initialOptions; + let projectResolve: ((packageName: string) => string) | undefined; - // Extract and normalize the base name of the raw locale tag - let partialLocaleTag: string; - try { - const locale = new Intl.Locale(rawLocaleTag); - partialLocaleTag = locale.baseName; - } catch { + const resolution = resolveLocaleDataPath(rawLocaleTag, (potentialPath) => { + projectResolve ??= createProjectResolver(absWorkingDir ?? process.cwd()); + try { + return projectResolve(potentialPath); + } catch { + return undefined; + } + }); + + if (resolution.error) { return { path: rawLocaleTag, namespace: LOCALE_DATA_NAMESPACE, - errors: [ - { - text: `Invalid or unsupported locale provided in configuration: "${rawLocaleTag}"`, - }, - ], + errors: [{ text: resolution.error }], }; } - let exact = true; - let projectResolve: ((packageName: string) => string) | undefined; - while (partialLocaleTag) { - // Angular embeds the `en`/`en-US` locale into the framework and it does not need to be included again here. - // The onLoad hook below for the locale data namespace has an `empty` loader that will prevent inclusion. - // Angular does not contain exact locale data for `en-US` but `en` is equivalent. - if (partialLocaleTag === 'en' || partialLocaleTag === 'en-US') { - return { - path: rawLocaleTag, - namespace: LOCALE_DATA_NAMESPACE, - }; - } - - // Attempt to resolve the locale tag data within the Angular base module location - const potentialPath = `${LOCALE_DATA_BASE_MODULE}/${partialLocaleTag}`; - - // If packages are configured to be external then leave the original angular locale import path. - // This happens when using the development server with caching enabled to allow Vite prebundling to work. - // There currently is no option on the esbuild resolve function to resolve while disabling the option. - // NOTE: If esbuild eventually allows controlling the external package options in a build.resolve call, this - // workaround can be removed. - let result: ResolveResult | undefined; - const { packages, absWorkingDir } = build.initialOptions; - if (packages === 'external' && absWorkingDir) { - projectResolve ??= createProjectResolver(absWorkingDir); - - try { - projectResolve(potentialPath); - - result = { - errors: [], - warnings: [], - external: true, - sideEffects: true, - namespace: '', - suffix: '', - pluginData: undefined, - path: potentialPath, - }; - } catch {} - } else { - result = await build.resolve(potentialPath, { - kind: 'import-statement', - resolveDir: absWorkingDir, - }); - if (result && result.external && absWorkingDir) { - projectResolve ??= createProjectResolver(absWorkingDir); - try { - const resolvedPath = projectResolve(potentialPath); - result = { - ...result, - external: false, - path: resolvedPath, - }; - } catch {} - } - } - - if (result?.path) { - if (exact) { - return result; - } else { - return { - ...result, - warnings: [ - ...result.warnings, - { - location: null, - text: `Locale data for '${rawLocaleTag}' cannot be found. Using locale data for '${partialLocaleTag}'.`, - }, - ], - }; - } - } - - // Remove the last subtag and try again with a less specific locale. - // Usually the match is exact so the string splitting here is not done until actually needed after the exact - // match fails to resolve. - const parts = partialLocaleTag.split('-'); - partialLocaleTag = parts.slice(0, -1).join('-'); - exact = false; + if (!resolution.path) { + return { + path: rawLocaleTag, + namespace: LOCALE_DATA_NAMESPACE, + warnings: resolution.warning + ? [{ location: null, text: resolution.warning }] + : undefined, + }; } - // Not found so issue a warning and use an empty loader. Framework built-in `en-US` data will be used. - // This retains existing behavior as in the Webpack-based builder. return { - path: rawLocaleTag, - namespace: LOCALE_DATA_NAMESPACE, - warnings: [ - { - location: null, - text: `Locale data for '${rawLocaleTag}' cannot be found. No locale data will be included for this locale.`, - }, - ], + path: resolution.path, + warnings: resolution.warning ? [{ location: null, text: resolution.warning }] : undefined, }; }); - // Locales that cannot be found will be loaded as empty content with a warning from the resolve step + // Locales that cannot be found or are en/en-US will be loaded as empty content build.onLoad({ filter: /./, namespace: LOCALE_DATA_NAMESPACE }, () => ({ contents: '', loader: 'empty', diff --git a/packages/angular/build/src/tools/esbuild/i18n-translation-encoder.ts b/packages/angular/build/src/tools/esbuild/i18n-translation-encoder.ts new file mode 100644 index 000000000000..091d9ba0ff26 --- /dev/null +++ b/packages/angular/build/src/tools/esbuild/i18n-translation-encoder.ts @@ -0,0 +1,109 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +/** + * Magic header identifier for i18n SharedArrayBuffer translation tables ('I18N'). + */ +export const I18N_MAGIC_ID = 0x4931384e; + +/** + * Encodes a JavaScript translation dictionary into a contiguous SharedArrayBuffer. + * The buffer contains a header, a sorted index table (by key for fast O(log N) binary search), + * and a UTF-8 string pool. + * + * @param translation The translation dictionary object. + * @returns A SharedArrayBuffer containing the binary encoded translation catalog. + */ +export function encodeTranslationToBuffer(translation: Record): SharedArrayBuffer { + const encoder = new TextEncoder(); + const entries = Object.entries(translation); + const entryCount = entries.length; + + // Pre-encode string keys and JSON-serialized string values + const encodedEntries: { + keyBytes: Uint8Array; + valBytes: Uint8Array; + }[] = new Array(entryCount); + + let stringPoolByteSize = 0; + + for (let i = 0; i < entryCount; i++) { + const [key, val] = entries[i]; + const keyBytes = encoder.encode(key); + const valBytes = encoder.encode(JSON.stringify(val)); + + encodedEntries[i] = { keyBytes, valBytes }; + stringPoolByteSize += keyBytes.byteLength + valBytes.byteLength; + } + + // Sort entries lexicographically by UTF-8 byte comparison. + // Lexicographical byte comparison matches the binary search reader's byte comparison, + // ensuring deterministic and zero-allocation lookups across all runtimes regardless of UTF-16 surrogate pairs. + encodedEntries.sort((a, b) => { + const aBytes = a.keyBytes; + const bBytes = b.keyBytes; + const minLen = Math.min(aBytes.length, bBytes.length); + + for (let i = 0; i < minLen; i++) { + const diff = aBytes[i] - bBytes[i]; + if (diff !== 0) { + return diff; + } + } + + return aBytes.length - bBytes.length; + }); + + const indexByteSize = entryCount * 16; + const headerByteSize = 16; + const stringPoolOffset = headerByteSize + indexByteSize; + + const totalByteSize = stringPoolOffset + stringPoolByteSize; + const buffer = new SharedArrayBuffer(totalByteSize); + + // Set up views over the SharedArrayBuffer + const uint32Header = new Uint32Array(buffer, 0, 4); + const uint32Index = new Uint32Array(buffer, headerByteSize, entryCount * 4); + const uint8Pool = new Uint8Array(buffer, stringPoolOffset); + + // Write Header: Magic, EntryCount, IndexOffset, StringPoolOffset + uint32Header[0] = I18N_MAGIC_ID; + uint32Header[1] = entryCount; + uint32Header[2] = headerByteSize; + uint32Header[3] = stringPoolOffset; + + // Write Index Table and String Pool entries + let currentPoolOffset = 0; + + for (let i = 0; i < entryCount; i++) { + const { keyBytes, valBytes } = encodedEntries[i]; + + const keyOffset = currentPoolOffset; + const keyLen = keyBytes.byteLength; + uint8Pool.set(keyBytes, keyOffset); + currentPoolOffset += keyLen; + + const valOffset = currentPoolOffset; + const valLen = valBytes.byteLength; + uint8Pool.set(valBytes, valOffset); + currentPoolOffset += valLen; + + // Index entry format (4 uint32 words / 16 bytes): + // [0]: keyOffset (uint32) + // [1]: keyLength (uint32) + // [2]: valOffset (uint32) + // [3]: valLength (uint32) + const idx = i * 4; + uint32Index[idx] = keyOffset; + uint32Index[idx + 1] = keyLen; + uint32Index[idx + 2] = valOffset; + uint32Index[idx + 3] = valLen; + } + + return buffer; +} diff --git a/packages/angular/build/src/tools/esbuild/i18n-translation-encoder_spec.ts b/packages/angular/build/src/tools/esbuild/i18n-translation-encoder_spec.ts new file mode 100644 index 000000000000..c3b6409d40b5 --- /dev/null +++ b/packages/angular/build/src/tools/esbuild/i18n-translation-encoder_spec.ts @@ -0,0 +1,168 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { encodeTranslationToBuffer } from './i18n-translation-encoder'; +import { + SharedTranslationDictionary, + createSharedTranslationProxy, +} from './i18n-translation-reader'; + +describe('SharedArrayBuffer Translation Encoder & Reader', () => { + it('encodes and reads simple key-value translations', () => { + const translation = { + greeting: 'Hello', + farewell: 'Goodbye', + numberKey: 42, + arrayKey: ['part1', 'part2'], + }; + + const buffer = encodeTranslationToBuffer(translation); + const dictionary = new SharedTranslationDictionary(buffer); + + expect(dictionary.get('greeting')).toEqual('Hello'); + expect(dictionary.get('farewell')).toEqual('Goodbye'); + expect(dictionary.get('numberKey')).toEqual(42); + expect(dictionary.get('arrayKey')).toEqual(['part1', 'part2']); + expect(dictionary.get('nonexistent')).toBeUndefined(); + }); + + it('works with Proxy wrapper and "in" operator', () => { + const translation = { + msg1: 'Message 1', + msg2: 'Message 2', + }; + + const buffer = encodeTranslationToBuffer(translation); + const proxy = createSharedTranslationProxy(buffer); + + expect(proxy['msg1']).toEqual('Message 1'); + expect(proxy['msg2']).toEqual('Message 2'); + expect(proxy['unknown']).toBeUndefined(); + expect('msg1' in proxy).toBeTrue(); + expect('msg2' in proxy).toBeTrue(); + expect('unknown' in proxy).toBeFalse(); + }); + + it('supports Object.prototype.hasOwnProperty and Object.getOwnPropertyDescriptor on Proxy', () => { + const translation = { + msg1: 'Message 1', + }; + + const buffer = encodeTranslationToBuffer(translation); + const proxy = createSharedTranslationProxy(buffer); + + expect(Object.prototype.hasOwnProperty.call(proxy, 'msg1')).toBeTrue(); + expect(Object.prototype.hasOwnProperty.call(proxy, 'unknown')).toBeFalse(); + + const desc = Object.getOwnPropertyDescriptor(proxy, 'msg1'); + expect(desc).toBeDefined(); + expect(desc?.value).toEqual('Message 1'); + expect(desc?.enumerable).toBeTrue(); + + const missingDesc = Object.getOwnPropertyDescriptor(proxy, 'unknown'); + expect(missingDesc).toBeUndefined(); + + // Verify standard Object.prototype methods work and don't throw + expect(typeof proxy.toString).toBe('function'); + expect(proxy.toString()).toEqual('[object Object]'); + expect(typeof proxy.valueOf).toBe('function'); + expect('toString' in proxy).toBeTrue(); + expect(String(proxy)).toEqual('[object Object]'); + }); + + it('handles non-BMP Unicode surrogate pairs and high BMP keys correctly', () => { + const translation = { + '😀': 'emoji message', + '\uE000': 'uE000 message', + 'abc': 'abc message', + }; + + const buffer = encodeTranslationToBuffer(translation); + const dictionary = new SharedTranslationDictionary(buffer); + + expect(dictionary.get('😀')).toEqual('emoji message'); + expect(dictionary.get('\uE000')).toEqual('uE000 message'); + expect(dictionary.get('abc')).toEqual('abc message'); + }); + + it('handles empty translation dictionaries', () => { + const buffer = encodeTranslationToBuffer({}); + const dictionary = new SharedTranslationDictionary(buffer); + + expect(dictionary.get('anyKey')).toBeUndefined(); + }); + + it('caches missing translation lookups efficiently', () => { + const translation = { + msg1: 'Message 1', + }; + + const buffer = encodeTranslationToBuffer(translation); + const dictionary = new SharedTranslationDictionary(buffer); + + // First lookup for missing key + expect(dictionary.get('missingKey')).toBeUndefined(); + // Second lookup for missing key (should hit lazyCache NOT_FOUND sentinel) + expect(dictionary.get('missingKey')).toBeUndefined(); + }); + + it('validates header size and offsets on initialization', () => { + // Buffer too small + const smallSab = new SharedArrayBuffer(8); + expect(() => new SharedTranslationDictionary(smallSab)).toThrowError( + 'Invalid SharedArrayBuffer translation header: buffer too small.', + ); + + // Invalid magic header + const badMagicSab = new SharedArrayBuffer(16); + expect(() => new SharedTranslationDictionary(badMagicSab)).toThrowError( + 'Invalid SharedArrayBuffer translation header magic identifier.', + ); + + // Out-of-bounds offset values + const invalidOffsetsSab = new SharedArrayBuffer(16); + const view = new Uint32Array(invalidOffsetsSab); + view[0] = 0x4931384e; // I18N_MAGIC_ID + view[1] = 100; // entryCount = 100 entries requiring 1600 bytes + view[2] = 16; + view[3] = 16; + expect(() => new SharedTranslationDictionary(invalidOffsetsSab)).toThrowError( + 'Invalid SharedArrayBuffer translation header offsets.', + ); + + // Unaligned header size + const unalignedHeaderSab = new SharedArrayBuffer(32); + const unalignedView = new Uint32Array(unalignedHeaderSab); + unalignedView[0] = 0x4931384e; + unalignedView[1] = 0; + unalignedView[2] = 17; // unaligned header byte size + unalignedView[3] = 17; + expect(() => new SharedTranslationDictionary(unalignedHeaderSab)).toThrowError( + 'Invalid SharedArrayBuffer translation header offsets.', + ); + }); + + it('safely handles corrupted index offsets within a valid header', () => { + // 1 entry: header (16 bytes) + index (16 bytes) + pool (10 bytes) = 42 bytes + const buffer = new SharedArrayBuffer(48); + const uint32 = new Uint32Array(buffer); + uint32[0] = 0x4931384e; // Magic + uint32[1] = 1; // 1 entry + uint32[2] = 16; // header size + uint32[3] = 32; // string pool offset + + // Corrupted entry: keyOffset points past pool length + uint32[4] = 100; // keyOffset = 100 + uint32[5] = 5; // keyLen = 5 + uint32[6] = 0; // valOffset = 0 + uint32[7] = 5; // valLen = 5 + + const dictionary = new SharedTranslationDictionary(buffer); + expect(dictionary.get('testKey')).toBeUndefined(); + }); +}); diff --git a/packages/angular/build/src/tools/esbuild/i18n-translation-reader.ts b/packages/angular/build/src/tools/esbuild/i18n-translation-reader.ts new file mode 100644 index 000000000000..bc87633da5dc --- /dev/null +++ b/packages/angular/build/src/tools/esbuild/i18n-translation-reader.ts @@ -0,0 +1,193 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { I18N_MAGIC_ID } from './i18n-translation-encoder'; + +/** + * Sentinel value used to cache non-existent message key lookups in lazyCache. + */ +const NOT_FOUND = Symbol('NOT_FOUND'); + +/** + * Compares a target key's UTF-8 byte array against a byte sequence in the string pool. + * Lexicographical byte comparison of UTF-8 sequences matches UTF-16 code-unit string comparison + * used when encoding the sorted index table. + */ +function compareBytes( + target: Uint8Array, + pool: Uint8Array, + poolOffset: number, + keyLen: number, +): number { + if (poolOffset < 0 || poolOffset + keyLen > pool.length) { + return 1; + } + + const minLen = Math.min(target.length, keyLen); + for (let i = 0; i < minLen; i++) { + const diff = target[i] - pool[poolOffset + i]; + if (diff !== 0) { + return diff; + } + } + + return target.length - keyLen; +} + +/** + * A zero-copy reader that queries translation messages directly from a SharedArrayBuffer + * using binary search over a sorted key index. + */ +export class SharedTranslationDictionary { + private readonly entryCount: number; + private readonly uint32Index: Uint32Array; + private readonly uint8Pool: Uint8Array; + private readonly decoder = new TextDecoder(); + private readonly encoder = new TextEncoder(); + private readonly lazyCache = new Map(); + + constructor(buffer: SharedArrayBuffer) { + if (buffer.byteLength < 16) { + throw new Error('Invalid SharedArrayBuffer translation header: buffer too small.'); + } + + const uint32Header = new Uint32Array(buffer, 0, 4); + if (uint32Header[0] !== I18N_MAGIC_ID) { + throw new Error('Invalid SharedArrayBuffer translation header magic identifier.'); + } + + this.entryCount = uint32Header[1]; + const headerByteSize = uint32Header[2]; + const stringPoolOffset = uint32Header[3]; + + if ( + headerByteSize < 16 || + headerByteSize % 4 !== 0 || + headerByteSize + this.entryCount * 16 > buffer.byteLength || + stringPoolOffset < headerByteSize + this.entryCount * 16 || + stringPoolOffset > buffer.byteLength + ) { + throw new Error('Invalid SharedArrayBuffer translation header offsets.'); + } + + this.uint32Index = new Uint32Array(buffer, headerByteSize, this.entryCount * 4); + this.uint8Pool = new Uint8Array(buffer, stringPoolOffset); + } + + /** + * Looks up a translation message by key. + * Performs a binary search over the index table if not previously cached in the lazy cache. + * + * @param targetKey The message key ID to search for. + * @returns The parsed translation message, or undefined if not found. + */ + get(targetKey: string): unknown | undefined { + const cached = this.lazyCache.get(targetKey); + if (cached !== undefined) { + return cached === NOT_FOUND ? undefined : cached; + } + + const targetBytes = this.encoder.encode(targetKey); + let low = 0; + let high = this.entryCount - 1; + + while (low <= high) { + const mid = (low + high) >>> 1; + const idx = mid * 4; + + const keyOffset = this.uint32Index[idx]; + const keyLen = this.uint32Index[idx + 1]; + + const cmp = compareBytes(targetBytes, this.uint8Pool, keyOffset, keyLen); + if (cmp === 0) { + const valOffset = this.uint32Index[idx + 2]; + const valLen = this.uint32Index[idx + 3]; + if (valOffset < 0 || valOffset + valLen > this.uint8Pool.length) { + this.lazyCache.set(targetKey, NOT_FOUND); + + return undefined; + } + + const valBytes = this.uint8Pool.subarray(valOffset, valOffset + valLen); + const valJson = this.decoder.decode(valBytes); + + try { + const val = JSON.parse(valJson); + this.lazyCache.set(targetKey, val); + + return val; + } catch { + this.lazyCache.set(targetKey, NOT_FOUND); + + return undefined; + } + } else if (cmp < 0) { + high = mid - 1; + } else { + low = mid + 1; + } + } + + this.lazyCache.set(targetKey, NOT_FOUND); + + return undefined; + } +} + +/** + * Creates a JavaScript object Proxy wrapping a SharedTranslationDictionary so it can be passed + * directly to `@angular/localize` inliner functions as a standard translation Record. + * + * Traps `get`, `has`, and `getOwnPropertyDescriptor` to fulfill all `@angular/localize` `translate()` + * lookup requirements (`translations[id]`, `translations[legacyId]`, `Object.hasOwn(translations, id)`). + * The `ownKeys` trap is intentionally omitted so keys are not eagerly decoded upfront upon reflection. + * + * @param buffer The SharedArrayBuffer containing binary encoded translation catalog. + * @returns A Proxy object that intercepts property reads and queries the SharedTranslationDictionary. + */ +export function createSharedTranslationProxy(buffer: SharedArrayBuffer): Record { + const dictionary = new SharedTranslationDictionary(buffer); + + return new Proxy( + {}, + { + get(target, prop: string | symbol) { + if (typeof prop === 'string') { + const value = dictionary.get(prop); + if (value !== undefined) { + return value; + } + } + + return Reflect.get(target, prop); + }, + has(target, prop: string | symbol) { + if (typeof prop === 'string' && dictionary.get(prop) !== undefined) { + return true; + } + + return Reflect.has(target, prop); + }, + getOwnPropertyDescriptor(target, prop: string | symbol) { + if (typeof prop === 'string') { + const val = dictionary.get(prop); + if (val !== undefined) { + return { + enumerable: true, + configurable: true, + writable: false, + value: val, + }; + } + } + + return Reflect.getOwnPropertyDescriptor(target, prop); + }, + }, + ); +} diff --git a/packages/angular/build/src/tools/esbuild/javascript-transformer-worker.ts b/packages/angular/build/src/tools/esbuild/javascript-transformer-worker.ts index ffc5feb6e008..f2ec7eccce6d 100644 --- a/packages/angular/build/src/tools/esbuild/javascript-transformer-worker.ts +++ b/packages/angular/build/src/tools/esbuild/javascript-transformer-worker.ts @@ -6,40 +6,50 @@ * found in the LICENSE file at https://angular.dev/license */ +import remapping, { type DecodedSourceMap, type EncodedSourceMap } from '@ampproject/remapping'; import { type PluginItem, transformAsync } from '@babel/core'; import { createRequire } from 'node:module'; +import { workerData } from 'node:worker_threads'; import Piscina from 'piscina'; import { useBabelLinker } from '../../utils/environment-options.js'; -import { loadInputSourceMap, removeSourceMappingURL } from '../../utils/source-map'; +import { + findTrailingSourceMapComment, + isTrailingSourceMapComment, + loadInputSourceMap, + loadInputSourceMapFromUrl, + removeSourceMappingURL, +} from '../../utils/source-map'; +import { transform as transformWithOxc } from '../oxc/oxc-transform.js'; +import type { JavaScriptTransformerOptions } from './javascript-transformer'; interface JavaScriptTransformRequest { filename: string; data: string | Uint8Array; - sourcemap: boolean; - thirdPartySourcemaps: boolean; - advancedOptimizations: boolean; skipLinker?: boolean; sideEffects?: boolean; - jit: boolean; instrumentForCoverage?: boolean; } +interface TransformOptions extends Omit { + inputSourceMap?: EncodedSourceMap; + isAlreadyStripped?: boolean; +} + +const { + sourcemap = false, + thirdPartySourcemaps = false, + advancedOptimizations = false, + jit = false, +} = (workerData || {}) as Partial; + const textDecoder = new TextDecoder(); const textEncoder = new TextEncoder(); -/** - * The function name prefix for all Angular partial compilation functions. - * Used to determine if linking of a JavaScript file is required. - * If any additional declarations are added or otherwise changed in the linker, - * the names MUST begin with this prefix. - */ -const LINKER_DECLARATION_PREFIX = 'ɵɵngDeclare'; - async function instrumentCoverage( filename: string, data: string, useInputSourcemap: boolean, -): Promise { +): Promise<{ code: string; map?: EncodedSourceMap }> { try { let resolvedPath = 'istanbul-lib-instrument'; try { @@ -63,15 +73,14 @@ async function instrumentCoverage( filename, inputSourceMap as Parameters[2], ); - const lastMap = instrumenter.lastSourceMap(); - - if (useInputSourcemap && lastMap) { - const inlineMap = Buffer.from(JSON.stringify(lastMap)).toString('base64'); - - return instrumentedCode + `\n//# sourceMappingURL=data:application/json;base64,${inlineMap}`; - } - - return removeSourceMappingURL(instrumentedCode); + const lastMap = useInputSourcemap + ? (instrumenter.lastSourceMap() as EncodedSourceMap) + : undefined; + + return { + code: instrumentedCode, + map: lastMap ?? undefined, + }; } catch (error) { throw new Error( `The 'istanbul-lib-instrument' package is required for code coverage but was not found. Please install the package.`, @@ -84,118 +93,160 @@ export default async function transformJavaScript( request: JavaScriptTransformRequest, ): Promise { const { filename, data, ...options } = request; - const textData = typeof data === 'string' ? data : textDecoder.decode(data); - const transformedData = await transformJavaScriptImpl(filename, textData, options); + const useInputSourcemap = + sourcemap && (!!thirdPartySourcemaps || !/[\\/]node_modules[\\/]/.test(filename)); + + let textData: string; + let inputSourceMap: EncodedSourceMap | undefined; + let isAlreadyStripped = false; + + if (typeof data !== 'string') { + const trailing = findTrailingSourceMapComment(data); + if (trailing === null) { + // 0 comments: fast path, no sourcemap to load or strip + textData = textDecoder.decode(data); + isAlreadyStripped = true; + } else if (trailing !== undefined) { + if (useInputSourcemap) { + inputSourceMap = loadInputSourceMapFromUrl(filename, trailing.urlLine); + if (inputSourceMap !== undefined) { + // Valid trailing sourcemap comment confirmed: safe to slice code buffer for transformation passes. + // Note: If no passes modify the code, the untouched original `data` buffer is returned below. + textData = textDecoder.decode(trailing.code); + isAlreadyStripped = true; + } else { + // Not a valid trailing sourcemap (e.g. inside template literal): fallback to full decode + textData = textDecoder.decode(data); + } + } else if (isTrailingSourceMapComment(trailing.urlLine)) { + // Valid trailing sourcemap comment confirmed: safe to slice code buffer + textData = textDecoder.decode(trailing.code); + isAlreadyStripped = true; + } else { + // Fallback to full decode and state-machine stripping + textData = textDecoder.decode(data); + } + } else { + // Multiple comments or comment not at line start: fall back to full decode and string parser + textData = textDecoder.decode(data); + } + } else { + textData = data; + } + + const transformedData = await transformJavaScriptImpl(filename, textData, { + ...options, + inputSourceMap, + isAlreadyStripped, + }); + + // If no transformations modified the code, return the original untouched data buffer via `move`. + // This preserves any original trailing sourcemap comment and avoids re-encoding. + if (transformedData === textData && typeof data !== 'string') { + return Piscina.move(data); + } - // Transfer the data via `move` instead of cloning return Piscina.move(textEncoder.encode(transformedData)); } -/** - * Cached instance of the OXC linker module. - */ -let oxcLinkerModule: typeof import('../angular/linker/oxc-linker.js') | undefined; - async function transformJavaScriptImpl( filename: string, data: string, - options: Omit, + options: TransformOptions, ): Promise { - const shouldLink = !options.skipLinker && requiresLinking(filename, data); + const shouldLink = !options.skipLinker; const useInputSourcemap = - options.sourcemap && - (!!options.thirdPartySourcemaps || !/[\\/]node_modules[\\/]/.test(filename)); + sourcemap && (!!thirdPartySourcemaps || !/[\\/]node_modules[\\/]/.test(filename)); let code = data; + const maps: (DecodedSourceMap | EncodedSourceMap)[] = []; + let coverageMap: EncodedSourceMap | undefined; if (options.instrumentForCoverage) { - code = await instrumentCoverage(filename, code, useInputSourcemap); + const result = await instrumentCoverage(filename, code, useInputSourcemap); + code = result.code; + coverageMap = result.map; } - if (shouldLink) { - if (useBabelLinker) { - const { createEs2015LinkerPlugin } = await import('@angular/compiler-cli/linker/babel'); - const { ConsoleLogger, LogLevel } = await import('@angular/compiler-cli'); - - const result = await transformAsync(code, { - filename, - inputSourceMap: (useInputSourcemap ? undefined : false) as undefined, - sourceMaps: useInputSourcemap ? 'inline' : false, - compact: false, - configFile: false, - babelrc: false, - browserslistConfigFile: false, - plugins: [ - createEs2015LinkerPlugin({ - fileSystem: { - exists: () => false, - readFile: () => '', - resolve: (...paths: string[]) => paths.join('/'), - dirname: (path: string) => path.split('/').slice(0, -1).join('/'), - relative: (_from: string, to: string) => to, - } as never, - logger: new ConsoleLogger(LogLevel.info), - linkerJitMode: options.jit, - // This is a workaround until https://github.com/angular/angular/issues/42769 is fixed. - sourceMapping: false, - }) as PluginItem, - ], - }); - - code = result?.code ?? code; - } else { - oxcLinkerModule ??= await import('../angular/linker/oxc-linker.js'); - const result = oxcLinkerModule.linkWithOxc(filename, code, { - sourcemap: useInputSourcemap, - jit: options.jit, - skipCheck: true, - }); - code = result.code; - if (useInputSourcemap && result.map) { - code = removeSourceMappingURL(code); - const base64Map = Buffer.from(result.map).toString('base64'); - code += `\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,${base64Map}`; - } + if (shouldLink && useBabelLinker) { + const { createEs2015LinkerPlugin } = await import('@angular/compiler-cli/linker/babel'); + const { ConsoleLogger, LogLevel } = await import('@angular/compiler-cli'); + + const result = await transformAsync(code, { + filename, + inputSourceMap: false, + sourceMaps: !!useInputSourcemap, + compact: false, + configFile: false, + babelrc: false, + browserslistConfigFile: false, + plugins: [ + createEs2015LinkerPlugin({ + fileSystem: { + exists: () => false, + readFile: () => '', + resolve: (...paths: string[]) => paths.join('/'), + dirname: (path: string) => path.split('/').slice(0, -1).join('/'), + relative: (_from: string, to: string) => to, + } as never, + logger: new ConsoleLogger(LogLevel.info), + linkerJitMode: jit, + // This is a workaround until https://github.com/angular/angular/issues/42769 is fixed. + sourceMapping: false, + }) as PluginItem, + ], + }); + + code = result?.code ?? code; + if (result?.map) { + maps.push(result.map as EncodedSourceMap); } } - // Run advanced optimizations using our fast oxc-transform - if (options.advancedOptimizations) { - const { transform } = await import('../oxc/oxc-transform.js'); + // Run Oxc linking and/or advanced optimizations in a single unified AST traversal pass + const oxcLink = shouldLink && !useBabelLinker; + if (oxcLink || advancedOptimizations) { const sideEffectFree = options.sideEffects === false; const safeAngularPackage = sideEffectFree && /[\\/]node_modules[\\/]@angular[\\/]/.test(filename); const topLevelSafeMode = !safeAngularPackage; - const result = transform(filename, code, { + const result = transformWithOxc(filename, code, { + link: oxcLink, + jit, + advancedOptimizations, sourcemap: useInputSourcemap, sideEffects: options.sideEffects, topLevelSafeMode, }); code = result.code; - - if (useInputSourcemap && result.map) { - // Strip old source map comment if Babel added one - code = removeSourceMappingURL(code); - const base64Map = Buffer.from(result.map).toString('base64'); - code += `\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,${base64Map}`; + if (result.map) { + maps.push(result.map); } } - // Strip sourcemaps if they should not be used - return useInputSourcemap ? code : removeSourceMappingURL(code); -} + if (useInputSourcemap) { + const baseMap = coverageMap ?? options.inputSourceMap ?? loadInputSourceMap(filename, data); + if (maps.length > 0 || coverageMap) { + if (!options.isAlreadyStripped) { + code = removeSourceMappingURL(code); + } + const remappingChain: (DecodedSourceMap | EncodedSourceMap)[] = maps.reverse(); + if (baseMap) { + remappingChain.push(baseMap); + } -function requiresLinking(path: string, source: string): boolean { - // @angular/core and @angular/compiler will cause false positives - // Also, TypeScript files do not require linking - if (/[\\/]@angular[\\/](?:compiler|core)|\.tsx?$/.test(path)) { - return false; + if (remappingChain.length > 0) { + const finalMap = remapping(remappingChain, () => null).toString(); + const base64Map = Buffer.from(finalMap).toString('base64'); + code += `\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,${base64Map}`; + } + } + + return code; } - // Check if the source code includes one of the declaration functions. - // There is a low chance of a false positive but the names are fairly unique - // and the result would be an unnecessary no-op additional plugin pass. - return source.includes(LINKER_DECLARATION_PREFIX); + // Strip sourcemaps if they should not be used + return options.isAlreadyStripped ? code : removeSourceMappingURL(code); } diff --git a/packages/angular/build/src/tools/esbuild/javascript-transformer.ts b/packages/angular/build/src/tools/esbuild/javascript-transformer.ts index de4103f57559..3ba0dfff45b1 100644 --- a/packages/angular/build/src/tools/esbuild/javascript-transformer.ts +++ b/packages/angular/build/src/tools/esbuild/javascript-transformer.ts @@ -6,13 +6,41 @@ * found in the LICENSE file at https://angular.dev/license */ -import { createHash } from 'node:crypto'; import { readFile } from 'node:fs/promises'; +import { createContentHash } from '../../utils/hash'; import { IMPORT_EXEC_ARGV } from '../../utils/server-rendering/esm-in-memory-loader/utils'; import { removeSourceMappingURL } from '../../utils/source-map'; import { WorkerPool, WorkerPoolOptions } from '../../utils/worker-pool'; import { Cache } from './cache'; +const LINKER_DECLARATION_PREFIX = 'ɵɵngDeclare'; +const LINKER_DECLARATION_PREFIX_BYTES = Buffer.from(LINKER_DECLARATION_PREFIX, 'utf-8'); + +/** + * Determines whether JavaScript code requires Angular linker processing. + * + * @param path The full path to the file. + * @param data The data (string or Buffer) of the file. + * @returns True if the code contains an Angular partial declaration; otherwise false. + */ +function requiresLinking(path: string, data: string | Uint8Array): boolean { + // @angular/core and @angular/compiler will cause false positives + // Also, TypeScript files do not require linking + if (/[\\/]@angular[\\/](?:compiler|core)[\\/]|\.[cm]?tsx?$/.test(path)) { + return false; + } + + if (typeof data === 'string') { + return data.includes(LINKER_DECLARATION_PREFIX); + } + + const dataBuffer = Buffer.isBuffer(data) + ? data + : Buffer.from(data.buffer, data.byteOffset, data.byteLength); + + return dataBuffer.includes(LINKER_DECLARATION_PREFIX_BYTES); +} + /** * Transformation options that should apply to all transformed files and data. */ @@ -65,6 +93,7 @@ export class JavaScriptTransformer { jit, }; this.#fileCacheKeyBase = Buffer.from(JSON.stringify(this.#commonOptions), 'utf-8'); + this.#workerPool = this.#ensureWorkerPool(); } /** @@ -102,6 +131,8 @@ export class JavaScriptTransformer { const workerPoolOptions: WorkerPoolOptions = { filename: require.resolve('./javascript-transformer-worker'), maxThreads: this.maxThreads, + minThreads: this.maxThreads, + workerData: this.#commonOptions, }; // Prevent passing SSR `--import` (loader-hooks) from parent to child worker. @@ -132,50 +163,39 @@ export class JavaScriptTransformer { return this.#runWithThrottle(async () => { const data = await readFile(filename); - let result; - let cacheKey; + let cacheKey: string | undefined; if (this.cache) { // Create a cache key from the file data and options that effect the output. // NOTE: If additional options are added, this may need to be updated. - // TODO: Consider xxhash or similar instead of SHA256 - const hash = createHash('sha256'); - hash.update(`${!!skipLinker}--${!!sideEffects}`); - hash.update(data); - hash.update(this.#fileCacheKeyBase); - cacheKey = hash.digest('hex'); + const hasher = createContentHash(); + hasher.update(`${!!skipLinker}--${!!sideEffects}`); + hasher.update(data); + hasher.update(this.#fileCacheKeyBase); + cacheKey = hasher.digest(); try { - result = await this.cache?.get(cacheKey); + const cached = await this.cache.get(cacheKey); + if (cached !== undefined) { + return cached; + } } catch { // Failure to get the value should not fail the transform } } - if (result === undefined) { - // If there is no cache or no cached entry, process the file - result = (await this.#ensureWorkerPool().run( - { - filename, - data, - skipLinker, - sideEffects, - instrumentForCoverage, - ...this.#commonOptions, - }, - { - // The below is disable as with Yarn PNP this causes build failures with the below message - // `Unable to deserialize cloned data`. - transferList: process.versions.pnp ? undefined : [data.buffer], - }, - )) as Uint8Array; - - // If there is a cache then store the result - if (this.cache && cacheKey) { - try { - await this.cache.put(cacheKey, result); - } catch { - // Failure to store the value in the cache should not fail the transform - } + const result = await this.transformData( + filename, + data, + !!skipLinker, + sideEffects, + instrumentForCoverage, + ); + + if (this.cache && cacheKey) { + try { + await this.cache.put(cacheKey, result); + } catch { + // Failure to store the value in the cache should not fail the transform } } @@ -194,30 +214,49 @@ export class JavaScriptTransformer { */ async transformData( filename: string, - data: string, + data: string | Uint8Array, skipLinker: boolean, sideEffects?: boolean, instrumentForCoverage?: boolean, ): Promise { + const shouldLink = !skipLinker && requiresLinking(filename, data); + // Perform a quick test to determine if the data needs any transformations. // This allows directly returning the data without the worker communication overhead. - if (skipLinker && !this.#commonOptions.advancedOptimizations && !instrumentForCoverage) { + if (!shouldLink && !this.#commonOptions.advancedOptimizations && !instrumentForCoverage) { const keepSourcemap = this.#commonOptions.sourcemap && (!!this.#commonOptions.thirdPartySourcemaps || !/[\\/]node_modules[\\/]/.test(filename)); - return Buffer.from(keepSourcemap ? data : removeSourceMappingURL(data), 'utf-8'); + if (typeof data === 'string') { + return Buffer.from(keepSourcemap ? data : removeSourceMappingURL(data), 'utf-8'); + } + + return keepSourcemap ? data : removeSourceMappingURL(data); } - return this.#runWithThrottle(() => - this.#ensureWorkerPool().run({ + // Only standalone (non-pooled) ArrayBuffers can be transferred across worker threads. + // Node.js shares an internal 8KB ArrayBuffer pool for small buffers, and transferring + // a pooled buffer will throw a DataCloneError because detaching it invalidates other slices. + // In addition, SharedArrayBuffers cannot be transferred, and Yarn PnP has deserialization issues. + const isTransferable = + typeof data !== 'string' && + data.buffer instanceof ArrayBuffer && + data.byteOffset === 0 && + data.byteLength === data.buffer.byteLength && + !process.versions.pnp; + + return this.#ensureWorkerPool().run( + { filename, data, - skipLinker, + skipLinker: !shouldLink, sideEffects, instrumentForCoverage, - ...this.#commonOptions, - }), + }, + { + transferList: isTransferable ? [data.buffer] : undefined, + }, ); } diff --git a/packages/angular/build/src/tools/esbuild/javascript-transformer_spec.ts b/packages/angular/build/src/tools/esbuild/javascript-transformer_spec.ts new file mode 100644 index 000000000000..b1cdeec07b44 --- /dev/null +++ b/packages/angular/build/src/tools/esbuild/javascript-transformer_spec.ts @@ -0,0 +1,377 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { JavaScriptTransformer } from './javascript-transformer'; + +describe('JavaScriptTransformer sourcemaps', () => { + let transformer: JavaScriptTransformer; + + afterEach(async () => { + await transformer?.close(); + }); + + function extractSourcemap(code: string): Record | null { + const match = code.match( + /\/\/# sourceMappingURL=data:application\/json;charset=utf-8;base64,(.+)/, + ); + if (!match) { + return null; + } + + return JSON.parse(Buffer.from(match[1], 'base64').toString('utf-8')); + } + + it('should remap correctly when only advanced optimizations are applied', async () => { + transformer = new JavaScriptTransformer( + { + sourcemap: true, + advancedOptimizations: true, + }, + 1, + ); + + const inputMap = { + version: 3, + sources: ['src/app.ts'], + sourcesContent: ['const x = new SomeClass();'], + mappings: 'AAAA', + names: [], + }; + const base64Map = Buffer.from(JSON.stringify(inputMap)).toString('base64'); + const input = `var x = new SomeClass();\n//# sourceMappingURL=data:application/json;base64,${base64Map}`; + + const result = await transformer.transformData('src/app.js', input, true); + const text = Buffer.from(result).toString('utf-8'); + const map = extractSourcemap(text); + + expect(map).toBeDefined(); + expect(map?.['version']).toBe(3); + expect(map?.['sources']).toContain('src/app.ts'); + expect(typeof map?.['mappings']).toBe('string'); + expect((map?.['mappings'] as string).length).toBeGreaterThan(0); + }); + + it('should remap correctly when only linking is applied', async () => { + transformer = new JavaScriptTransformer( + { + sourcemap: true, + thirdPartySourcemaps: true, + }, + 1, + ); + + const inputMap = { + version: 3, + sources: ['node_modules/my-lib/directive.ts'], + sourcesContent: ['export class MyDirective {}'], + mappings: 'AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA', + names: [], + }; + const base64Map = Buffer.from(JSON.stringify(inputMap)).toString('base64'); + const input = ` + import * as i0 from "@angular/core"; + export class MyDirective {} + MyDirective.ɵdir = i0.ɵɵngDeclareDirective({ + minVersion: "12.0.0", + version: "14.0.0", + ngImport: i0, + type: MyDirective, + selector: "[my-dir]" + }); + //# sourceMappingURL=data:application/json;base64,${base64Map} + `; + + const result = await transformer.transformData( + 'node_modules/my-lib/directive.js', + input, + false, + ); + const text = Buffer.from(result).toString('utf-8'); + const map = extractSourcemap(text); + + expect(map).toBeDefined(); + expect(map?.['version']).toBe(3); + expect(map?.['sources']).toContain('node_modules/my-lib/directive.ts'); + expect(typeof map?.['mappings']).toBe('string'); + expect((map?.['mappings'] as string).length).toBeGreaterThan(0); + }); + + it('should defer and chain remapping when both linking and advanced optimizations are applied', async () => { + transformer = new JavaScriptTransformer( + { + sourcemap: true, + thirdPartySourcemaps: true, + advancedOptimizations: true, + }, + 1, + ); + + const inputMap = { + version: 3, + sources: ['node_modules/my-lib/component.ts'], + sourcesContent: ['export class MyComponent {}'], + mappings: 'AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA', + names: [], + }; + const base64Map = Buffer.from(JSON.stringify(inputMap)).toString('base64'); + const input = ` + import * as i0 from "@angular/core"; + export class MyComponent {} + MyComponent.ɵcmp = i0.ɵɵngDeclareComponent({ + minVersion: "12.0.0", + version: "14.0.0", + ngImport: i0, + type: MyComponent, + selector: "my-cmp", + template: "
" + }); + //# sourceMappingURL=data:application/json;base64,${base64Map} + `; + + const result = await transformer.transformData( + 'node_modules/my-lib/component.js', + input, + false, + ); + const text = Buffer.from(result).toString('utf-8'); + const map = extractSourcemap(text); + + expect(map).toBeDefined(); + expect(map?.['version']).toBe(3); + expect(map?.['sources']).toContain('node_modules/my-lib/component.ts'); + expect(typeof map?.['mappings']).toBe('string'); + expect((map?.['mappings'] as string).length).toBeGreaterThan(0); + }); + + it('should produce a valid sourcemap when no input sourcemap is present', async () => { + transformer = new JavaScriptTransformer( + { + sourcemap: true, + advancedOptimizations: true, + }, + 1, + ); + + const input = 'var x = new SomeClass();'; + const result = await transformer.transformData('src/app.js', input, true); + const text = Buffer.from(result).toString('utf-8'); + const map = extractSourcemap(text); + + expect(map).toBeDefined(); + expect(map?.['version']).toBe(3); + expect(map?.['sources']).toContain('src/app.js'); + expect(typeof map?.['mappings']).toBe('string'); + expect((map?.['mappings'] as string).length).toBeGreaterThan(0); + }); + + it('should remap correctly when coverage instrumentation is applied with an input sourcemap', async () => { + transformer = new JavaScriptTransformer( + { + sourcemap: true, + }, + 1, + ); + + const inputMap = { + version: 3, + sources: ['src/counter.ts'], + sourcesContent: ['export function add(a: number, b: number) { return a + b; }'], + mappings: 'AAAA', + names: [], + }; + const base64Map = Buffer.from(JSON.stringify(inputMap)).toString('base64'); + const input = `export function add(a, b) { return a + b; }\n//# sourceMappingURL=data:application/json;base64,${base64Map}`; + + const result = await transformer.transformData( + 'src/counter.js', + input, + true, + undefined, + true /* instrumentForCoverage */, + ); + const text = Buffer.from(result).toString('utf-8'); + const map = extractSourcemap(text); + + expect(map).toBeDefined(); + expect(map?.['version']).toBe(3); + expect(map?.['sources']).toContain('src/counter.ts'); + expect(typeof map?.['mappings']).toBe('string'); + expect((map?.['mappings'] as string).length).toBeGreaterThan(0); + }); + + it('should defer and chain remapping when coverage instrumentation and advanced optimizations are applied', async () => { + transformer = new JavaScriptTransformer( + { + sourcemap: true, + advancedOptimizations: true, + }, + 1, + ); + + const inputMap = { + version: 3, + sources: ['src/app.ts'], + sourcesContent: ['const x = new SomeClass();'], + mappings: 'AAAA', + names: [], + }; + const base64Map = Buffer.from(JSON.stringify(inputMap)).toString('base64'); + const input = `var x = new SomeClass();\n//# sourceMappingURL=data:application/json;base64,${base64Map}`; + + const result = await transformer.transformData( + 'src/app.js', + input, + true, + undefined, + true /* instrumentForCoverage */, + ); + const text = Buffer.from(result).toString('utf-8'); + const map = extractSourcemap(text); + + expect(map).toBeDefined(); + expect(map?.['version']).toBe(3); + expect(map?.['sources']).toContain('src/app.ts'); + expect(typeof map?.['mappings']).toBe('string'); + expect((map?.['mappings'] as string).length).toBeGreaterThan(0); + }); + + it('should accept a Uint8Array input in transformData', async () => { + transformer = new JavaScriptTransformer( + { + sourcemap: true, + advancedOptimizations: true, + }, + 1, + ); + + const inputBuffer = Buffer.from('var x = new SomeClass();', 'utf-8'); + const result = await transformer.transformData('src/app.js', inputBuffer, true); + const text = Buffer.from(result).toString('utf-8'); + const map = extractSourcemap(text); + + expect(map).toBeDefined(); + expect(map?.['version']).toBe(3); + expect(map?.['sources']).toContain('src/app.js'); + expect(typeof map?.['mappings']).toBe('string'); + }); + + it('should strip trailing sourcemap comments from Uint8Array input on fast-path', async () => { + transformer = new JavaScriptTransformer( + { + sourcemap: false, + }, + 1, + ); + + const inputBuffer = Buffer.from( + 'console.log("hello");\n//# sourceMappingURL=app.js.map', + 'utf-8', + ); + const result = await transformer.transformData('node_modules/my-lib/lib.js', inputBuffer, true); + const text = Buffer.from(result).toString('utf-8'); + + expect(text).toBe('console.log("hello");\n'); + }); + + it('should return Uint8Array input untouched on fast-path when no sourcemap comment is present', async () => { + transformer = new JavaScriptTransformer( + { + sourcemap: false, + }, + 1, + ); + + const inputBuffer = Buffer.from('console.log("hello");\nconst x = 1;', 'utf-8'); + const result = await transformer.transformData('node_modules/my-lib/lib.js', inputBuffer, true); + + expect(result).toBe(inputBuffer); + }); + + it('should return Uint8Array untouched when skipLinker is false but file contains no linker declarations', async () => { + transformer = new JavaScriptTransformer( + { + sourcemap: false, + }, + 1, + ); + + const inputBuffer = Buffer.from('console.log("no linking required");\nconst x = 1;', 'utf-8'); + const result = await transformer.transformData( + 'node_modules/my-lib/lib.js', + inputBuffer, + false, // skipLinker: false + ); + + expect(result).toBe(inputBuffer); + }); + + it('should bypass worker and skip linking for @angular/core and @angular/compiler paths', async () => { + transformer = new JavaScriptTransformer( + { + sourcemap: false, + }, + 1, + ); + + const inputBuffer = Buffer.from('export const ɵɵngDeclareDirective = () => {};', 'utf-8'); + const result = await transformer.transformData( + 'node_modules/@angular/core/fesm2022/core.mjs', + inputBuffer, + false, + ); + + expect(result).toBe(inputBuffer); + }); + + it('should bypass worker and skip linking for TypeScript file extensions (.ts, .tsx, .mts, .cts)', async () => { + transformer = new JavaScriptTransformer( + { + sourcemap: false, + }, + 1, + ); + + const inputBuffer = Buffer.from('export const ɵɵngDeclareDirective = () => {};', 'utf-8'); + + for (const ext of ['.ts', '.tsx', '.mts', '.cts']) { + const result = await transformer.transformData(`src/app/directive${ext}`, inputBuffer, false); + + expect(result).toBe(inputBuffer); + } + }); + + it('should not exclude packages with similar prefixes such as @angular/compiler-cli', async () => { + transformer = new JavaScriptTransformer( + { + sourcemap: false, + }, + 1, + ); + + const input = ` + import * as i0 from "@angular/core"; + export class MyDirective {} + MyDirective.ɵdir = i0.ɵɵngDeclareDirective({ + minVersion: "12.0.0", + version: "14.0.0", + ngImport: i0, + type: MyDirective, + selector: "[my-dir]" + }); + `; + + const result = await transformer.transformData( + 'node_modules/@angular/compiler-cli/test.js', + input, + false, + ); + const text = Buffer.from(result).toString('utf-8'); + + expect(text).not.toContain('i0.ɵɵngDeclareDirective'); + }); +}); diff --git a/packages/angular/build/src/tools/esbuild/license-extractor.ts b/packages/angular/build/src/tools/esbuild/license-extractor.ts index 890ebdd9826f..4c73012033bb 100644 --- a/packages/angular/build/src/tools/esbuild/license-extractor.ts +++ b/packages/angular/build/src/tools/esbuild/license-extractor.ts @@ -60,124 +60,132 @@ const EXTRACTION_FILE_SEPARATOR = '-'.repeat(80) + '\n'; * @param rootDirectory The root directory of the workspace. * @returns A string containing the content of the output licenses file. */ -export async function extractLicenses(metafile: Metafile, rootDirectory: string) { +export async function extractLicenses( + metafiles: Metafile[], + rootDirectory: string, +): Promise { let extractedLicenseContent = `${EXTRACTION_FILE_HEADER}\n${EXTRACTION_FILE_SEPARATOR}`; const seenPaths = new Set(); const seenPackageDirectories = new Set(); const seenPackages = new Set(); - for (const entry of Object.values(metafile.outputs)) { - for (const [inputPath, { bytesInOutput }] of Object.entries(entry.inputs)) { - // Skip if not included in output - if (bytesInOutput <= 0) { - continue; - } - - // Skip already processed paths - if (seenPaths.has(inputPath)) { - continue; - } - seenPaths.add(inputPath); + for (const metafile of metafiles) { + for (const entry of Object.values(metafile.outputs)) { + for (const [inputPath, { bytesInOutput }] of Object.entries(entry.inputs)) { + // Skip if not included in output + if (bytesInOutput <= 0) { + continue; + } - // Skip non-package paths - if (!inputPath.includes(NODE_MODULE_SEGMENT)) { - continue; - } + // Skip already processed paths + if (seenPaths.has(inputPath)) { + continue; + } + seenPaths.add(inputPath); - // Extract the package name from the path - let baseDirectory = path.join(rootDirectory, inputPath); - let nameOrScope, nameOrFile; - let found = false; - while (baseDirectory !== path.dirname(baseDirectory)) { - const segment = path.basename(baseDirectory); - if (segment === NODE_MODULE_SEGMENT) { - found = true; - break; + // Skip non-package paths + if (!inputPath.includes(NODE_MODULE_SEGMENT)) { + continue; } - nameOrFile = nameOrScope; - nameOrScope = segment; - baseDirectory = path.dirname(baseDirectory); - } + // Extract the package name from the path + let baseDirectory = path.join(rootDirectory, inputPath); + let nameOrScope, nameOrFile; + let found = false; + while (baseDirectory !== path.dirname(baseDirectory)) { + const segment = path.basename(baseDirectory); + if (segment === NODE_MODULE_SEGMENT) { + found = true; + break; + } - // Skip non-package path edge cases that are not caught in the includes check above - if (!found || !nameOrScope) { - continue; - } + nameOrFile = nameOrScope; + nameOrScope = segment; + baseDirectory = path.dirname(baseDirectory); + } - const packageName = nameOrScope.startsWith('@') - ? `${nameOrScope}/${nameOrFile}` - : nameOrScope; - const packageDirectory = path.join(baseDirectory, packageName); + // Skip non-package path edge cases that are not caught in the includes check above + if (!found || !nameOrScope) { + continue; + } - if (seenPackageDirectories.has(packageDirectory)) { - continue; - } - seenPackageDirectories.add(packageDirectory); - - // Load the package's metadata to find the package's name, version, and license type - const packageJsonPath = path.join(packageDirectory, 'package.json'); - let packageJson; - try { - packageJson = JSON.parse(await readFile(packageJsonPath, 'utf-8')) as { - name: string; - version: string; - // The object form is deprecated and should only be present in old packages - license?: string | { type: string }; - }; - } catch { - // Invalid package - continue; - } + const packageName = nameOrScope.startsWith('@') + ? `${nameOrScope}/${nameOrFile}` + : nameOrScope; + const packageDirectory = path.join(baseDirectory, packageName); - // Skip already processed packages - const packageId = `${packageName}@${packageJson.version}`; - if (seenPackages.has(packageId)) { - continue; - } - seenPackages.add(packageId); - - // Attempt to find license text inside package - let licenseText = ''; - if ( - typeof packageJson.license === 'string' && - packageJson.license.toUpperCase().startsWith(CUSTOM_LICENSE_TEXT) - ) { - // Attempt to load the package's custom license - let customLicensePath; - const customLicenseFile = path.normalize( - packageJson.license.slice(CUSTOM_LICENSE_TEXT.length).trim(), - ); - if (customLicenseFile.startsWith('..') || path.isAbsolute(customLicenseFile)) { - // Path is attempting to access files outside of the package - // TODO: Issue warning? - } else { - customLicensePath = path.join(packageDirectory, customLicenseFile); - try { - licenseText = await readFile(customLicensePath, 'utf-8'); - } catch {} + if (seenPackageDirectories.has(packageDirectory)) { + continue; + } + seenPackageDirectories.add(packageDirectory); + + // Load the package's metadata to find the package's name, version, and license type + const packageJsonPath = path.join(packageDirectory, 'package.json'); + let packageJson; + try { + packageJson = JSON.parse(await readFile(packageJsonPath, 'utf-8')) as { + name: string; + version: string; + // The object form is deprecated and should only be present in old packages + license?: string | { type: string }; + }; + } catch { + // Invalid package + continue; } - } else { - // Search for a license file within the root of the package - const entries = await readdir(packageDirectory, { withFileTypes: true }).catch(() => []); - for (const entry of entries) { - if ((entry.isFile() || entry.isSymbolicLink()) && LICENSE_FILE_REGEXP.test(entry.name)) { - const packageLicensePath = path.join(packageDirectory, entry.name); + // Skip already processed packages + const packageId = `${packageName}@${packageJson.version}`; + if (seenPackages.has(packageId)) { + continue; + } + seenPackages.add(packageId); + + // Attempt to find license text inside package + let licenseText = ''; + if ( + typeof packageJson.license === 'string' && + packageJson.license.toUpperCase().startsWith(CUSTOM_LICENSE_TEXT) + ) { + // Attempt to load the package's custom license + let customLicensePath; + const customLicenseFile = path.normalize( + packageJson.license.slice(CUSTOM_LICENSE_TEXT.length).trim(), + ); + if (customLicenseFile.startsWith('..') || path.isAbsolute(customLicenseFile)) { + // Path is attempting to access files outside of the package + // TODO: Issue warning? + } else { + customLicensePath = path.join(packageDirectory, customLicenseFile); try { - licenseText = await readFile(packageLicensePath, 'utf-8'); - break; + licenseText = await readFile(customLicensePath, 'utf-8'); } catch {} } + } else { + // Search for a license file within the root of the package + const entries = await readdir(packageDirectory, { withFileTypes: true }).catch(() => []); + + for (const entry of entries) { + if ( + (entry.isFile() || entry.isSymbolicLink()) && + LICENSE_FILE_REGEXP.test(entry.name) + ) { + const packageLicensePath = path.join(packageDirectory, entry.name); + try { + licenseText = await readFile(packageLicensePath, 'utf-8'); + break; + } catch {} + } + } } - } - // Generate the package's license entry in the output content - extractedLicenseContent += `Package: ${packageJson.name}\n`; - extractedLicenseContent += `License: ${JSON.stringify(packageJson.license, null, 2)}\n`; - extractedLicenseContent += `\n${licenseText}\n`; - extractedLicenseContent += EXTRACTION_FILE_SEPARATOR; + // Generate the package's license entry in the output content + extractedLicenseContent += `Package: ${packageJson.name}\n`; + extractedLicenseContent += `License: ${JSON.stringify(packageJson.license, null, 2)}\n`; + extractedLicenseContent += `\n${licenseText}\n`; + extractedLicenseContent += EXTRACTION_FILE_SEPARATOR; + } } } diff --git a/packages/angular/build/src/tools/esbuild/load-result-cache.ts b/packages/angular/build/src/tools/esbuild/load-result-cache.ts index 00f803f2fdc7..402069cff1a5 100644 --- a/packages/angular/build/src/tools/esbuild/load-result-cache.ts +++ b/packages/angular/build/src/tools/esbuild/load-result-cache.ts @@ -12,6 +12,7 @@ import { normalize } from 'node:path'; export interface LoadResultCache { get(path: string): OnLoadResult | Promise | undefined; put(path: string, result: OnLoadResult): Promise; + invalidate(path: string): boolean; readonly watchFiles: ReadonlyArray; } @@ -57,14 +58,35 @@ export class MemoryLoadResultCache implements LoadResultCache { } async put(path: string, result: OnLoadResult): Promise { + const previousWatchFiles = this.#watchFilesPerKey.get(path); + if (result.errors && result.errors.length > 0) { - const previousWatchFiles = this.#watchFilesPerKey.get(path); if (previousWatchFiles) { result.watchFiles = Array.from( new Set([...(result.watchFiles ?? []), ...previousWatchFiles]), ); } - } else if (result.watchFiles && result.watchFiles.length > 0) { + } + + const currentNormalizedWatchFiles = new Set(result.watchFiles?.map(normalize) ?? []); + + // Clean up any previous file dependencies that are no longer referenced + if (previousWatchFiles) { + for (const watchFile of previousWatchFiles) { + const normalizedWatchFile = normalize(watchFile); + if (!currentNormalizedWatchFiles.has(normalizedWatchFile)) { + const affected = this.#fileDependencies.get(normalizedWatchFile); + if (affected) { + affected.delete(path); + if (affected.size === 0) { + this.#fileDependencies.delete(normalizedWatchFile); + } + } + } + } + } + + if (result.watchFiles && result.watchFiles.length > 0) { this.#watchFilesPerKey.set(path, [...result.watchFiles]); } else { this.#watchFilesPerKey.delete(path); @@ -87,18 +109,15 @@ export class MemoryLoadResultCache implements LoadResultCache { invalidate(path: string): boolean { const affectedPaths = this.#fileDependencies.get(path); - let found = false; + if (!affectedPaths) { + return false; + } - if (affectedPaths) { - for (const affected of affectedPaths) { - if (this.#loadResults.delete(affected)) { - found = true; - } - } - this.#fileDependencies.delete(path); + for (const affected of affectedPaths) { + this.#loadResults.delete(affected); } - return found; + return true; } get watchFiles(): string[] { diff --git a/packages/angular/build/src/tools/esbuild/load-result-cache_spec.ts b/packages/angular/build/src/tools/esbuild/load-result-cache_spec.ts index fb0396aea829..8a8682beed61 100644 --- a/packages/angular/build/src/tools/esbuild/load-result-cache_spec.ts +++ b/packages/angular/build/src/tools/esbuild/load-result-cache_spec.ts @@ -82,4 +82,38 @@ describe('MemoryLoadResultCache', () => { expect(invalidated).toBeTrue(); expect(cache.get('file:/test/styles.css')).toBeUndefined(); }); + + it('should clean up previous file dependencies when put updates watchFiles', async () => { + const initialResult = { + contents: 'body { color: red; }', + loader: 'css' as const, + watchFiles: ['/test/styles.css', '/test/old-dep.json'], + }; + + await cache.put('file:/test/styles.css', initialResult); + expect(cache.watchFiles).toContain('/test/old-dep.json'); + + // Update with new watch files (not containing old-dep.json) + const updatedResult = { + contents: 'body { color: blue; }', + loader: 'css' as const, + watchFiles: ['/test/styles.css', '/test/new-dep.json'], + }; + + await cache.put('file:/test/styles.css', updatedResult); + expect(cache.watchFiles).not.toContain('/test/old-dep.json'); + expect(cache.watchFiles).toContain('/test/new-dep.json'); + + // Invalidating old dependency should not affect the cache + expect(cache.invalidate('/test/old-dep.json')).toBeFalse(); + expect(cache.get('file:/test/styles.css')).toBe(updatedResult); + + // Invalidating new dependency should invalidate the cache + expect(cache.invalidate('/test/new-dep.json')).toBeTrue(); + expect(cache.get('file:/test/styles.css')).toBeUndefined(); + // Invalidating a file marks its cached results stale, but preserves watch file tracking + // so the file watcher continues monitoring the dependency for subsequent changes until + // a new build pass (via put) updates the active dependencies. + expect(cache.watchFiles).toContain('/test/new-dep.json'); + }); }); diff --git a/packages/angular/build/src/tools/esbuild/persistent-load-result-cache.ts b/packages/angular/build/src/tools/esbuild/persistent-load-result-cache.ts index 82f2b70ed25d..9d1e3ff5cfba 100644 --- a/packages/angular/build/src/tools/esbuild/persistent-load-result-cache.ts +++ b/packages/angular/build/src/tools/esbuild/persistent-load-result-cache.ts @@ -28,10 +28,10 @@ */ import type { Loader, OnLoadResult, PartialMessage } from 'esbuild'; -import { createHash } from 'node:crypto'; import { readFile, stat } from 'node:fs/promises'; import { isAbsolute } from 'node:path'; import { fileURLToPath } from 'node:url'; +import { calculateHash, createContentHash } from '../../utils/hash'; import type { Cache as PersistentCacheStore } from './cache'; import { LoadResultCache, MemoryLoadResultCache } from './load-result-cache'; @@ -70,10 +70,6 @@ export interface CachedLoadResultEntry { errors?: PartialMessage[]; } -function hashContent(content: string | Uint8Array): string { - return createHash('sha256').update(content).digest('hex'); -} - /** * Calculates a unique cache key by updating the hash incrementally. * This prevents implicit string coercion of large binary content buffers. @@ -83,13 +79,14 @@ function calculateCacheKey( path: string, content: string | Uint8Array, ): string { - return createHash('sha256') - .update(globalConfigHash) - .update('\0') - .update(path) - .update('\0') - .update(content) - .digest('hex'); + const hasher = createContentHash(); + hasher.update(globalConfigHash); + hasher.update('\0'); + hasher.update(path); + hasher.update('\0'); + hasher.update(content); + + return hasher.digest(); } /** @@ -193,7 +190,7 @@ async function validateAndHealCacheEntry( // 3. Slow Path for dependencies: content hash fallback const currentContent = await readFile(filePath); - const currentHash = hashContent(currentContent); + const currentHash = calculateHash(currentContent); if (currentHash === expected.hash) { // Heal cache entry with new metadata watchFilesMetadata[filePath] = { @@ -244,8 +241,9 @@ async function computeMetadataForWatchFiles( knownContent !== undefined ? knownContent : readFile(filePath), stat(filePath), ]); + const hash = calculateHash(content); watchFilesMetadata[filePath] = { - hash: hashContent(content), + hash, mtimeMs: stats.mtimeMs, size: stats.size, }; diff --git a/packages/angular/build/src/tools/esbuild/persistent-load-result-cache_spec.ts b/packages/angular/build/src/tools/esbuild/persistent-load-result-cache_spec.ts index b99c0a76a74e..f8ecc0611818 100644 --- a/packages/angular/build/src/tools/esbuild/persistent-load-result-cache_spec.ts +++ b/packages/angular/build/src/tools/esbuild/persistent-load-result-cache_spec.ts @@ -10,6 +10,7 @@ import type { OnLoadResult } from 'esbuild'; import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; +import { initializeHash } from '../../utils/hash'; import type { Cache as PersistentCacheStore } from './cache'; import { type CachedLoadResultEntry, @@ -40,6 +41,10 @@ describe('PersistentLoadResultCache', () => { let tmpDir: string; let file1: string; + beforeAll(async () => { + await initializeHash(); + }); + beforeEach(() => { tmpDir = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), 'persistent-cache-test-')); file1 = path.join(tmpDir, 'test.js'); diff --git a/packages/angular/build/src/tools/esbuild/sqlite-cache-store.ts b/packages/angular/build/src/tools/esbuild/sqlite-cache-store.ts index 3d594668d44f..4f50515749df 100644 --- a/packages/angular/build/src/tools/esbuild/sqlite-cache-store.ts +++ b/packages/angular/build/src/tools/esbuild/sqlite-cache-store.ts @@ -15,6 +15,8 @@ export class SqliteCacheStore implements PersistentCacheStore { #hasStmt: StatementSync | undefined; #setStmt: StatementSync | undefined; #updateAccessedStmt: StatementSync | undefined; + readonly #pendingAccessedKeys = new Set(); + #flushTimeout: NodeJS.Timeout | undefined; constructor( readonly cachePath: string, @@ -29,6 +31,9 @@ export class SqliteCacheStore implements PersistentCacheStore { this.#db.exec('PRAGMA auto_vacuum = FULL;'); this.#db.exec('PRAGMA journal_mode = WAL;'); this.#db.exec('PRAGMA synchronous = NORMAL;'); + this.#db.exec('PRAGMA busy_timeout = 5000;'); + this.#db.exec('PRAGMA temp_store = MEMORY;'); + this.#db.exec('PRAGMA mmap_size = 268435456;'); this.#db.exec( 'CREATE TABLE IF NOT EXISTS cache (key TEXT PRIMARY KEY, value TEXT, last_accessed INTEGER NOT NULL) WITHOUT ROWID;', ); @@ -46,13 +51,51 @@ export class SqliteCacheStore implements PersistentCacheStore { return this.#db; } + #queueAccessUpdate(key: string): void { + this.#pendingAccessedKeys.add(key); + + if (this.#pendingAccessedKeys.size >= 100) { + this.#flushAccessUpdates(); + } else if (!this.#flushTimeout) { + this.#flushTimeout = setTimeout(() => this.#flushAccessUpdates(), 500); + this.#flushTimeout.unref?.(); + } + } + + #flushAccessUpdates(): void { + if (this.#flushTimeout) { + clearTimeout(this.#flushTimeout); + this.#flushTimeout = undefined; + } + + if (!this.#db || this.#pendingAccessedKeys.size === 0 || !this.#updateAccessedStmt) { + return; + } + + try { + this.#db.exec('BEGIN IMMEDIATE TRANSACTION;'); + for (const key of this.#pendingAccessedKeys) { + this.#updateAccessedStmt.run(key); + } + this.#db.exec('COMMIT;'); + } catch { + try { + this.#db.exec('ROLLBACK;'); + } catch { + // Ignore rollback errors if transaction was not active + } + } finally { + this.#pendingAccessedKeys.clear(); + } + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any async get(key: string): Promise { this.#ensureDb(); const row = this.#getStmt?.get(key) as { value: string } | undefined; if (row) { - this.#updateAccessedStmt?.run(key); + this.#queueAccessUpdate(key); try { return JSON.parse(row.value); @@ -72,6 +115,7 @@ export class SqliteCacheStore implements PersistentCacheStore { async set(key: string, value: unknown): Promise { this.#ensureDb(); + this.#pendingAccessedKeys.delete(key); this.#setStmt?.run(key, JSON.stringify(value)); return this; @@ -84,6 +128,9 @@ export class SqliteCacheStore implements PersistentCacheStore { close(): void { if (this.#db) { try { + // Flush any pending access updates in one transaction before pruning + this.#flushAccessUpdates(); + // 1. Delete items older than N days this.#db .prepare("DELETE FROM cache WHERE last_accessed < unixepoch('now', ?);") @@ -103,6 +150,12 @@ export class SqliteCacheStore implements PersistentCacheStore { } catch { // Pruning errors should not block build success } finally { + if (this.#flushTimeout) { + clearTimeout(this.#flushTimeout); + this.#flushTimeout = undefined; + } + this.#pendingAccessedKeys.clear(); + this.#getStmt = undefined; this.#hasStmt = undefined; this.#setStmt = undefined; diff --git a/packages/angular/build/src/tools/esbuild/stylesheets/sass-language.ts b/packages/angular/build/src/tools/esbuild/stylesheets/sass-language.ts index 4bccfb3adba4..92c75d76d7ec 100644 --- a/packages/angular/build/src/tools/esbuild/stylesheets/sass-language.ts +++ b/packages/angular/build/src/tools/esbuild/stylesheets/sass-language.ts @@ -9,26 +9,27 @@ import type { OnLoadResult, PartialMessage, PartialNote, ResolveResult } from 'esbuild'; import { dirname, join } from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; -import type { CanonicalizeContext, CompileResult, Exception, Syntax } from 'sass'; -import type { SassWorkerImplementation } from '../../sass/sass-service'; +import type { CanonicalizeContext, CompileResult, Exception, Syntax } from 'sass-embedded'; +import { useSassWorker } from '../../../utils/environment-options'; +import type { SassServiceImplementation } from '../../sass/sass-service'; import { MemoryCache } from '../cache'; import { StylesheetLanguage, StylesheetPluginOptions } from './stylesheet-plugin-factory'; -let sassWorkerPool: SassWorkerImplementation | undefined; -let sassWorkerPoolPromise: Promise | undefined; +let sassService: SassServiceImplementation | undefined; +let sassServicePromise: Promise | undefined; function isSassException(error: unknown): error is Exception { return !!error && typeof error === 'object' && 'sassMessage' in error; } export function shutdownSassWorkerPool(): void { - if (sassWorkerPool) { - void sassWorkerPool.close(); - sassWorkerPool = undefined; - } else if (sassWorkerPoolPromise) { - void sassWorkerPoolPromise.then(shutdownSassWorkerPool); + if (sassService) { + void sassService.close(); + sassService = undefined; + } else if (sassServicePromise) { + void sassServicePromise.then(shutdownSassWorkerPool); } - sassWorkerPoolPromise = undefined; + sassServicePromise = undefined; } export const SassStylesheetLanguage = Object.freeze({ @@ -78,13 +79,21 @@ async function compileString( resolveUrl: (url: string, options: CanonicalizeContext) => Promise, ): Promise { // Lazily load Sass when a Sass file is found - if (sassWorkerPool === undefined) { - if (sassWorkerPoolPromise === undefined) { - sassWorkerPoolPromise = import('../../sass/sass-service').then( - (sassService) => new sassService.SassWorkerImplementation(true), - ); + if (sassService === undefined) { + if (sassServicePromise === undefined) { + sassServicePromise = useSassWorker + ? import('../../sass/sass-worker-implementation').then( + (sassService) => new sassService.SassWorkerImplementation(true), + ) + : import('../../sass/sass-async-compiler-implementation').then( + (sassService) => new sassService.SassAsyncCompilerImplementation(), + ); + } + try { + sassService = await sassServicePromise; + } finally { + sassServicePromise = undefined; } - sassWorkerPool = await sassWorkerPoolPromise; } // Cache is currently local to individual compile requests. @@ -99,7 +108,7 @@ async function compileString( const { silenceDeprecations, futureDeprecations, fatalDeprecations } = options.sass ?? {}; try { - const { css, sourceMap, loadedUrls } = await sassWorkerPool.compileStringAsync(data, { + const { css, sourceMap, loadedUrls } = await sassService.compileStringAsync(data, { url: pathToFileURL(filePath), style: 'expanded', syntax, @@ -226,7 +235,7 @@ function* extractFilesFromStack(stack: string): Iterable { } if (path) { - // Stack paths from dart-sass are relative to the current working directory (not input file or workspace root) + // Stack paths from sass are relative to the current working directory (not input file or workspace root) yield join(cwd, path); } } diff --git a/packages/angular/build/src/tools/esbuild/stylesheets/stylesheet-cache-key.ts b/packages/angular/build/src/tools/esbuild/stylesheets/stylesheet-cache-key.ts index 75b0915daa64..22397ab189b3 100644 --- a/packages/angular/build/src/tools/esbuild/stylesheets/stylesheet-cache-key.ts +++ b/packages/angular/build/src/tools/esbuild/stylesheets/stylesheet-cache-key.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import { createHash } from 'node:crypto'; +import { calculateHash } from '../../../utils/hash'; import type { BundleStylesheetOptions } from './bundle-options'; /** @@ -39,34 +39,30 @@ export function calculateGlobalStylesheetConfigHash( options: BundleStylesheetOptions, packageVersion: string = '', ): string { - return createHash('sha256') - .update( - JSON.stringify({ - optimization: options.optimization, - sourcemap: options.sourcemap, - sourcesContent: options.sourcesContent, - includePaths: options.includePaths, - sassOptions: options.sass - ? { - futureDeprecations: options.sass.futureDeprecations, - fatalDeprecations: options.sass.fatalDeprecations, - silenceDeprecations: options.sass.silenceDeprecations, - } - : undefined, - target: options.target, - publicPath: options.publicPath, - outputNames: options.outputNames, - inlineFonts: options.inlineFonts, - preserveSymlinks: options.preserveSymlinks, - externalDependencies: options.externalDependencies, - postcssConfig: options.postcssConfiguration?.configPath - ? options.postcssConfiguration.configPath - : '', - tailwindConfig: options.tailwindConfiguration?.file - ? options.tailwindConfiguration.file - : '', - packageVersion, - }), - ) - .digest('hex'); + return calculateHash( + JSON.stringify({ + optimization: options.optimization, + sourcemap: options.sourcemap, + sourcesContent: options.sourcesContent, + includePaths: options.includePaths, + sassOptions: options.sass + ? { + futureDeprecations: options.sass.futureDeprecations, + fatalDeprecations: options.sass.fatalDeprecations, + silenceDeprecations: options.sass.silenceDeprecations, + } + : undefined, + target: options.target, + publicPath: options.publicPath, + outputNames: options.outputNames, + inlineFonts: options.inlineFonts, + preserveSymlinks: options.preserveSymlinks, + externalDependencies: options.externalDependencies, + postcssConfig: options.postcssConfiguration?.configPath + ? options.postcssConfiguration.configPath + : '', + tailwindConfig: options.tailwindConfiguration?.file ? options.tailwindConfiguration.file : '', + packageVersion, + }), + ); } diff --git a/packages/angular/build/src/tools/esbuild/stylesheets/stylesheet-cache-key_spec.ts b/packages/angular/build/src/tools/esbuild/stylesheets/stylesheet-cache-key_spec.ts index 4e674b968fdd..c524a2d0c36b 100644 --- a/packages/angular/build/src/tools/esbuild/stylesheets/stylesheet-cache-key_spec.ts +++ b/packages/angular/build/src/tools/esbuild/stylesheets/stylesheet-cache-key_spec.ts @@ -6,10 +6,15 @@ * found in the LICENSE file at https://angular.dev/license */ +import { initializeHash } from '../../../utils/hash'; import type { BundleStylesheetOptions } from './bundle-options'; import { calculateGlobalStylesheetConfigHash } from './stylesheet-cache-key'; describe('Stylesheet Global Config Hash', () => { + beforeAll(async () => { + await initializeHash(); + }); + const baseOptions: BundleStylesheetOptions = { workspaceRoot: '/root', optimization: true, diff --git a/packages/angular/build/src/tools/esbuild/stylesheets/stylesheet-plugin-factory.ts b/packages/angular/build/src/tools/esbuild/stylesheets/stylesheet-plugin-factory.ts index 2c40e007350e..79337fe1652e 100644 --- a/packages/angular/build/src/tools/esbuild/stylesheets/stylesheet-plugin-factory.ts +++ b/packages/angular/build/src/tools/esbuild/stylesheets/stylesheet-plugin-factory.ts @@ -11,7 +11,7 @@ import assert from 'node:assert'; import { readFile } from 'node:fs/promises'; import { createRequire } from 'node:module'; import { dirname, extname } from 'node:path'; -import type { Options } from 'sass'; +import type { Options } from 'sass-embedded'; import { glob } from 'tinyglobby'; import { assertIsError } from '../../../utils/error'; import type { PostcssConfiguration } from '../../../utils/postcss-configuration'; diff --git a/packages/angular/build/src/tools/esbuild/target.ts b/packages/angular/build/src/tools/esbuild/target.ts new file mode 100644 index 000000000000..2925905276b7 --- /dev/null +++ b/packages/angular/build/src/tools/esbuild/target.ts @@ -0,0 +1,116 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { coerce, compare, minVersion } from 'semver'; + +/** + * Compares two target version strings. + * + * This function is used to determine the lowest version for a given browser target. + * + * @param a The first version string. + * @param b The second version string. + * @returns A negative value if `a` is lower than `b`, a positive value if `a` is higher than `b`, and 0 if they are equal. + */ +function compareTargetVersions(a: string, b: string): number { + const aVersion = coerce(a); + const bVersion = coerce(b); + + if (!aVersion || !bVersion) { + return aVersion ? -1 : bVersion ? 1 : 0; + } + + return compare(aVersion, bVersion); +} + +// https://esbuild.github.io/api/#target +const ESBUILD_SUPPORTED_BROWSERS: ReadonlySet = new Set([ + 'chrome', + 'edge', + 'firefox', + 'ie', + 'ios', + 'node', + 'opera', + 'safari', +]); + +/** + * Transform browserlists result to esbuild target. + * + * Only the lowest version for each browser is returned to avoid issues with esbuild and rolldown + * when multiple versions of the same target engine are specified. + * + * @see https://esbuild.github.io/api/#target + * @see https://github.com/evanw/esbuild/issues/4509 + * @see https://github.com/rolldown/rolldown/issues/10633 + */ +export function transformSupportedBrowsersToTargets(supportedBrowsers: string[]): string[] { + const browsers = new Map(); + + for (const browser of supportedBrowsers) { + let [browserName, version] = browser.toLowerCase().split(' '); + if (!browserName || !version) { + continue; + } + + // browserslist uses the name `ios_saf` for iOS Safari whereas esbuild uses `ios` + if (browserName === 'ios_saf') { + browserName = 'ios'; + } + + if (!ESBUILD_SUPPORTED_BROWSERS.has(browserName)) { + continue; + } + + // browserslist uses ranges `15.2-15.3` versions but only the lowest is required + // to perform minimum supported feature checks. esbuild also expects a single version. + [version] = version.split('-'); + + if (browserName === 'safari' && version === 'tp') { + // esbuild only supports numeric versions so `TP` is converted to a high number (999) since + // a Technology Preview (TP) of Safari is assumed to support all currently known features. + version = '999'; + } else if (!version.includes('.')) { + // A lone major version is considered by esbuild to include all minor versions. However, + // browserslist does not and is also inconsistent in its `.0` version naming. For example, + // Safari 15.0 is named `safari 15` but Safari 16.0 is named `safari 16.0`. + version += '.0'; + } + + const current = browsers.get(browserName); + if (!current || compareTargetVersions(version, current) < 0) { + browsers.set(browserName, version); + } + } + + return Array.from(browsers, ([browserName, version]) => browserName + version); +} + +const SUPPORTED_NODE_VERSIONS = '0.0.0-ENGINES-NODE'; + +/** + * Transform supported Node.js versions to esbuild target. + * + * Only the lowest Node.js version is returned to avoid issues with esbuild and rolldown + * when multiple versions of the same target engine are specified. + * + * @see https://esbuild.github.io/api/#target + * @see https://github.com/evanw/esbuild/issues/4509 + * @see https://github.com/rolldown/rolldown/issues/10633 + */ +export function getSupportedNodeTargets(): string[] { + if (SUPPORTED_NODE_VERSIONS.charAt(0) === '0') { + // Unlike `pkg_npm`, `ts_library` which is used to run unit tests does not support substitutions. + return []; + } + + const parsed = minVersion(SUPPORTED_NODE_VERSIONS); + + return parsed ? ['node' + parsed.version] : []; +} diff --git a/packages/angular/build/src/tools/esbuild/target_spec.ts b/packages/angular/build/src/tools/esbuild/target_spec.ts new file mode 100644 index 000000000000..e5a375a1412b --- /dev/null +++ b/packages/angular/build/src/tools/esbuild/target_spec.ts @@ -0,0 +1,80 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { getSupportedNodeTargets, transformSupportedBrowsersToTargets } from './target'; + +describe('esbuild target', () => { + describe('transformSupportedBrowsersToTargets', () => { + it('should return the smallest version for each browser', () => { + const targets = transformSupportedBrowsersToTargets([ + 'chrome 122', + 'chrome 120', + 'chrome 121', + 'firefox 116', + 'firefox 115', + 'safari 17.0', + 'safari 16.4', + ]); + + expect(targets).toEqual(['chrome120.0', 'firefox115.0', 'safari16.4']); + }); + + it('should handle version ranges and pick the lowest version', () => { + const targets = transformSupportedBrowsersToTargets([ + 'ios_saf 15.4', + 'ios_saf 15.2-15.3', + 'ios_saf 16.0', + ]); + + expect(targets).toEqual(['ios15.2']); + }); + + it('should handle Safari TP (Technology Preview)', () => { + const targetsWithOlderSafari = transformSupportedBrowsersToTargets([ + 'safari TP', + 'safari 16.4', + ]); + expect(targetsWithOlderSafari).toEqual(['safari16.4']); + + const targetsWithOnlyTP = transformSupportedBrowsersToTargets(['safari TP']); + expect(targetsWithOnlyTP).toEqual(['safari999']); + }); + + it('should ignore browsers not supported by esbuild', () => { + const targets = transformSupportedBrowsersToTargets([ + 'android 4.4', + 'samsung 22', + 'kaios 2.5', + 'chrome 115', + ]); + + expect(targets).toEqual(['chrome115.0']); + }); + + it('should return empty array for empty supportedBrowsers', () => { + const targets = transformSupportedBrowsersToTargets([]); + expect(targets).toEqual([]); + }); + + it('should handle malformed or incomplete browser strings gracefully', () => { + const targets = transformSupportedBrowsersToTargets(['chrome', 'firefox ', '']); + expect(targets).toEqual([]); + }); + + it('should handle single major versions by appending .0', () => { + const targets = transformSupportedBrowsersToTargets(['chrome 120', 'edge 120']); + expect(targets).toEqual(['chrome120.0', 'edge120.0']); + }); + }); + + describe('getSupportedNodeTargets', () => { + it('should return empty array when node versions are not stamped', () => { + expect(getSupportedNodeTargets()).toEqual([]); + }); + }); +}); diff --git a/packages/angular/build/src/tools/esbuild/utils.ts b/packages/angular/build/src/tools/esbuild/utils.ts index 6d76623f1493..7d9dce4a5522 100644 --- a/packages/angular/build/src/tools/esbuild/utils.ts +++ b/packages/angular/build/src/tools/esbuild/utils.ts @@ -12,7 +12,6 @@ import { Listr } from 'listr2'; import { basename, join } from 'node:path'; import { pathToFileURL } from 'node:url'; import { brotliCompress } from 'node:zlib'; -import { coerce } from 'semver'; import { NormalizedApplicationBuildOptions } from '../../builders/application/options'; import { OutputMode } from '../../builders/application/schema'; import { BudgetCalculatorResult } from '../../utils/bundle-calculator'; @@ -26,7 +25,7 @@ import { import { type BuildOutputFile, BuildOutputFileType, type InitialFileRecord } from './bundler-files'; export function logBuildStats( - metafile: Metafile, + metafiles: Metafile[], outputFiles: BuildOutputFile[], initial: Map, budgetFailures: BudgetCalculatorResult[] | undefined, @@ -66,12 +65,12 @@ export function logBuildStats( } // Skip logging external component stylesheets used for HMR - if (metafile.outputs[file] && 'ng-component' in metafile.outputs[file]) { + if (metafiles.some((mf) => mf.outputs[file] && 'ng-component' in mf.outputs[file])) { componentStyleChange = true; continue; } - const name = initial.get(file)?.name ?? getChunkNameFromMetafile(metafile, file); + const name = initial.get(file)?.name ?? getChunkNameFromMetafile(metafiles, file); const stat: BundleStats = { initial: initial.has(file), stats: [file, name ?? '-', size, estimatedTransferSizes?.get(file) ?? '-'], @@ -109,9 +108,15 @@ export function logBuildStats( return ''; } -export function getChunkNameFromMetafile(metafile: Metafile, file: string): string | undefined { - if (metafile.outputs[file]?.entryPoint) { - return getEntryPointName(metafile.outputs[file].entryPoint); +export function getChunkNameFromMetafile( + metafiles: Metafile[] | Metafile, + file: string, +): string | undefined { + const metafileArray = Array.isArray(metafiles) ? metafiles : [metafiles]; + for (const metafile of metafileArray) { + if (metafile.outputs[file]?.entryPoint) { + return getEntryPointName(metafile.outputs[file].entryPoint); + } } } @@ -213,7 +218,7 @@ export async function emitFilesToDisk( writeFileCallback: (file: T) => Promise, ): Promise { // Write files in groups of MAX_CONCURRENT_WRITES to avoid too many open files - for (let fileIndex = 0; fileIndex < files.length; ) { + for (let fileIndex = 0; fileIndex < files.length;) { const groupMax = Math.min(fileIndex + MAX_CONCURRENT_WRITES, files.length); const actions = []; @@ -225,71 +230,6 @@ export async function emitFilesToDisk( } } -/** - * Transform browserlists result to esbuild target. - * @see https://esbuild.github.io/api/#target - */ -export function transformSupportedBrowsersToTargets(supportedBrowsers: string[]): string[] { - const transformed: string[] = []; - - // https://esbuild.github.io/api/#target - const esBuildSupportedBrowsers = new Set([ - 'chrome', - 'edge', - 'firefox', - 'ie', - 'ios', - 'node', - 'opera', - 'safari', - ]); - - for (const browser of supportedBrowsers) { - let [browserName, version] = browser.toLowerCase().split(' '); - - // browserslist uses the name `ios_saf` for iOS Safari whereas esbuild uses `ios` - if (browserName === 'ios_saf') { - browserName = 'ios'; - } - - // browserslist uses ranges `15.2-15.3` versions but only the lowest is required - // to perform minimum supported feature checks. esbuild also expects a single version. - [version] = version.split('-'); - - if (esBuildSupportedBrowsers.has(browserName)) { - if (browserName === 'safari' && version === 'tp') { - // esbuild only supports numeric versions so `TP` is converted to a high number (999) since - // a Technology Preview (TP) of Safari is assumed to support all currently known features. - version = '999'; - } else if (!version.includes('.')) { - // A lone major version is considered by esbuild to include all minor versions. However, - // browserslist does not and is also inconsistent in its `.0` version naming. For example, - // Safari 15.0 is named `safari 15` but Safari 16.0 is named `safari 16.0`. - version += '.0'; - } - - transformed.push(browserName + version); - } - } - - return transformed; -} - -const SUPPORTED_NODE_VERSIONS = '0.0.0-ENGINES-NODE'; - -/** - * Transform supported Node.js versions to esbuild target. - * @see https://esbuild.github.io/api/#target - */ -export function getSupportedNodeTargets(): string[] { - if (SUPPORTED_NODE_VERSIONS.charAt(0) === '0') { - // Unlike `pkg_npm`, `ts_library` which is used to run unit tests does not support substitutions. - return []; - } - - return SUPPORTED_NODE_VERSIONS.split('||').map((v) => 'node' + coerce(v)?.version); -} - interface BuildManifest { errors: string[]; warnings: string[]; diff --git a/packages/angular/build/src/tools/esbuild/watcher.ts b/packages/angular/build/src/tools/esbuild/watcher.ts index cf9e1d94cb87..b6e26f5c72af 100644 --- a/packages/angular/build/src/tools/esbuild/watcher.ts +++ b/packages/angular/build/src/tools/esbuild/watcher.ts @@ -6,7 +6,12 @@ * found in the LICENSE file at https://angular.dev/license */ -import WatchPack from 'watchpack'; +import type * as ParcelWatcher from '@parcel/watcher'; +import type * as Chokidar from 'chokidar'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import picomatch from 'picomatch'; +import { toPosixPath } from '../../utils/path'; export class ChangedFiles { readonly added = new Set(); @@ -14,7 +19,7 @@ export class ChangedFiles { readonly removed = new Set(); get all(): string[] { - return [...this.added, ...this.modified, ...this.removed]; + return Array.from(new Set([...this.added, ...this.modified, ...this.removed])); } toDebugString(): string { @@ -34,102 +39,581 @@ export interface BuildWatcher extends AsyncIterableIterator { close(): Promise; } -export function createWatcher(options?: { +export interface WatcherOptions { polling?: boolean; interval?: number; ignored?: string[]; followSymlinks?: boolean; -}): BuildWatcher { - const watcher = new WatchPack({ - poll: options?.polling ? (options?.interval ?? true) : false, - ignored: options?.ignored, - followSymlinks: options?.followSymlinks, - aggregateTimeout: 250, - }); - const watchedFiles = new Set(); + cwd?: string; +} + +/** + * Probes the filesystem at the specified target directory to determine whether it is case-sensitive. + */ +function isFileSystemCaseSensitive(targetDir: string = process.cwd()): boolean { + try { + const resolved = path.resolve(targetDir); + if (!fs.existsSync(resolved)) { + return process.platform !== 'win32' && process.platform !== 'darwin'; + } + + // Invert the casing of the target directory path. + const altCase = + resolved === resolved.toLowerCase() ? resolved.toUpperCase() : resolved.toLowerCase(); + + // If the path contains no alphabetic characters (e.g. root '/'), invert-casing + // produces the exact same string. Fall back to platform-specific defaults in this case. + if (resolved === altCase) { + return process.platform !== 'win32' && process.platform !== 'darwin'; + } + + // If both the original path and the inverted-casing path exist on disk, + // the filesystem is case-insensitive (returns false). + return !fs.existsSync(altCase); + } catch { + // If an error occurs (e.g., permission denied), default to the platform-specific + // behavior (case-insensitive on Windows/macOS, sensitive on Linux/Unix). + return process.platform !== 'win32' && process.platform !== 'darwin'; + } +} + +/** + * Normalizes a file system path string to POSIX format (forward slashes '/') + * and strips trailing slashes (except root '/' or Windows drive root 'C:/'). + */ +export function toPosixPathNormalized(pathString: string): string { + let posixPath = toPosixPath(pathString); + if (posixPath.length > 1 && posixPath.endsWith('/') && !/^[a-zA-Z]:\/$/.test(posixPath)) { + posixPath = posixPath.slice(0, -1); + } + + return posixPath; +} + +/** + * Returns a lookup key for set lookups and matching, lowercasing on case-insensitive file systems. + */ +function toLookupKey(posixPath: string, isCaseSensitive: boolean): string { + return isCaseSensitive ? posixPath : posixPath.toLowerCase(); +} - const nextQueue: ((value?: ChangedFiles) => void)[] = []; - let currentChangedFiles: ChangedFiles | undefined; +/** + * Returns the parent directory of a normalized POSIX path, correctly handling Windows drive roots. + */ +export function getDirectoryPath(posixPath: string): string { + const lastSlash = posixPath.lastIndexOf('/'); + if (lastSlash === -1) { + return '.'; + } + const dir = posixPath.slice(0, lastSlash); + if (dir === '' || dir.endsWith(':')) { + return dir + '/'; + } + + return dir; +} + +/** + * Determines whether a file path lookup key or any of its parent directories are present in watchedFiles. + */ +function isPathWatched(fileLookupKey: string, watchedFiles: Set): boolean { + if (watchedFiles.has(fileLookupKey)) { + return true; + } + + let current = fileLookupKey; + while (true) { + const parent = getDirectoryPath(current); + if (parent === current) { + break; + } + if (watchedFiles.has(parent)) { + return true; + } + current = parent; + } + + return false; +} + +class WatcherQueue { + private readonly nextQueue: ((value?: ChangedFiles) => void)[] = []; + private currentChangedFiles: ChangedFiles | undefined; + private isClosed = false; + private timeoutId: NodeJS.Timeout | undefined; + + addChange(type: 'added' | 'modified' | 'removed', file: string): void { + if (this.isClosed) { + return; + } + + const changedFiles = (this.currentChangedFiles ??= new ChangedFiles()); + changedFiles[type].add(file); + this.scheduleFlush(); + } - watcher.on('aggregated', (changes, removals) => { - const changedFiles = currentChangedFiles ?? new ChangedFiles(); - for (const file of changes) { - changedFiles.modified.add(file); + addChanges( + changes: ReadonlyArray<{ type: 'added' | 'modified' | 'removed'; file: string }>, + ): void { + if (this.isClosed || changes.length === 0) { + return; } - for (const file of removals) { - changedFiles.removed.add(file); + + const changedFiles = (this.currentChangedFiles ??= new ChangedFiles()); + for (const { type, file } of changes) { + changedFiles[type].add(file); } + this.scheduleFlush(); + } - const next = nextQueue.shift(); - if (next) { - currentChangedFiles = undefined; - next(changedFiles); - } else { - currentChangedFiles = changedFiles; + private scheduleFlush(): void { + if (this.timeoutId) { + clearTimeout(this.timeoutId); } - }); + this.timeoutId = setTimeout(() => { + this.timeoutId = undefined; + this.flush(); + }, 250); + } + + private flush(): void { + if ( + this.currentChangedFiles && + this.currentChangedFiles.all.length > 0 && + this.nextQueue.length > 0 + ) { + const next = this.nextQueue.shift(); + if (next) { + const result = this.currentChangedFiles; + this.currentChangedFiles = undefined; + next(result); + } + } + } + + async next(): Promise> { + if ( + this.currentChangedFiles && + this.currentChangedFiles.all.length > 0 && + this.nextQueue.length === 0 && + !this.timeoutId + ) { + const result = { value: this.currentChangedFiles }; + this.currentChangedFiles = undefined; + + return result; + } + + if (this.isClosed) { + return { done: true, value: undefined as unknown as ChangedFiles }; + } + + return new Promise((resolve) => { + this.nextQueue.push((value) => + resolve(value ? { value } : { done: true, value: undefined as unknown as ChangedFiles }), + ); + }); + } + + close(): void { + if (this.isClosed) { + return; + } + + if (this.timeoutId) { + clearTimeout(this.timeoutId); + this.timeoutId = undefined; + } + + this.isClosed = true; + this.currentChangedFiles = undefined; + + let next; + while ((next = this.nextQueue.shift()) !== undefined) { + next(); + } + } +} + +export async function createWatcher(options?: WatcherOptions): Promise { + if (options?.polling) { + return createChokidarWatcher(options); + } + + try { + const parcelWatcher = await import('@parcel/watcher'); + + return await createParcelWatcher(options, parcelWatcher); + } catch { + return createChokidarWatcher(options); + } +} + +/** + * Checks whether a file path is located inside a parent directory. + * + * Input Expectations: + * - Both `file` and `dir` must be normalized POSIX-style paths (using forward slashes '/'). + * - Both paths must share the same casing normalization (e.g., lowercased on case-insensitive file systems). + */ +export function isPathInside(file: string, dir: string): boolean { + if (file === dir) { + return false; + } + + const dirWithSlash = dir.endsWith('/') ? dir : dir + '/'; + + return file.startsWith(dirWithSlash); +} + +class ParcelExternalManager { + private readonly extraSubscriptions = new Map(); + private readonly pendingSubscriptions = new Map< + string, + Promise + >(); + private readonly externalDirFiles = new Map }>(); + + constructor( + private readonly parcelWatcher: typeof ParcelWatcher, + private readonly options: WatcherOptions | undefined, + private readonly rootDirLookupKey: string, + private readonly handleEvents: (events: ParcelWatcher.Event[]) => void, + ) {} + + async ensureWatched(posixPath: string, lookupKey: string): Promise { + if (isPathInside(lookupKey, this.rootDirLookupKey) || lookupKey === this.rootDirLookupKey) { + return; + } + + const dirPath = getDirectoryPath(posixPath); + const dirKey = getDirectoryPath(lookupKey); + let dirEntry = this.externalDirFiles.get(dirKey); + if (!dirEntry) { + dirEntry = { dirPath, files: new Set() }; + this.externalDirFiles.set(dirKey, dirEntry); + } + dirEntry.files.add(lookupKey); + + await this.ensureDirWatched(dirPath, dirKey); + } + + removeFile(lookupKey: string): void { + if (isPathInside(lookupKey, this.rootDirLookupKey) || lookupKey === this.rootDirLookupKey) { + return; + } + + const dirKey = getDirectoryPath(lookupKey); + const dirEntry = this.externalDirFiles.get(dirKey); + if (dirEntry) { + dirEntry.files.delete(lookupKey); + if (dirEntry.files.size === 0) { + this.externalDirFiles.delete(dirKey); + const sub = this.extraSubscriptions.get(dirKey); + if (sub) { + this.extraSubscriptions.delete(dirKey); + sub.unsubscribe().catch(() => {}); + + for (const [remainingDirKey, remainingDirEntry] of this.externalDirFiles.entries()) { + if (!this.isCoveredByExistingExternal(remainingDirKey)) { + this.ensureDirWatched(remainingDirEntry.dirPath, remainingDirKey).catch(() => {}); + } + } + } + } + } + } + + async close(): Promise { + try { + if (this.pendingSubscriptions.size > 0) { + await Promise.allSettled(Array.from(this.pendingSubscriptions.values())); + } + if (this.extraSubscriptions.size > 0) { + await Promise.allSettled( + Array.from(this.extraSubscriptions.values()).map((sub) => sub.unsubscribe()), + ); + } + } finally { + this.extraSubscriptions.clear(); + this.pendingSubscriptions.clear(); + this.externalDirFiles.clear(); + } + } + + private isCoveredByExistingExternal(dirLookupKey: string): boolean { + for (const existingDir of this.extraSubscriptions.keys()) { + if (dirLookupKey === existingDir || isPathInside(dirLookupKey, existingDir)) { + return true; + } + } + for (const pendingDir of this.pendingSubscriptions.keys()) { + if (dirLookupKey === pendingDir || isPathInside(dirLookupKey, pendingDir)) { + return true; + } + } + + return false; + } + + private async ensureDirWatched(dirPath: string, dirKey: string): Promise { + if (this.isCoveredByExistingExternal(dirKey)) { + return; + } + + const subPromise = this.parcelWatcher.subscribe( + dirPath, + (err, events) => { + if (!err) { + this.handleEvents(events); + } + }, + { + ignore: this.options?.ignored, + }, + ); + + this.pendingSubscriptions.set(dirKey, subPromise); + + try { + const sub = await subPromise; + if (this.externalDirFiles.has(dirKey) && !this.isCoveredByExistingExternal(dirKey)) { + this.extraSubscriptions.set(dirKey, sub); + + // Subsume any nested child subscriptions that are now covered by this parent subscription + for (const [childDir, childSub] of this.extraSubscriptions.entries()) { + if (childDir !== dirKey && isPathInside(childDir, dirKey)) { + this.extraSubscriptions.delete(childDir); + childSub.unsubscribe().catch(() => {}); + } + } + } else { + sub.unsubscribe().catch(() => {}); + } + } catch { + // Ignore subscription errors for missing or restricted external directories + } finally { + this.pendingSubscriptions.delete(dirKey); + } + } +} + +async function createParcelWatcher( + options: WatcherOptions | undefined, + parcelWatcher: typeof ParcelWatcher, +): Promise { + const watchedFiles = new Set(); + const queue = new WatcherQueue(); + + const isCaseSensitive = isFileSystemCaseSensitive(options?.cwd); + const rootDirPosix = toPosixPathNormalized(options?.cwd ?? process.cwd()); + const rootDirLookupKey = toLookupKey(rootDirPosix, isCaseSensitive); + const initTime = Date.now(); + + const handleEvents = (events: ParcelWatcher.Event[]) => { + const changes: { type: 'added' | 'modified' | 'removed'; file: string }[] = []; + for (const event of events) { + const posixPath = toPosixPathNormalized(event.path); + const lookupKey = toLookupKey(posixPath, isCaseSensitive); + if (!isPathWatched(lookupKey, watchedFiles)) { + continue; + } + + if (event.type !== 'delete') { + const stat = fs.statSync(event.path, { throwIfNoEntry: false }); + // Ignore historical events from before watcher initialization, but allow a 1000 ms window + // to account for coarse filesystem timestamp resolution (e.g., ext4/overlayfs integer second + // mtime truncation on Linux) where files modified during startup may have truncated .000 ms mtimes. + if (stat && stat.mtimeMs < initTime - 1000) { + continue; + } + } + + const type = + event.type === 'create' ? 'added' : event.type === 'delete' ? 'removed' : 'modified'; + changes.push({ type, file: event.path }); + } + + if (changes.length > 0) { + queue.addChanges(changes); + } + }; - return { + const subscription = await parcelWatcher.subscribe( + rootDirPosix, + (err, events) => { + if (!err) { + handleEvents(events); + } + }, + { + ignore: options?.ignored, + }, + ); + + const externalManager = new ParcelExternalManager( + parcelWatcher, + options, + rootDirLookupKey, + handleEvents, + ); + + const buildWatcher: BuildWatcher = { [Symbol.asyncIterator]() { return this; }, - async next() { - if (currentChangedFiles && nextQueue.length === 0) { - const result = { value: currentChangedFiles }; - currentChangedFiles = undefined; + next() { + return queue.next(); + }, - return result; + add(paths) { + const targets = typeof paths === 'string' ? [paths] : paths; + for (const file of targets) { + const posixPath = toPosixPathNormalized(file); + const lookupKey = toLookupKey(posixPath, isCaseSensitive); + if (!watchedFiles.has(lookupKey)) { + watchedFiles.add(lookupKey); + void externalManager.ensureWatched(posixPath, lookupKey); + } } + }, - return new Promise((resolve) => { - nextQueue.push((value) => resolve(value ? { value } : { done: true, value })); - }); + remove(paths) { + const targets = typeof paths === 'string' ? [paths] : paths; + for (const file of targets) { + const posixPath = toPosixPathNormalized(file); + const lookupKey = toLookupKey(posixPath, isCaseSensitive); + if (watchedFiles.delete(lookupKey)) { + externalManager.removeFile(lookupKey); + } + } }, - add(paths) { - const previousSize = watchedFiles.size; - if (typeof paths === 'string') { - watchedFiles.add(paths); - } else { - for (const file of paths) { - watchedFiles.add(file); + async close() { + try { + if (subscription) { + await subscription.unsubscribe(); } + await externalManager.close(); + } finally { + queue.close(); } + }, + }; + + return buildWatcher; +} + +async function createChokidarWatcher( + options?: WatcherOptions, + chokidarModule?: typeof Chokidar, +): Promise { + const chokidar = chokidarModule ?? (await import('chokidar')); + const watchedFiles = new Set(); + const queue = new WatcherQueue(); + + const rootDir = options?.cwd ?? process.cwd(); + const isCaseSensitive = isFileSystemCaseSensitive(rootDir); + const rootDirPosix = toPosixPathNormalized(rootDir); + const rootDirLookupKey = toLookupKey(rootDirPosix, isCaseSensitive); - if (previousSize !== watchedFiles.size) { - watcher.watch({ - files: watchedFiles, - }); + const ignored = options?.ignored?.map((pattern) => { + if (/[*?[\]{}()]/.test(pattern)) { + const isMatch = picomatch(pattern, { dot: true }); + + return (filePath: string) => isMatch(toPosixPathNormalized(filePath)); + } + + return { path: toPosixPathNormalized(pattern), recursive: true }; + }); + + const watcher = chokidar.watch(rootDir, { + ignoreInitial: true, + ignored, + followSymlinks: options?.followSymlinks, + usePolling: !!options?.polling, + interval: options?.interval, + }); + const initTime = Date.now(); + + const handleEvent = (type: 'added' | 'modified' | 'removed', rawPath: string) => { + const posixPath = toPosixPathNormalized(rawPath); + const lookupKey = toLookupKey(posixPath, isCaseSensitive); + if (!isPathWatched(lookupKey, watchedFiles)) { + return; + } + + if (type !== 'removed') { + const stat = fs.statSync(rawPath, { throwIfNoEntry: false }); + // Ignore historical events from before watcher initialization, but allow a 1000 ms window + // to account for coarse filesystem timestamp resolution (e.g., ext4/overlayfs integer second + // mtime truncation on Linux) where files modified during startup may have truncated .000 ms mtimes. + if (stat && stat.mtimeMs < initTime - 1000) { + return; } + } + + queue.addChange(type, rawPath); + }; + + watcher.on('add', (path) => handleEvent('added', path)); + watcher.on('change', (path) => handleEvent('modified', path)); + watcher.on('unlink', (path) => handleEvent('removed', path)); + + const buildWatcher: BuildWatcher = { + [Symbol.asyncIterator]() { + return this; }, - remove(paths) { - const previousSize = watchedFiles.size; - if (typeof paths === 'string') { - watchedFiles.delete(paths); - } else { - for (const file of paths) { - watchedFiles.delete(file); + next() { + return queue.next(); + }, + + add(paths) { + const targets = typeof paths === 'string' ? [paths] : paths; + const newPaths: string[] = []; + for (const p of targets) { + const posixPath = toPosixPathNormalized(p); + const lookupKey = toLookupKey(posixPath, isCaseSensitive); + if (!watchedFiles.has(lookupKey)) { + watchedFiles.add(lookupKey); + if (!isPathInside(lookupKey, rootDirLookupKey) && lookupKey !== rootDirLookupKey) { + newPaths.push(posixPath); + } } } + if (newPaths.length > 0) { + watcher.add(newPaths); + } + }, - if (previousSize !== watchedFiles.size) { - watcher.watch({ - files: watchedFiles, - }); + remove(paths) { + const targets = typeof paths === 'string' ? [paths] : paths; + const removePaths: string[] = []; + for (const p of targets) { + const posixPath = toPosixPathNormalized(p); + const lookupKey = toLookupKey(posixPath, isCaseSensitive); + if (watchedFiles.has(lookupKey)) { + watchedFiles.delete(lookupKey); + if (!isPathInside(lookupKey, rootDirLookupKey) && lookupKey !== rootDirLookupKey) { + removePaths.push(posixPath); + } + } + } + if (removePaths.length > 0) { + watcher.unwatch(removePaths); } }, async close() { try { - watcher.close(); + await watcher.close(); } finally { - let next; - while ((next = nextQueue.shift()) !== undefined) { - next(); - } + queue.close(); } }, }; + + return buildWatcher; } diff --git a/packages/angular/build/src/tools/esbuild/watcher_spec.ts b/packages/angular/build/src/tools/esbuild/watcher_spec.ts new file mode 100644 index 000000000000..2c82510a5ae5 --- /dev/null +++ b/packages/angular/build/src/tools/esbuild/watcher_spec.ts @@ -0,0 +1,486 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import * as fs from 'node:fs'; +import * as os from 'node:os'; +import * as path from 'node:path'; +import { setTimeout } from 'node:timers/promises'; +import { + ChangedFiles, + createWatcher, + getDirectoryPath, + isPathInside, + toPosixPathNormalized, +} from './watcher'; + +describe('Watcher', () => { + describe('toPosixPathNormalized', () => { + it('should strip trailing slashes for standard directories', () => { + expect(toPosixPathNormalized('/src/app/')).toBe('/src/app'); + expect(toPosixPathNormalized('C:/src/app/')).toBe('C:/src/app'); + }); + + it('should preserve single root slash', () => { + expect(toPosixPathNormalized('/')).toBe('/'); + }); + + it('should preserve trailing slash for Windows drive root', () => { + expect(toPosixPathNormalized('C:/')).toBe('C:/'); + expect(toPosixPathNormalized('c:/')).toBe('c:/'); + }); + }); + + describe('getDirectoryPath', () => { + it('should return parent directory for POSIX paths', () => { + expect(getDirectoryPath('/src/app/main.ts')).toBe('/src/app'); + expect(getDirectoryPath('/src/app')).toBe('/src'); + expect(getDirectoryPath('/src')).toBe('/'); + expect(getDirectoryPath('/')).toBe('/'); + }); + + it('should correctly handle Windows drive roots', () => { + expect(getDirectoryPath('C:/src/app/main.ts')).toBe('C:/src/app'); + expect(getDirectoryPath('C:/src')).toBe('C:/'); + expect(getDirectoryPath('C:/')).toBe('C:/'); + expect(getDirectoryPath('c:/')).toBe('c:/'); + }); + + it('should return dot for relative paths without slash', () => { + expect(getDirectoryPath('main.ts')).toBe('.'); + }); + }); + + describe('isPathInside', () => { + it('should return true for a file inside a directory', () => { + expect(isPathInside('/src/app/main.ts', '/src/app')).toBeTrue(); + }); + + it('should return false when file and dir are identical', () => { + expect(isPathInside('/src/app', '/src/app')).toBeFalse(); + }); + + it('should return false for sibling directories with matching prefix', () => { + expect(isPathInside('/src/app-other/main.ts', '/src/app')).toBeFalse(); + }); + + it('should handle Windows drive letters on the same drive', () => { + expect(isPathInside('c:/src/app/main.ts', 'c:/src/app')).toBeTrue(); + }); + + it('should return false for Windows drive letters on different drives', () => { + expect(isPathInside('d:/src/app/main.ts', 'c:/src/app')).toBeFalse(); + }); + + it('should handle root directory correctly', () => { + expect(isPathInside('/src/main.ts', '/')).toBeTrue(); + }); + + it('should handle Windows drive root directory correctly', () => { + expect(isPathInside('c:/src/main.ts', 'c:/')).toBeTrue(); + }); + }); + + describe('ChangedFiles', () => { + it('should track added, modified, and removed files', () => { + const changes = new ChangedFiles(); + changes.added.add('/src/app.component.ts'); + changes.modified.add('/src/main.ts'); + changes.removed.add('/src/old.ts'); + + expect(changes.all).toEqual(['/src/app.component.ts', '/src/main.ts', '/src/old.ts']); + }); + + it('should deduplicate files present in multiple sets in .all', () => { + const changes = new ChangedFiles(); + changes.added.add('/src/main.ts'); + changes.modified.add('/src/main.ts'); + + expect(changes.all).toEqual(['/src/main.ts']); + }); + + it('should format debug string correctly', () => { + const changes = new ChangedFiles(); + changes.modified.add('/src/main.ts'); + + const debug = JSON.parse(changes.toDebugString()); + expect(debug).toEqual({ + added: [], + modified: ['/src/main.ts'], + removed: [], + }); + }); + }); + + describe('createWatcher', () => { + let tempDir: string; + + beforeEach(() => { + tempDir = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'watcher-spec-'))); + }); + + afterEach(() => { + if (fs.existsSync(tempDir)) { + fs.rmSync(tempDir, { recursive: true, force: true }); + } + }); + + it('should instantiate and close watcher without error', async () => { + const watcher = await createWatcher({ cwd: tempDir }); + expect(watcher).toBeDefined(); + + watcher.add(path.join(tempDir, 'main.ts')); + watcher.remove(path.join(tempDir, 'main.ts')); + + await watcher.close(); + }); + + it('should support array of paths in add and remove', async () => { + const watcher = await createWatcher({ cwd: tempDir }); + const file1 = path.join(tempDir, 'a.ts'); + const file2 = path.join(tempDir, 'b.ts'); + + watcher.add([file1, file2]); + watcher.remove([file1, file2]); + + await watcher.close(); + }); + + it('should support polling option', async () => { + const watcher = await createWatcher({ polling: true, interval: 100, cwd: tempDir }); + expect(watcher).toBeDefined(); + + watcher.add(path.join(tempDir, 'main.ts')); + await watcher.close(); + }); + + it('should emit changes when a watched file is modified (chokidar polling)', async () => { + const testFile = path.join(tempDir, 'test.txt'); + fs.writeFileSync(testFile, 'initial'); + + const watcher = await createWatcher({ polling: true, interval: 50, cwd: tempDir }); + watcher.add(testFile); + + // Wait a short moment for watcher setup and mtime tick + await setTimeout(100); + + const iterator = watcher[Symbol.asyncIterator](); + const nextPromise = iterator.next(); + + // Trigger change + fs.writeFileSync(testFile, 'updated'); + + const result = await nextPromise; + expect(result.done).toBeFalsy(); + expect(result.value?.all.length).toBeGreaterThan(0); + + await watcher.close(); + }, 10000); + + it('should preserve original path character casing in emitted changes', async () => { + const casedFile = path.join(tempDir, 'App.Component.ts'); + fs.writeFileSync(casedFile, 'initial'); + + const watcher = await createWatcher({ polling: true, interval: 50, cwd: tempDir }); + watcher.add(casedFile); + + await setTimeout(100); + + const iterator = watcher[Symbol.asyncIterator](); + const nextPromise = iterator.next(); + + fs.writeFileSync(casedFile, 'updated'); + + const result = await nextPromise; + expect(result.done).toBeFalsy(); + const emittedFiles = result.value?.all ?? []; + expect(emittedFiles.some((f: string) => f.includes('App.Component.ts'))).toBeTrue(); + + await watcher.close(); + }, 10000); + + it('should emit changes when watching a directory containing modified files', async () => { + const subDir = path.join(tempDir, 'sub'); + fs.mkdirSync(subDir); + const testFile = path.join(subDir, 'nested.txt'); + fs.writeFileSync(testFile, 'initial'); + + const watcher = await createWatcher({ polling: true, interval: 50, cwd: tempDir }); + watcher.add(subDir); + + await setTimeout(100); + + const iterator = watcher[Symbol.asyncIterator](); + const nextPromise = iterator.next(); + + fs.writeFileSync(testFile, 'updated'); + + const result = await nextPromise; + expect(result.done).toBeFalsy(); + expect(result.value?.all.length).toBeGreaterThan(0); + + await watcher.close(); + }, 10000); + + it('should support watching paths outside cwd', async () => { + const externalDir = fs.mkdtempSync(path.join(os.tmpdir(), 'external-watcher-spec-')); + const externalFile = path.join(externalDir, 'external.txt'); + fs.writeFileSync(externalFile, 'initial'); + + try { + const watcher = await createWatcher({ polling: true, interval: 50, cwd: tempDir }); + watcher.add(externalFile); + + await setTimeout(100); + + const iterator = watcher[Symbol.asyncIterator](); + const nextPromise = iterator.next(); + + fs.writeFileSync(externalFile, 'updated'); + + const result = await nextPromise; + expect(result.done).toBeFalsy(); + expect(result.value?.all.some((f: string) => f.includes('external.txt'))).toBeTrue(); + + await watcher.close(); + } finally { + fs.rmSync(externalDir, { recursive: true, force: true }); + } + }, 10000); + + it('should handle adding multiple external files in the same directory concurrently', async () => { + const externalDir = fs.mkdtempSync(path.join(os.tmpdir(), 'external-watcher-spec-')); + const file1 = path.join(externalDir, 'file1.txt'); + const file2 = path.join(externalDir, 'file2.txt'); + fs.writeFileSync(file1, 'initial1'); + fs.writeFileSync(file2, 'initial2'); + + try { + const watcher = await createWatcher({ polling: true, interval: 50, cwd: tempDir }); + watcher.add([file1, file2]); + + await setTimeout(100); + + const iterator = watcher[Symbol.asyncIterator](); + let nextPromise = iterator.next(); + fs.writeFileSync(file1, 'updated1'); + let result = await nextPromise; + expect(result.value?.all.some((f: string) => f.includes('file1.txt'))).toBeTrue(); + + nextPromise = iterator.next(); + fs.writeFileSync(file2, 'updated2'); + result = await nextPromise; + expect(result.value?.all.some((f: string) => f.includes('file2.txt'))).toBeTrue(); + + await watcher.close(); + } finally { + fs.rmSync(externalDir, { recursive: true, force: true }); + } + }, 10000); + + it('should clean up external subscriptions when all external files in a directory are removed', async () => { + const externalDir = fs.mkdtempSync(path.join(os.tmpdir(), 'external-watcher-spec-')); + const file1 = path.join(externalDir, 'file1.txt'); + const file2 = path.join(externalDir, 'file2.txt'); + fs.writeFileSync(file1, 'initial1'); + fs.writeFileSync(file2, 'initial2'); + + try { + const watcher = await createWatcher({ polling: true, interval: 50, cwd: tempDir }); + watcher.add([file1, file2]); + + await setTimeout(100); + + // Remove files from watcher + watcher.remove(file1); + watcher.remove(file2); + + await watcher.close(); + } finally { + fs.rmSync(externalDir, { recursive: true, force: true }); + } + }); + + it('should handle nested external directories without creating duplicate subscriptions', async () => { + const externalDir = fs.mkdtempSync(path.join(os.tmpdir(), 'external-watcher-spec-')); + const subDir = path.join(externalDir, 'sub'); + fs.mkdirSync(subDir); + const parentFile = path.join(externalDir, 'parent.txt'); + const childFile = path.join(subDir, 'child.txt'); + fs.writeFileSync(parentFile, 'initial-parent'); + fs.writeFileSync(childFile, 'initial-child'); + + try { + const watcher = await createWatcher({ polling: true, interval: 50, cwd: tempDir }); + watcher.add(parentFile); + watcher.add(childFile); + + await setTimeout(100); + + const iterator = watcher[Symbol.asyncIterator](); + const nextPromise = iterator.next(); + + fs.writeFileSync(childFile, 'updated-child'); + + const result = await nextPromise; + expect(result.done).toBeFalsy(); + expect(result.value?.all.some((f: string) => f.includes('child.txt'))).toBeTrue(); + + await watcher.close(); + } finally { + fs.rmSync(externalDir, { recursive: true, force: true }); + } + }, 10000); + + it('should subscribe to subsumed external child directory when parent external subscription is removed', async () => { + const externalDir = fs.mkdtempSync(path.join(os.tmpdir(), 'external-watcher-spec-')); + const subDir = path.join(externalDir, 'sub'); + fs.mkdirSync(subDir); + const parentFile = path.join(externalDir, 'parent.txt'); + const childFile = path.join(subDir, 'child.txt'); + fs.writeFileSync(parentFile, 'initial-parent'); + fs.writeFileSync(childFile, 'initial-child'); + + try { + const watcher = await createWatcher({ polling: true, interval: 50, cwd: tempDir }); + watcher.add(parentFile); + watcher.add(childFile); + + await setTimeout(100); + + watcher.remove(parentFile); + + await setTimeout(100); + + const iterator = watcher[Symbol.asyncIterator](); + const nextPromise = iterator.next(); + + fs.writeFileSync(childFile, 'updated-child'); + + const result = await nextPromise; + expect(result.done).toBeFalsy(); + expect(result.value?.all.some((f: string) => f.includes('child.txt'))).toBeTrue(); + + await watcher.close(); + } finally { + fs.rmSync(externalDir, { recursive: true, force: true }); + } + }, 10000); + + it('should signal completion on close', async () => { + const watcher = await createWatcher({ polling: true, interval: 50, cwd: tempDir }); + const iterator = watcher[Symbol.asyncIterator](); + + const nextPromise = iterator.next(); + await watcher.close(); + + const result = await nextPromise; + expect(result.done).toBeTrue(); + }); + + it('should return done immediately if next() is called after close()', async () => { + const watcher = await createWatcher({ polling: true, interval: 50, cwd: tempDir }); + await watcher.close(); + + const result = await watcher.next(); + expect(result.done).toBeTrue(); + }); + + it('should ignore stale modifications before initTime and emit changes after initTime (@parcel/watcher)', async () => { + const testFile = path.join(tempDir, 'test.txt'); + fs.writeFileSync(testFile, 'initial'); + + // Small delay to ensure initial mtimeMs is strictly earlier than initTime - 1000 + await setTimeout(1100); + + // Create native @parcel/watcher (polling: false / default) + const watcher = await createWatcher({ cwd: tempDir }); + watcher.add(testFile); + + // Wait a short moment for native watcher setup and kernel event stream initialization + await setTimeout(150); + + const iterator = watcher[Symbol.asyncIterator](); + const nextPromise = iterator.next(); + + // Trigger a change after watcher initialization + fs.writeFileSync(testFile, 'updated'); + + const result = await nextPromise; + expect(result.done).toBeFalsy(); + expect(result.value?.all.some((f: string) => f.includes('test.txt'))).toBeTrue(); + + await watcher.close(); + }, 10000); + + it('should emit changes when a file is deleted and recreated with stabilization delay (@parcel/watcher)', async () => { + const testFile = path.join(tempDir, 'recreate.txt'); + fs.writeFileSync(testFile, 'initial'); + + await setTimeout(50); + + const watcher = await createWatcher({ cwd: tempDir }); + watcher.add(testFile); + + await setTimeout(150); + + const iterator = watcher[Symbol.asyncIterator](); + + // Delete the file + fs.rmSync(testFile); + let result = await iterator.next(); + expect(result.done).toBeFalsy(); + expect(result.value?.removed.size).toBeGreaterThan(0); + + // Brief stabilization delay before recreating to prevent macOS fsevents kernel driver + // from coalescing unlink and create into a single directory event + await setTimeout(150); + + // Recreate the file + fs.writeFileSync(testFile, 'recreated'); + result = await iterator.next(); + expect(result.done).toBeFalsy(); + expect(result.value?.added.size).toBeGreaterThan(0); + + await watcher.close(); + }, 10000); + + it('should ignore changes matching glob patterns in polling mode (chokidar)', async () => { + const ignoredDir = path.join(tempDir, 'dist'); + fs.mkdirSync(ignoredDir); + const ignoredFile = path.join(ignoredDir, 'bundle.js'); + const watchedFile = path.join(tempDir, 'src.ts'); + fs.writeFileSync(ignoredFile, 'initial-dist'); + fs.writeFileSync(watchedFile, 'initial-src'); + + const watcher = await createWatcher({ + polling: true, + interval: 50, + cwd: tempDir, + ignored: [`${toPosixPathNormalized(ignoredDir)}/**`], + }); + + watcher.add(tempDir); + await setTimeout(100); + + const iterator = watcher[Symbol.asyncIterator](); + const nextPromise = iterator.next(); + + // Trigger changes in ignored file and watched file + fs.writeFileSync(ignoredFile, 'updated-dist'); + fs.writeFileSync(watchedFile, 'updated-src'); + + const result = await nextPromise; + expect(result.done).toBeFalsy(); + const emitted = result.value?.all ?? []; + expect(emitted.some((f: string) => f.includes('src.ts'))).toBeTrue(); + expect(emitted.some((f: string) => f.includes('bundle.js'))).toBeFalse(); + + await watcher.close(); + }, 10000); + }); +}); diff --git a/packages/angular/build/src/tools/oxc/adjust-typescript-enums_oxc_spec.ts b/packages/angular/build/src/tools/oxc/adjust-typescript-enums_oxc_spec.ts index c074f315d35f..82877cb3b2e3 100644 --- a/packages/angular/build/src/tools/oxc/adjust-typescript-enums_oxc_spec.ts +++ b/packages/angular/build/src/tools/oxc/adjust-typescript-enums_oxc_spec.ts @@ -300,4 +300,83 @@ describe('adjust-typescript-enums oxc-transform implementation', () => { `, }), ); + + it( + 'handles TypeScript enums wrapped in parentheses', + testCase({ + input: ` + var ChangeDetectionStrategy; + ((function (ChangeDetectionStrategy) { + ChangeDetectionStrategy[ChangeDetectionStrategy["OnPush"] = 0] = "OnPush"; + ChangeDetectionStrategy[ChangeDetectionStrategy["Default"] = 1] = "Default"; + })(ChangeDetectionStrategy || (ChangeDetectionStrategy = {}))); + `, + expected: ` + var ChangeDetectionStrategy = /*#__PURE__*/ (function (ChangeDetectionStrategy) { + ChangeDetectionStrategy[(ChangeDetectionStrategy["OnPush"] = 0)] = "OnPush"; + ChangeDetectionStrategy[(ChangeDetectionStrategy["Default"] = 1)] = "Default"; + return ChangeDetectionStrategy; + })(ChangeDetectionStrategy || {}); + `, + }), + ); + + it( + 'wraps Crockford-style TypeScript enum IIFE without leaving dangling parentheses', + testCase({ + input: ` + var HDirection; + (function (HDirection) { + HDirection[HDirection['Backwards'] = -1] = 'Backwards'; + HDirection[HDirection['Forwards'] = 1] = 'Forwards'; + }(HDirection || (HDirection = {}))); + const nextStatement = true; + `, + expected: ` + var HDirection = /*#__PURE__*/ (function (HDirection) { + HDirection[(HDirection['Backwards'] = -1)] = 'Backwards'; + HDirection[(HDirection['Forwards'] = 1)] = 'Forwards'; + return HDirection; + }(HDirection || {})); + + const nextStatement = true; + `, + }), + ); + + it( + 'wraps TypeScript enum IIFE with multiple nested parentheses', + testCase({ + input: ` + var Foo; + (((function (Foo) { + Foo[Foo['A'] = 0] = 'A'; + }(Foo || (Foo = {}))))); + `, + expected: ` + var Foo = /*#__PURE__*/ (function (Foo) { + Foo[(Foo['A'] = 0)] = 'A'; + return Foo; + }(Foo || {})); + `, + }), + ); + + it( + 'wraps Crockford-style TypeScript enum IIFE with chained export assignments', + testCase({ + input: ` + var Foo; + (function (Foo) { + Foo[Foo['A'] = 0] = 'A'; + }(Foo || (Foo = exports.Foo = {}))); + `, + expected: ` + var Foo = /*#__PURE__*/ (function (Foo) { + Foo[(Foo['A'] = 0)] = 'A'; + return Foo; + }(Foo || (exports.Foo = {}))); + `, + }), + ); }); diff --git a/packages/angular/build/src/tools/oxc/oxc-transform.ts b/packages/angular/build/src/tools/oxc/oxc-transform.ts index 64a5aa74062e..c0389a9f75a4 100644 --- a/packages/angular/build/src/tools/oxc/oxc-transform.ts +++ b/packages/angular/build/src/tools/oxc/oxc-transform.ts @@ -6,17 +6,21 @@ * found in the LICENSE file at https://angular.dev/license */ -import remapping, { type EncodedSourceMap } from '@ampproject/remapping'; +import type { DecodedSourceMap } from '@ampproject/remapping'; +import { needsLinking } from '@angular/compiler-cli/linker'; import type { BindingIdentifier, Class, Node } from '@oxc-project/types'; import { MagicString } from 'magic-string'; import { Visitor, parseSync } from 'oxc-parser'; -import { loadInputSourceMap } from '../../utils/source-map'; +import { OxcLinker } from '../angular/linker/oxc-linker'; export interface OxcTransformOptions { sourcemap?: boolean; sideEffects?: boolean; topLevelSafeMode?: boolean; pureAnnotate?: boolean; + link?: boolean; + jit?: boolean; + advancedOptimizations?: boolean; } /** @@ -257,8 +261,12 @@ function analyzeClassStaticProperties(classNode: Node, code: string): boolean { // eslint-disable-next-line max-lines-per-function export function transform(filename: string, code: string, options: OxcTransformOptions) { const { program } = parseSync(filename, code, { range: true }); - const s = new MagicString(code); + const source = new MagicString(code); + const shouldLink = options.link && needsLinking(filename, code); + const linker = shouldLink ? new OxcLinker(filename, code, options.jit) : undefined; + + const advancedOptimizations = options.advancedOptimizations ?? true; const sideEffectFree = options.sideEffects === false; const topLevelSafeMode = options.topLevelSafeMode ?? false; const wrapDecorators = sideEffectFree; @@ -412,12 +420,13 @@ export function transform(filename: string, code: string, options: OxcTransformO continue; } - // 1. Remove only the trailing characters/semicolon of the expression statement - s.remove(nextExpr.end, nextStatement.end); - markEdited(nextExpr.end, nextStatement.end); + // 1. Remove leading/trailing characters/parentheses of the expression statement + source.remove(nextStatement.start, nextExpr.start); + source.remove(nextExpr.end, nextStatement.end); + markEdited(nextStatement.start, nextStatement.end); // 2. Add return statement inside IIFE body - s.appendRight(callee.body.end - 1, `; return ${paramName};`); + source.appendRight(callee.body.end - 1, `; return ${paramName};`); // 3. Remove `Name = ` assignment in arguments if it's a simple identifier if (rightCallArgument.left.type === 'Identifier') { @@ -428,14 +437,13 @@ export function transform(filename: string, code: string, options: OxcTransformO if (unwrapParentheses(rightCallArgument.right).type === 'AssignmentExpression') { replacement = `(${replacement})`; } - s.overwrite(arg.right.start, arg.right.end, replacement); + source.overwrite(arg.right.start, arg.right.end, replacement); markEdited(arg.right.start, arg.right.end); } // 4. Move IIFE to the var initializer - s.move(nextExpr.start, nextExpr.end, decl.id.end); - s.appendLeft(decl.id.end, ' = /*#__PURE__*/ '); - markEdited(nextExpr.start, nextExpr.end); + source.move(nextExpr.start, nextExpr.end, decl.id.end); + source.appendLeft(decl.id.end, ' = /*#__PURE__*/ '); } } @@ -563,7 +571,7 @@ export function transform(filename: string, code: string, options: OxcTransformO // Perform elisions immediately for (const item of wrapStatementPaths) { if (item.type === 'elide') { - s.remove(item.statement.start, item.statement.end); + source.remove(item.statement.start, item.statement.end); markEdited(item.statement.start, item.statement.end); } } @@ -583,27 +591,30 @@ export function transform(filename: string, code: string, options: OxcTransformO if (isExportDefault) { // 1. Remove `export default ` - s.overwrite(statement.start, classNode.start, ''); + source.overwrite(statement.start, classNode.start, ''); // 2. Wrap in IIFE - s.appendRight(classNode.start, `let ${classIdName} = /*#__PURE__*/ (() => {\n`); - s.appendLeft( + source.appendRight(classNode.start, `let ${classIdName} = /*#__PURE__*/ (() => {\n`); + source.appendLeft( lastStatement.end, `\nreturn ${classIdName};\n})();\nexport { ${classIdName} as default };`, ); } else if (isExportNamed) { // 1. Export is kept, turn `class` into `let ClassName = IIFE` - s.appendRight(classNode.start, `let ${classIdName} = /*#__PURE__*/ (() => {\n`); - s.appendLeft(lastStatement.end, `\nreturn ${classIdName};\n})();`); + source.appendRight(classNode.start, `let ${classIdName} = /*#__PURE__*/ (() => {\n`); + source.appendLeft(lastStatement.end, `\nreturn ${classIdName};\n})();`); } else if (isVariableClass) { // Wrap class inside init: `/*#__PURE__*/ (() => { let ClassName = class ClassName {}; return ClassName; })()` - s.appendRight(classNode.start, `/*#__PURE__*/ (() => {\nlet ${classIdName} = `); + source.appendRight(classNode.start, `/*#__PURE__*/ (() => {\nlet ${classIdName} = `); const terminator = activeWrapPaths.length === 0 ? ';' : ''; const iifeClosing = activeWrapPaths.length === 0 ? '})()' : '})();'; - s.appendLeft(lastStatement.end, `${terminator}\nreturn ${classIdName};\n${iifeClosing}`); + source.appendLeft( + lastStatement.end, + `${terminator}\nreturn ${classIdName};\n${iifeClosing}`, + ); } else { // Standard ClassDeclaration - s.appendRight(classNode.start, `let ${classIdName} = /*#__PURE__*/ (() => {\n`); - s.appendLeft(lastStatement.end, `\nreturn ${classIdName};\n})();`); + source.appendRight(classNode.start, `let ${classIdName} = /*#__PURE__*/ (() => {\n`); + source.appendLeft(lastStatement.end, `\nreturn ${classIdName};\n})();`); } markEdited(statement.start, lastStatement.end); @@ -612,8 +623,8 @@ export function transform(filename: string, code: string, options: OxcTransformO i += wrapStatementPaths.length; } else if (isExportDefault && !hasPotentialSideEffects) { // Splitting default export even when not wrapped - s.overwrite(statement.start, classNode.start, ''); - s.appendLeft(classNode.end, `\nexport { ${classIdName} as default };`); + source.overwrite(statement.start, classNode.start, ''); + source.appendLeft(classNode.end, `\nexport { ${classIdName} as default };`); markEdited(statement.start, classNode.end); } } @@ -656,19 +667,37 @@ export function transform(filename: string, code: string, options: OxcTransformO functionDepth--; functionStack.pop(); }, - Program(node) { - adjustTypeScriptEnumsInStatements(node.body); - adjustStaticMembersInStatements(node.body); + 'Program:exit'(node) { + if (advancedOptimizations) { + adjustTypeScriptEnumsInStatements(node.body); + adjustStaticMembersInStatements(node.body); + } }, - BlockStatement(node) { - adjustTypeScriptEnumsInStatements(node.body); - adjustStaticMembersInStatements(node.body); + 'BlockStatement:exit'(node) { + if (advancedOptimizations) { + adjustTypeScriptEnumsInStatements(node.body); + adjustStaticMembersInStatements(node.body); + } }, CallExpression(node) { if (isAlreadyEdited(node.start, node.end)) { return; } + if (linker) { + const linkedCode = linker.linkCallExpression(node); + if (linkedCode !== undefined) { + source.overwrite(node.start, node.end, linkedCode); + markEdited(node.start, node.end); + + return; + } + } + + if (!advancedOptimizations) { + return; + } + // 1. Elide Angular Metadata check let calleeName: string | undefined; if (node.callee.type === 'Identifier') { @@ -687,7 +716,7 @@ export function transform(filename: string, code: string, options: OxcTransformO (parentFunc.type === 'FunctionExpression' || parentFunc.type === 'ArrowFunctionExpression') ) { - s.overwrite(node.start, node.end, 'void 0'); + source.overwrite(node.start, node.end, 'void 0'); markEdited(node.start, node.end); return; @@ -715,11 +744,12 @@ export function transform(filename: string, code: string, options: OxcTransformO } if (!hasPureComment(node.start)) { - s.appendLeft(node.start, '/*#__PURE__*/ '); + source.appendLeft(node.start, '/*#__PURE__*/ '); } }, NewExpression(node) { if ( + !advancedOptimizations || !pureAnnotate || functionDepth > 0 || classDepth > 0 || @@ -730,7 +760,7 @@ export function transform(filename: string, code: string, options: OxcTransformO if (!topLevelSafeMode) { if (!hasPureComment(node.start)) { - s.appendLeft(node.start, '/*#__PURE__*/ '); + source.appendLeft(node.start, '/*#__PURE__*/ '); } return; @@ -739,7 +769,7 @@ export function transform(filename: string, code: string, options: OxcTransformO const callee = node.callee; if (callee.type === 'Identifier' && sideEffectFreeConstructors.has(callee.name)) { if (!hasPureComment(node.start)) { - s.appendLeft(node.start, '/*#__PURE__*/ '); + source.appendLeft(node.start, '/*#__PURE__*/ '); } } }, @@ -747,20 +777,14 @@ export function transform(filename: string, code: string, options: OxcTransformO visitor.visit(program); - let map: string | undefined; + let map: DecodedSourceMap | undefined; if (options.sourcemap) { - const rawMap = s.generateMap({ hires: true, source: filename }); - const inputMap = loadInputSourceMap(filename, code); - - if (inputMap) { - map = remapping([rawMap as EncodedSourceMap, inputMap], () => null).toString(); - } else { - map = rawMap.toString(); - } + const rawMap = source.generateDecodedMap({ hires: true, source: filename }); + map = { ...rawMap, version: 3 }; } return { - code: s.toString(), + code: source.toString(), map, }; } diff --git a/packages/angular/build/src/tools/oxc/oxc-transform_spec.ts b/packages/angular/build/src/tools/oxc/oxc-transform_spec.ts new file mode 100644 index 000000000000..d1b02f6a1bdc --- /dev/null +++ b/packages/angular/build/src/tools/oxc/oxc-transform_spec.ts @@ -0,0 +1,80 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { transform } from './oxc-transform'; + +describe('oxc-transform', () => { + describe('sourcemaps', () => { + it('should generate a decoded sourcemap when sourcemap option is enabled', () => { + const input = 'var result = new SomeClass();'; + const result = transform('test.js', input, { sourcemap: true }); + + expect(result.map).toBeDefined(); + expect(result.map?.version).toBe(3); + expect(result.map?.sources).toContain('test.js'); + expect(result.map?.mappings.length).toBeGreaterThan(0); + }); + }); + + describe('linking and unified passes', () => { + const componentInput = ` + import * as i0 from "@angular/core"; + export class MyComponent {} + MyComponent.ɵcmp = i0.ɵɵngDeclareComponent({ + minVersion: "12.0.0", + version: "14.0.0", + ngImport: i0, + type: MyComponent, + isStandalone: true, + selector: "my-cmp", + template: "Hello" + }); + `; + + it('should link partial component declarations when link option is enabled', () => { + const result = transform('test.js', componentInput, { link: true }); + expect(result.code).toContain('i0.ɵɵdefineComponent'); + expect(result.code).not.toContain('i0.ɵɵngDeclareComponent'); + }); + + it('should not link partial component declarations when link option is disabled', () => { + const result = transform('test.js', componentInput, { link: false }); + expect(result.code).not.toContain('i0.ɵɵdefineComponent'); + expect(result.code).toContain('i0.ɵɵngDeclareComponent'); + }); + + it('should perform linking and advanced optimizations simultaneously in a single pass', () => { + const input = ` + import * as i0 from "@angular/core"; + export class MyComponent { + static create() { + return new MyComponent(); + } + } + MyComponent.ɵcmp = i0.ɵɵngDeclareComponent({ + minVersion: "12.0.0", + version: "14.0.0", + ngImport: i0, + type: MyComponent, + isStandalone: true, + selector: "my-cmp", + template: "Hello" + }); + `; + + const result = transform('test.js', input, { + link: true, + advancedOptimizations: true, + topLevelSafeMode: true, + }); + expect(result.code).toContain('i0.ɵɵdefineComponent'); + expect(result.code).not.toContain('i0.ɵɵngDeclareComponent'); + expect(result.code).toContain('let MyComponent = /*#__PURE__*/ (() => {'); + }); + }); +}); diff --git a/packages/angular/build/src/tools/sass/rebasing-importer.ts b/packages/angular/build/src/tools/sass/rebasing-importer.ts index ccc90014616d..e9509574f455 100644 --- a/packages/angular/build/src/tools/sass/rebasing-importer.ts +++ b/packages/angular/build/src/tools/sass/rebasing-importer.ts @@ -6,12 +6,12 @@ * found in the LICENSE file at https://angular.dev/license */ -import { RawSourceMap } from '@ampproject/remapping'; +import type { DecodedSourceMap } from '@ampproject/remapping'; import { MagicString } from 'magic-string'; import { readFileSync, readdirSync, statSync } from 'node:fs'; import { basename, dirname, extname, join, relative } from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; -import type { CanonicalizeContext, Importer, ImporterResult, Syntax } from 'sass'; +import type { CanonicalizeContext, Importer, ImporterResult, Syntax } from 'sass-embedded'; import { assertIsError } from '../../utils/error'; import { toPosixPath } from '../../utils/path'; import { findUrls } from './lexer'; @@ -44,7 +44,7 @@ abstract class UrlRebasingImporter implements Importer<'sync'> { */ constructor( private entryDirectory: string, - private rebaseSourceMaps?: Map, + private rebaseSourceMaps?: Map, ) {} abstract canonicalize(url: string, options: { fromImport: boolean }): URL | null; @@ -95,12 +95,15 @@ abstract class UrlRebasingImporter implements Importer<'sync'> { contents = updatedContents.toString(); if (this.rebaseSourceMaps) { // Generate an intermediate source map for the rebasing changes - const map = updatedContents.generateMap({ + const map = updatedContents.generateDecodedMap({ hires: 'boundary', includeContent: true, source: canonicalUrl.href, }); - this.rebaseSourceMaps.set(canonicalUrl.href, map as RawSourceMap); + this.rebaseSourceMaps.set(canonicalUrl.href, { + ...map, + version: 3, + } satisfies DecodedSourceMap); } } @@ -134,7 +137,7 @@ export class RelativeUrlRebasingImporter extends UrlRebasingImporter { constructor( entryDirectory: string, private directoryCache = new Map(), - rebaseSourceMaps?: Map, + rebaseSourceMaps?: Map, ) { super(entryDirectory, rebaseSourceMaps); } @@ -322,7 +325,7 @@ export class ModuleUrlRebasingImporter extends RelativeUrlRebasingImporter { constructor( entryDirectory: string, directoryCache: Map, - rebaseSourceMaps: Map | undefined, + rebaseSourceMaps: Map | undefined, private finder: (specifier: string, options: CanonicalizeContext) => URL | null, ) { super(entryDirectory, directoryCache, rebaseSourceMaps); @@ -340,6 +343,44 @@ export class ModuleUrlRebasingImporter extends RelativeUrlRebasingImporter { } } +/** + * Provides the Sass importer logic to resolve module (npm package) stylesheet imports asynchronously + * and also rebase any `url()` function usage within those stylesheets. + */ +export class AsyncModuleUrlRebasingImporter implements Importer<'async'> { + private relativeImporter: RelativeUrlRebasingImporter; + + constructor( + entryDirectory: string, + directoryCache: Map, + rebaseSourceMaps: Map | undefined, + private finder: ( + specifier: string, + options: CanonicalizeContext, + ) => Promise | URL | null, + ) { + this.relativeImporter = new RelativeUrlRebasingImporter( + entryDirectory, + directoryCache, + rebaseSourceMaps, + ); + } + + async canonicalize(url: string, options: CanonicalizeContext): Promise { + if (url.startsWith('file://')) { + return this.relativeImporter.canonicalize(url, options); + } + + const result = await this.finder(url, options); + + return result ? this.relativeImporter.canonicalize(result.href, options) : null; + } + + load(canonicalUrl: URL): ImporterResult | null { + return this.relativeImporter.load(canonicalUrl); + } +} + /** * Provides the Sass importer logic to resolve load paths located stylesheet imports via both import and * use rules and also rebase any `url()` function usage within those stylesheets. The rebasing will ensure that @@ -349,7 +390,7 @@ export class LoadPathsUrlRebasingImporter extends RelativeUrlRebasingImporter { constructor( entryDirectory: string, directoryCache: Map, - rebaseSourceMaps: Map | undefined, + rebaseSourceMaps: Map | undefined, private loadPaths: Iterable, ) { super(entryDirectory, directoryCache, rebaseSourceMaps); diff --git a/packages/angular/build/src/tools/sass/sass-async-compiler-implementation.ts b/packages/angular/build/src/tools/sass/sass-async-compiler-implementation.ts new file mode 100644 index 000000000000..cdf483994f12 --- /dev/null +++ b/packages/angular/build/src/tools/sass/sass-async-compiler-implementation.ts @@ -0,0 +1,182 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import mergeSourceMaps, { type DecodedSourceMap, type RawSourceMap } from '@ampproject/remapping'; +import { dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import type { + AsyncCompiler, + CanonicalizeContext, + CompileResult, + FileImporter, + Importer, + NodePackageImporter, + StringOptions, +} from 'sass-embedded'; +import { + AsyncModuleUrlRebasingImporter, + DirectoryEntry, + LoadPathsUrlRebasingImporter, + RelativeUrlRebasingImporter, +} from './rebasing-importer'; +import { type SassServiceImplementation, isFileImporter } from './sass-service'; + +/** + * A Sass renderer implementation that uses the persistent Dart Sass embedded compiler + * daemon (`sass-embedded`) communicating over standard input/output with protocol buffers. + */ +export class SassAsyncCompilerImplementation implements SassServiceImplementation { + #asyncCompiler: AsyncCompiler | undefined; + #asyncCompilerPromise: Promise | undefined; + + async #ensureAsyncCompiler(): Promise { + if (this.#asyncCompiler) { + return this.#asyncCompiler; + } + + // Import and initialize the async compiler on the main thread. + this.#asyncCompilerPromise ??= import('sass-embedded').then(({ initAsyncCompiler }) => + initAsyncCompiler(), + ); + + try { + this.#asyncCompiler = await this.#asyncCompilerPromise; + } finally { + this.#asyncCompilerPromise = undefined; + } + + return this.#asyncCompiler; + } + + /** + * Provides information about the Sass implementation. + * This mimics enough of the `sass-embedded` value to be used with the `sass-loader`. + */ + get info(): string { + return 'sass-embedded\tasync-compiler'; + } + + /** + * The synchronous render function is not used by the `sass-loader`. + */ + compileString(): never { + throw new Error('Sass compileString is not supported.'); + } + + /** + * Asynchronously request a Sass stylesheet to be rendered using the native embedded compiler. + * + * @param source The contents to compile. + * @param options The `sass-embedded` options to use when rendering the stylesheet. + */ + async compileStringAsync( + source: string, + options: StringOptions<'async'>, + ): Promise { + // The CLI's configuration does not use or expose the ability to define custom Sass functions + if (options.functions && Object.keys(options.functions).length > 0) { + throw new Error('Sass custom functions are not supported.'); + } + + const { functions, importers, importer, url, logger, ...serializableOptions } = options; + + let finalImporters: + (Importer<'async'> | FileImporter<'async'> | NodePackageImporter)[] | undefined; + let loadPaths = options.loadPaths; + const entryDirectory = url ? dirname(fileURLToPath(url)) : process.cwd(); + const directoryCache = new Map(); + const rebaseSourceMaps = options.sourceMap ? new Map() : undefined; + + if (importers?.length) { + for (const importer of importers) { + if (!isFileImporter(importer)) { + throw new Error('Only File Importers are supported.'); + } + } + + finalImporters = [ + new AsyncModuleUrlRebasingImporter( + entryDirectory, + directoryCache, + rebaseSourceMaps, + async (specifier: string, options: CanonicalizeContext): Promise => { + for (const importer of importers) { + const result = await (importer as FileImporter<'async'>).findFileUrl( + specifier, + options, + ); + if (result) { + return result; + } + } + + return null; + }, + ), + ]; + } + + if (loadPaths?.length) { + finalImporters ??= []; + finalImporters.push( + new LoadPathsUrlRebasingImporter( + entryDirectory, + directoryCache, + rebaseSourceMaps, + loadPaths, + ), + ); + loadPaths = undefined; + } + + const relativeImporter = new RelativeUrlRebasingImporter( + entryDirectory, + directoryCache, + rebaseSourceMaps, + ); + + const compiler = await this.#ensureAsyncCompiler(); + const result = await compiler.compileStringAsync(source, { + ...serializableOptions, + url, + loadPaths, + importers: finalImporters, + importer: relativeImporter, + logger, + }); + + if (result.sourceMap && rebaseSourceMaps?.size) { + result.sourceMap = mergeSourceMaps( + result.sourceMap as unknown as RawSourceMap, + (file, context) => (file !== context.importer ? rebaseSourceMaps.get(file) : null), + ) as unknown as typeof result.sourceMap; + } + + return result; + } + + /** + * Shutdown the native embedded Sass compiler daemon. + * @returns A void promise that resolves when closing is complete. + */ + async close(): Promise { + if (this.#asyncCompilerPromise) { + try { + await this.#ensureAsyncCompiler(); + } catch { + // Ignore compiler initialization failures on shutdown + } + } + + if (this.#asyncCompiler) { + const compiler = this.#asyncCompiler; + this.#asyncCompiler = undefined; + await compiler.dispose(); + } + } +} diff --git a/packages/angular/build/src/tools/sass/sass-service.ts b/packages/angular/build/src/tools/sass/sass-service.ts index 2df6f85f7a52..1a7c3b7866d8 100644 --- a/packages/angular/build/src/tools/sass/sass-service.ts +++ b/packages/angular/build/src/tools/sass/sass-service.ts @@ -6,261 +6,33 @@ * found in the LICENSE file at https://angular.dev/license */ -import assert from 'node:assert'; -import { fileURLToPath, pathToFileURL } from 'node:url'; -import { MessageChannel } from 'node:worker_threads'; import type { - CanonicalizeContext, CompileResult, - Deprecation, - Exception, FileImporter, Importer, NodePackageImporter, - SourceSpan, StringOptions, -} from 'sass'; -import { maxWorkers } from '../../utils/environment-options'; -import { WorkerPool } from '../../utils/worker-pool'; - -// Polyfill Symbol.dispose if not present -// eslint-disable-next-line @typescript-eslint/no-explicit-any -(Symbol as any).dispose ??= Symbol('Symbol Dispose'); +} from 'sass-embedded'; /** - * The maximum number of Workers that will be created to execute render requests. + * Common interface for Sass service implementations. */ -const MAX_RENDER_WORKERS = maxWorkers; +export interface SassServiceImplementation { + readonly info: string; + compileStringAsync(source: string, options: StringOptions<'async'>): Promise; + close(): Promise; +} /** * All available importer types. */ -type Importers = +export type Importers = | Importer<'sync'> | Importer<'async'> | FileImporter<'sync'> | FileImporter<'async'> | NodePackageImporter; -export interface SerializableVersion { - major: number; - minor: number; - patch: number; -} - -export interface SerializableDeprecation extends Omit { - /** The version this deprecation first became active in. */ - deprecatedIn: SerializableVersion | null; - - /** The version this deprecation became obsolete in. */ - obsoleteIn: SerializableVersion | null; -} - -export type SerializableWarningMessage = ( - | { - deprecation: true; - deprecationType: SerializableDeprecation; - } - | { deprecation: false } -) & { - message: string; - span?: Omit & { url?: string }; - stack?: string; -}; - -/** - * A response from the Sass render Worker containing the result of the operation. - */ -interface RenderResponseMessage { - error?: Exception; - result?: Omit & { loadedUrls: string[] }; - warnings?: SerializableWarningMessage[]; -} - -/** - * A Sass renderer implementation that provides an interface that can be used by Webpack's - * `sass-loader`. The implementation uses a Worker thread to perform the Sass rendering - * with the `dart-sass` package. The `dart-sass` synchronous render function is used within - * the worker which can be up to two times faster than the asynchronous variant. - */ -export class SassWorkerImplementation { - #workerPool: WorkerPool | undefined; - - constructor( - private readonly rebase = false, - readonly maxThreads = MAX_RENDER_WORKERS, - ) {} - - #ensureWorkerPool(): WorkerPool { - this.#workerPool ??= new WorkerPool({ - filename: require.resolve('./worker'), - maxThreads: this.maxThreads, - }); - - return this.#workerPool; - } - - /** - * Provides information about the Sass implementation. - * This mimics enough of the `dart-sass` value to be used with the `sass-loader`. - */ - get info(): string { - return 'dart-sass\tworker'; - } - - /** - * The synchronous render function is not used by the `sass-loader`. - */ - compileString(): never { - throw new Error('Sass compileString is not supported.'); - } - - /** - * Asynchronously request a Sass stylesheet to be renderered. - * - * @param source The contents to compile. - * @param options The `dart-sass` options to use when rendering the stylesheet. - */ - async compileStringAsync( - source: string, - options: StringOptions<'async'>, - ): Promise { - // The `functions`, `logger` and `importer` options are JavaScript functions that cannot be transferred. - // If any additional function options are added in the future, they must be excluded as well. - const { functions, importers, url, logger, ...serializableOptions } = options; - - // The CLI's configuration does not use or expose the ability to define custom Sass functions - if (functions && Object.keys(functions).length > 0) { - throw new Error('Sass custom functions are not supported.'); - } - - using importerChannel = importers?.length ? this.#createImporterChannel(importers) : undefined; - - const response = (await this.#ensureWorkerPool().run( - { - source, - importerChannel, - hasLogger: !!logger, - rebase: this.rebase, - options: { - ...serializableOptions, - // URL is not serializable so to convert to string here and back to URL in the worker. - url: url ? fileURLToPath(url) : undefined, - }, - }, - { - transferList: importerChannel ? [importerChannel.port] : undefined, - }, - )) as RenderResponseMessage; - - const { result, error, warnings } = response; - - if (warnings && logger?.warn) { - for (const { message, span, ...options } of warnings) { - logger.warn(message, { - ...options, - span: span && { - ...span, - url: span.url ? pathToFileURL(span.url) : undefined, - }, - }); - } - } - - if (error) { - // Convert stringified url value required for cloning back to a URL object - const url = error.span?.url as string | undefined; - if (url) { - error.span.url = pathToFileURL(url); - } - - throw error; - } - - assert(result, 'Sass render worker should always return a result or an error'); - - return { - ...result, - // URL is not serializable so in the worker we convert to string and here back to URL. - loadedUrls: result.loadedUrls.map((p) => pathToFileURL(p)), - }; - } - - /** - * Shutdown the Sass render worker. - * Executing this method will stop any pending render requests. - * @returns A void promise that resolves when closing is complete. - */ - async close(): Promise { - if (this.#workerPool) { - try { - await this.#workerPool.destroy(); - } finally { - this.#workerPool = undefined; - } - } - } - - #createImporterChannel(importers: Iterable) { - const { port1: mainImporterPort, port2: workerImporterPort } = new MessageChannel(); - const importerSignal = new Int32Array(new SharedArrayBuffer(4)); - - mainImporterPort.on( - 'message', - ({ url, options }: { url: string; options: CanonicalizeContext }) => { - this.processImporters(importers, url, { - ...options, - // URL is not serializable so in the worker we convert to string and here back to URL. - containingUrl: options.containingUrl - ? pathToFileURL(options.containingUrl as unknown as string) - : null, - }) - .then((result) => { - mainImporterPort.postMessage(result); - }) - .catch((error) => { - mainImporterPort.postMessage(error); - }) - .finally(() => { - Atomics.store(importerSignal, 0, 1); - Atomics.notify(importerSignal, 0); - }); - }, - ); - - mainImporterPort.unref(); - - return { - port: workerImporterPort, - signal: importerSignal, - [Symbol.dispose]() { - mainImporterPort.close(); - }, - }; - } - - private async processImporters( - importers: Iterable, - url: string, - options: CanonicalizeContext, - ): Promise { - for (const importer of importers) { - if (!this.isFileImporter(importer)) { - // Importer - throw new Error('Only File Importers are supported.'); - } - - // File importer (Can be sync or aync). - const result = await importer.findFileUrl(url, options); - if (result) { - return fileURLToPath(result); - } - } - - return null; - } - - private isFileImporter(value: Importers): value is FileImporter { - return 'findFileUrl' in value; - } +export function isFileImporter(value: Importers): value is FileImporter { + return 'findFileUrl' in value; } diff --git a/packages/angular/build/src/tools/sass/sass-worker-implementation.ts b/packages/angular/build/src/tools/sass/sass-worker-implementation.ts new file mode 100644 index 000000000000..9208bef3a803 --- /dev/null +++ b/packages/angular/build/src/tools/sass/sass-worker-implementation.ts @@ -0,0 +1,245 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import assert from 'node:assert'; +import { fileURLToPath, pathToFileURL } from 'node:url'; +import { MessageChannel } from 'node:worker_threads'; +import type { + CanonicalizeContext, + CompileResult, + Deprecation, + Exception, + SourceSpan, + StringOptions, +} from 'sass-embedded'; +import { maxWorkers } from '../../utils/environment-options'; +import { WorkerPool } from '../../utils/worker-pool'; +import { type Importers, type SassServiceImplementation, isFileImporter } from './sass-service'; + +// Polyfill Symbol.dispose if not present +// eslint-disable-next-line @typescript-eslint/no-explicit-any +(Symbol as any).dispose ??= Symbol('Symbol Dispose'); + +/** + * The maximum number of Workers that will be created to execute render requests. + */ +const MAX_RENDER_WORKERS = maxWorkers; + +export interface SerializableVersion { + major: number; + minor: number; + patch: number; +} + +export interface SerializableDeprecation extends Omit { + /** The version this deprecation first became active in. */ + deprecatedIn: SerializableVersion | null; + + /** The version this deprecation became obsolete in. */ + obsoleteIn: SerializableVersion | null; +} + +export type SerializableWarningMessage = ( + | { + deprecation: true; + deprecationType: SerializableDeprecation; + } + | { deprecation: false } +) & { + message: string; + span?: Omit & { url?: string }; + stack?: string; +}; + +/** + * A response from the Sass render Worker containing the result of the operation. + */ +export interface RenderResponseMessage { + error?: Exception; + result?: Omit & { loadedUrls: string[] }; + warnings?: SerializableWarningMessage[]; +} + +/** + * A Sass renderer implementation that provides an interface that can be used by Webpack's + * `sass-loader` or as a fallback in environments that do not support native binaries. + * The implementation uses a Worker thread pool to perform Sass rendering with the pure-JS + * `sass` package. + */ +export class SassWorkerImplementation implements SassServiceImplementation { + #workerPool: WorkerPool | undefined; + + constructor( + private readonly rebase = false, + readonly maxThreads = MAX_RENDER_WORKERS, + ) {} + + #ensureWorkerPool(): WorkerPool { + this.#workerPool ??= new WorkerPool({ + filename: require.resolve('./worker'), + maxThreads: this.maxThreads, + }); + + return this.#workerPool; + } + + /** + * Provides information about the Sass implementation. + * This mimics enough of the `sass` value to be used with the `sass-loader`. + */ + get info(): string { + return 'dart-sass\tworker'; + } + + /** + * The synchronous render function is not used by the `sass-loader`. + */ + compileString(): never { + throw new Error('Sass compileString is not supported.'); + } + + /** + * Asynchronously request a Sass stylesheet to be rendered using worker threads. + * + * @param source The contents to compile. + * @param options The Sass options to use when rendering the stylesheet. + */ + async compileStringAsync( + source: string, + options: StringOptions<'async'>, + ): Promise { + // The CLI's configuration does not use or expose the ability to define custom Sass functions + if (options.functions && Object.keys(options.functions).length > 0) { + throw new Error('Sass custom functions are not supported.'); + } + + const { functions, importers, importer, url, logger, ...serializableOptions } = options; + using importerChannel = importers?.length ? this.#createImporterChannel(importers) : undefined; + + const response = (await this.#ensureWorkerPool().run( + { + source, + importerChannel, + hasLogger: !!logger, + rebase: this.rebase, + options: { + ...serializableOptions, + // URL is not serializable so to convert to string here and back to URL in the worker. + url: url ? fileURLToPath(url) : undefined, + }, + }, + { + transferList: importerChannel ? [importerChannel.port] : undefined, + }, + )) as RenderResponseMessage; + + const { result, error, warnings } = response; + + if (warnings && logger?.warn) { + for (const { message, span, ...options } of warnings) { + logger.warn(message, { + ...options, + span: span && { + ...span, + url: span.url ? pathToFileURL(span.url) : undefined, + }, + }); + } + } + + if (error) { + // Convert stringified url value required for cloning back to a URL object + const url = error.span?.url as string | undefined; + if (url) { + error.span.url = pathToFileURL(url); + } + + throw error; + } + + assert(result, 'Sass render worker should always return a result or an error'); + + return { + ...result, + // URL is not serializable so in the worker we convert to string and here back to URL. + loadedUrls: result.loadedUrls.map((p) => pathToFileURL(p)), + }; + } + + /** + * Shutdown the Sass render worker. + * Executing this method will stop any pending render requests. + * @returns A void promise that resolves when closing is complete. + */ + async close(): Promise { + if (this.#workerPool) { + const pool = this.#workerPool; + this.#workerPool = undefined; + await pool.destroy(); + } + } + + #createImporterChannel(importers: Iterable) { + const { port1: mainImporterPort, port2: workerImporterPort } = new MessageChannel(); + const importerSignal = new Int32Array(new SharedArrayBuffer(4)); + + mainImporterPort.on( + 'message', + ({ url, options }: { url: string; options: CanonicalizeContext }) => { + this.processImporters(importers, url, { + ...options, + // URL is not serializable so in the worker we convert to string and here back to URL. + containingUrl: options.containingUrl + ? pathToFileURL(options.containingUrl as unknown as string) + : null, + }) + .then((result) => { + mainImporterPort.postMessage(result); + }) + .catch((error) => { + mainImporterPort.postMessage(error); + }) + .finally(() => { + Atomics.store(importerSignal, 0, 1); + Atomics.notify(importerSignal, 0); + }); + }, + ); + + mainImporterPort.unref(); + + return { + port: workerImporterPort, + signal: importerSignal, + [Symbol.dispose]() { + mainImporterPort.close(); + }, + }; + } + + private async processImporters( + importers: Iterable, + url: string, + options: CanonicalizeContext, + ): Promise { + for (const importer of importers) { + if (!isFileImporter(importer)) { + // Importer + throw new Error('Only File Importers are supported.'); + } + + // File importer (Can be sync or aync). + const result = await importer.findFileUrl(url, options); + if (result) { + return fileURLToPath(result); + } + } + + return null; + } +} diff --git a/packages/angular/build/src/tools/sass/worker.ts b/packages/angular/build/src/tools/sass/worker.ts index 1a2e1184892f..2416fe21e5cd 100644 --- a/packages/angular/build/src/tools/sass/worker.ts +++ b/packages/angular/build/src/tools/sass/worker.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import mergeSourceMaps, { RawSourceMap } from '@ampproject/remapping'; +import mergeSourceMaps, { type DecodedSourceMap, type RawSourceMap } from '@ampproject/remapping'; import { dirname } from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; import { MessagePort, receiveMessageOnPort } from 'node:worker_threads'; @@ -25,7 +25,10 @@ import { RelativeUrlRebasingImporter, sassBindWorkaround, } from './rebasing-importer'; -import type { SerializableDeprecation, SerializableWarningMessage } from './sass-service'; +import type { + SerializableDeprecation, + SerializableWarningMessage, +} from './sass-worker-implementation'; /** * A request to render a Sass stylesheet using the supplied options. @@ -89,7 +92,7 @@ export default async function renderSassStylesheet( let warnings: SerializableWarningMessage[] | undefined; try { const directoryCache = new Map(); - const rebaseSourceMaps = options.sourceMap ? new Map() : undefined; + const rebaseSourceMaps = options.sourceMap ? new Map() : undefined; if (importerChannel) { // When a custom importer function is present, the importer request must be proxied // back to the main thread where it can be executed. diff --git a/packages/angular/build/src/tools/vite/middlewares/assets-middleware.ts b/packages/angular/build/src/tools/vite/middlewares/assets-middleware.ts index cf98614bc55b..02f54756eaff 100644 --- a/packages/angular/build/src/tools/vite/middlewares/assets-middleware.ts +++ b/packages/angular/build/src/tools/vite/middlewares/assets-middleware.ts @@ -7,7 +7,6 @@ */ import { lookup as lookupMimeType } from 'mrmime'; -import { createHash } from 'node:crypto'; import { readFileSync } from 'node:fs'; import type { ServerResponse } from 'node:http'; import { extname } from 'node:path'; @@ -15,6 +14,7 @@ import type { Connect, ViteDevServer } from 'vite' with { 'resolution-mode': 'import', }; import { ResultFile } from '../../../builders/application/results'; +import { calculateHash } from '../../../utils/hash'; import { AngularMemoryOutputFiles, AngularOutputAssets, pathnameWithoutBasePath } from '../utils'; export interface ComponentStyleRecord { @@ -50,7 +50,7 @@ export function createAngularAssetsMiddleware( // This is a workaround to serve extensionless, CSS, JS and TS files without Vite transformations. if (!extension || JS_TS_REGEXP.test(extension) || CSS_PREPROCESSOR_REGEXP.test(extension)) { const contents = readFileSync(asset.source); - const etag = `W/${createHash('sha256').update(contents).digest('hex')}`; + const etag = `W/${calculateHash(contents)}`; if (checkAndHandleEtag(req, res, etag)) { return; } @@ -238,7 +238,7 @@ export function createBuildAssetsMiddleware( const contents = outputFile.origin === 'memory' ? outputFile.contents : readHandler(outputFile.inputPath); - const etag = `W/${createHash('sha256').update(contents).digest('hex')}`; + const etag = `W/${calculateHash(contents)}`; if (checkAndHandleEtag(req, res, etag)) { return; } diff --git a/packages/angular/build/src/utils/bundle-calculator.ts b/packages/angular/build/src/utils/bundle-calculator.ts index 3349a8a40830..71a75dafde02 100644 --- a/packages/angular/build/src/utils/bundle-calculator.ts +++ b/packages/angular/build/src/utils/bundle-calculator.ts @@ -152,6 +152,8 @@ function calculateSizes(budget: BudgetEntry, stats: BudgetStats): Size[] { } abstract class Calculator { + private assetMap?: ReadonlyMap; + constructor( protected budget: BudgetEntry, protected chunks: BudgetChunk[], @@ -167,15 +169,24 @@ abstract class Calculator { return 0; } + if (!this.assetMap) { + const map = new Map(); + for (const asset of this.assets) { + map.set(asset.name, asset.size); + } + this.assetMap = map; + } + const assetMap = this.assetMap; + return chunk.files .filter((file) => !file.endsWith('.map')) .map((file) => { - const asset = this.assets.find((asset) => asset.name === file); - if (!asset) { + const assetSize = assetMap.get(file); + if (assetSize === undefined) { throw new Error(`Could not find asset for file: ${file}`); } - return asset.size; + return assetSize; }) .reduce((l, r) => l + r, 0); } diff --git a/packages/angular/build/src/utils/environment-options.ts b/packages/angular/build/src/utils/environment-options.ts index 206467e929a0..c9ac132e7887 100644 --- a/packages/angular/build/src/utils/environment-options.ts +++ b/packages/angular/build/src/utils/environment-options.ts @@ -201,6 +201,13 @@ export const usePartialSsrBuild = parseTristate(process.env['NG_BUILD_PARTIAL_SS */ export const useBabelLinker = parseTristate(process.env['NG_BUILD_BABEL_LINKER']) === true; +/** + * When `NG_BUILD_SASS_WORKER` is enabled (`1` or `true`), the worker-based + * Sass implementation will be used instead of the native asynchronous compiler. + */ +export const useSassWorker = + !!process.versions.webcontainer || parseTristate(process.env['NG_BUILD_SASS_WORKER']) === true; + const bazelBinDirectory = process.env['BAZEL_BINDIR']; const bazelExecRoot = process.env['JS_BINARY__EXECROOT']; diff --git a/packages/angular/build/src/utils/hash.ts b/packages/angular/build/src/utils/hash.ts new file mode 100644 index 000000000000..fdc5565afbb1 --- /dev/null +++ b/packages/angular/build/src/utils/hash.ts @@ -0,0 +1,74 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import assert from 'node:assert'; +import type { XXHashAPI } from 'xxhash-wasm'; + +let xxhashInstance: XXHashAPI | undefined; +let xxhashPromise: Promise | undefined; + +/** + * Initializes the xxHash WASM instance early to ensure synchronous hashing uses xxHash. + */ +export async function initializeHash(): Promise { + if (xxhashInstance) { + return; + } + + xxhashPromise ??= import('xxhash-wasm').then((m) => m.default()); + xxhashInstance = await xxhashPromise; +} + +function getXxhash(): XXHashAPI { + assert( + xxhashInstance, + 'Hash utility must be initialized by awaiting `initializeHash()` before use.', + ); + + return xxhashInstance; +} + +/** + * Calculates a fast 64-bit non-cryptographic hash of the provided content. + * Suitable for cache keys, ETags, and change detection. + */ +export function calculateHash(data: string | Uint8Array): string { + const instance = getXxhash(); + + if (typeof data === 'string') { + return instance.h64ToString(data); + } + + return instance.h64Raw(data).toString(16).padStart(16, '0'); +} + +export interface ContentHasher { + update(data: string | Uint8Array): ContentHasher; + digest(): string; +} + +/** + * Creates a streaming 64-bit non-cryptographic content hasher. + */ +export function createContentHash(): ContentHasher { + const instance = getXxhash(); + const hasher = instance.create64(); + + const contentHasher: ContentHasher = { + update(data: string | Uint8Array): ContentHasher { + hasher.update(data); + + return contentHasher; + }, + digest(): string { + return hasher.digest().toString(16).padStart(16, '0'); + }, + }; + + return contentHasher; +} diff --git a/packages/angular/build/src/utils/hash_spec.ts b/packages/angular/build/src/utils/hash_spec.ts new file mode 100644 index 000000000000..77fcbc909403 --- /dev/null +++ b/packages/angular/build/src/utils/hash_spec.ts @@ -0,0 +1,60 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { calculateHash, createContentHash, initializeHash } from './hash'; + +describe('hash utility', () => { + beforeAll(async () => { + await initializeHash(); + }); + + it('should calculate identical 64-bit hex hash for string and Buffer with same content', () => { + const text = 'export const message = "hello world";'; + const buffer = Buffer.from(text, 'utf-8'); + + const stringHash = calculateHash(text); + const bufferHash = calculateHash(buffer); + + expect(typeof stringHash).toBe('string'); + expect(stringHash.length).toBe(16); + expect(stringHash).toBe(bufferHash); + }); + + it('should calculate different hashes for different contents', () => { + const hash1 = calculateHash('const a = 1;'); + const hash2 = calculateHash('const a = 2;'); + + expect(hash1).not.toBe(hash2); + }); + + it('should support streaming multi-part hashing matching combined single-shot hash', () => { + const part1 = 'header: '; + const part2 = 'body content: '; + const part3 = 'footer'; + + const hasher = createContentHash(); + hasher.update(part1); + hasher.update(part2); + hasher.update(Buffer.from(part3, 'utf-8')); + const streamingHash = hasher.digest(); + + const singleShotHash = calculateHash(part1 + part2 + part3); + + expect(streamingHash.length).toBe(16); + expect(streamingHash).toBe(singleShotHash); + }); + + it('should handle Uint8Array chunks in streaming hasher', () => { + const hasher = createContentHash(); + hasher.update(new Uint8Array([1, 2, 3, 4])).update('some-string'); + const digest = hasher.digest(); + + expect(typeof digest).toBe('string'); + expect(digest.length).toBe(16); + }); +}); diff --git a/packages/angular/build/src/utils/i18n-options.ts b/packages/angular/build/src/utils/i18n-options.ts index 822683bef03d..6a288622d053 100644 --- a/packages/angular/build/src/utils/i18n-options.ts +++ b/packages/angular/build/src/utils/i18n-options.ts @@ -28,6 +28,7 @@ export interface I18nOptions { flatOutput?: boolean; readonly shouldInline: boolean; hasDefinedSourceLocale?: boolean; + localizeVersion?: string; } function normalizeTranslationFileOption( diff --git a/packages/angular/build/src/utils/index-file/auto-csp.ts b/packages/angular/build/src/utils/index-file/auto-csp.ts index 0e1dfe3ed916..df33f0766607 100644 --- a/packages/angular/build/src/utils/index-file/auto-csp.ts +++ b/packages/angular/build/src/utils/index-file/auto-csp.ts @@ -22,6 +22,8 @@ interface SrcScriptTag { type?: string; async: boolean; defer: boolean; + integrity?: string; + crossOrigin?: string; } /** @@ -106,11 +108,14 @@ export async function autoCsp(html: string, unsafeEval = false): Promise // If there are any interesting attributes, note them down. const scriptType = getScriptAttributeValue(tag, 'type'); if (shouldDynamicallyLoadScriptTagBasedOnType(scriptType)) { + const crossOrigin = getScriptAttributeValue(tag, 'crossorigin'); scriptContent.push({ src: src, type: scriptType, async: getScriptAttributeValue(tag, 'async') !== undefined, defer: getScriptAttributeValue(tag, 'defer') !== undefined, + integrity: getScriptAttributeValue(tag, 'integrity'), + crossOrigin: crossOrigin === '' ? 'anonymous' : crossOrigin, }); return; // Skip writing my script tag until we've read it all. @@ -267,36 +272,52 @@ function createLoaderScript(srcList: SrcScriptTag[], enableTrustedTypes = false) const srcAttr = encodeURI(s.src).replaceAll("'", "\\'"); // Can only be 'module' or a JS MIME type or an empty string. const typeAttr = s.type ? "'" + s.type + "'" : "''"; - const asyncAttr = s.async ? 'true' : 'false'; - const deferAttr = s.defer ? 'true' : 'false'; + const asyncAttr = !!s.async; + const deferAttr = !!s.defer; + const integrityAttr = JSON.stringify(s.integrity ?? null).replaceAll('<', '\\u003c'); + const crossOriginAttr = JSON.stringify(s.crossOrigin ?? null).replaceAll('<', '\\u003c'); - return `['${srcAttr}', ${typeAttr}, ${asyncAttr}, ${deferAttr}]`; + return `['${srcAttr}', ${typeAttr}, ${asyncAttr}, ${deferAttr}, ${integrityAttr}, ${crossOriginAttr}]`; }) .join(); return enableTrustedTypes - ? ` - var scripts = [${srcListFormatted}]; - var policy = self.trustedTypes && self.trustedTypes.createPolicy ? + ? `(() => { + const scripts = [${srcListFormatted}]; + const policy = self.trustedTypes && self.trustedTypes.createPolicy ? self.trustedTypes.createPolicy('angular#auto-csp', {createScriptURL: function(u) { - return scripts.includes(u) ? u : null; + return scripts.some(function(s) { return s[0] === u; }) ? u : null; }}) : { createScriptURL: function(u) { return u; } }; scripts.forEach(function(scriptUrl) { - var s = document.createElement('script'); + const s = document.createElement('script'); s.src = policy.createScriptURL(scriptUrl[0]); s.type = scriptUrl[1]; - s.async = !!scriptUrl[2]; - s.defer = !!scriptUrl[3]; + s.async = scriptUrl[2]; + s.defer = scriptUrl[3]; + if (scriptUrl[4]) { + s.integrity = scriptUrl[4]; + } + if (scriptUrl[5]) { + s.crossOrigin = scriptUrl[5]; + } document.lastElementChild.appendChild(s); - });\n` - : ` - var scripts = [${srcListFormatted}]; + }); +})();\n` + : `(() => { + const scripts = [${srcListFormatted}]; scripts.forEach(function(scriptUrl) { - var s = document.createElement('script'); + const s = document.createElement('script'); s.src = scriptUrl[0]; s.type = scriptUrl[1]; - s.async = !!scriptUrl[2]; - s.defer = !!scriptUrl[3]; + s.async = scriptUrl[2]; + s.defer = scriptUrl[3]; + if (scriptUrl[4]) { + s.integrity = scriptUrl[4]; + } + if (scriptUrl[5]) { + s.crossOrigin = scriptUrl[5]; + } document.lastElementChild.appendChild(s); - });\n`; + }); +})();\n`; } diff --git a/packages/angular/build/src/utils/index-file/auto-csp_spec.ts b/packages/angular/build/src/utils/index-file/auto-csp_spec.ts index efbd338233ad..29e9bff68074 100644 --- a/packages/angular/build/src/utils/index-file/auto-csp_spec.ts +++ b/packages/angular/build/src/utils/index-file/auto-csp_spec.ts @@ -58,7 +58,7 @@ describe('auto-csp', () => { const csps = getCsps(result); expect(csps).toHaveSize(1); expect(csps[0]).toMatch(CSP_SINGLE_HASH_REGEX); - expect(result).toContain(`var scripts = [['./main.js', '', false, false]];`); + expect(result).toContain(`const scripts = [['./main.js', '', false, false, null, null]];`); }); it('should rewrite a single source script in place', async () => { @@ -78,7 +78,7 @@ describe('auto-csp', () => { expect(csps[0]).toMatch(CSP_SINGLE_HASH_REGEX); // Our loader script appears after the HTML text content. expect(result).toMatch( - /Some text<\/div>\s*`); @@ -166,12 +166,12 @@ describe('auto-csp', () => { // Loader script for main.js and main2.js appear after 'foo' and before 'bar'. expect(result).toMatch( // eslint-disable-next-line max-len - /console.log\('foo'\);<\/script>\s* +
Some text
+ + + `); + + const csps = getCsps(result); + expect(csps).toHaveSize(1); + expect(csps[0]).toMatch(CSP_SINGLE_HASH_REGEX); + expect(result).toContain( + `const scripts = [['./main.js', 'module', false, false, "sha384-xyz123", "anonymous"]];`, + ); + }); + + it('should preserve only integrity attribute when crossorigin is omitted', async () => { + const result = await autoCsp(` + + + + + +
Some text
+ + + `); + + const csps = getCsps(result); + expect(csps).toHaveSize(1); + expect(csps[0]).toMatch(CSP_SINGLE_HASH_REGEX); + expect(result).toContain( + `const scripts = [['./main.js', '', false, false, "sha384-xyz123", null]];`, + ); + }); + + it('should map empty crossorigin attribute to anonymous', async () => { + const result = await autoCsp(` + + + + + +
Some text
+ + + `); + + const csps = getCsps(result); + expect(csps).toHaveSize(1); + expect(csps[0]).toMatch(CSP_SINGLE_HASH_REGEX); + expect(result).toContain( + `const scripts = [['./main.js', '', false, false, null, "anonymous"]];`, + ); + }); }); diff --git a/packages/angular/build/src/utils/index-file/inline-critical-css.ts b/packages/angular/build/src/utils/index-file/inline-critical-css.ts index e5106f39cf6b..716d34bf037e 100644 --- a/packages/angular/build/src/utils/index-file/inline-critical-css.ts +++ b/packages/angular/build/src/utils/index-file/inline-critical-css.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.dev/license */ -import Beasties from 'beasties'; import { readFile } from 'node:fs/promises'; /** @@ -95,147 +94,184 @@ interface PartialDocument { querySelector(selector: string): PartialHTMLElement | null; } -/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */ - -// We use Typescript declaration merging because `embedLinkedStylesheet` it's not declared in -// the `Beasties` types which means that we can't call the `super` implementation. interface BeastiesBase { embedLinkedStylesheet(link: PartialHTMLElement, document: PartialDocument): Promise; + readFile(path: string): Promise; + process(html: string): Promise; } -class BeastiesBase extends Beasties {} -/* eslint-enable @typescript-eslint/no-unsafe-declaration-merging */ - -class BeastiesExtended extends BeastiesBase { - readonly warnings: string[] = []; - readonly errors: string[] = []; - private addedCspScriptsDocuments = new WeakSet(); - private documentNonces = new WeakMap(); - - constructor( - private readonly optionsExtended: InlineCriticalCssProcessorOptions & - InlineCriticalCssProcessOptions, - ) { - super({ - logger: { - warn: (s: string) => this.warnings.push(s), - error: (s: string) => this.errors.push(s), - info: () => {}, - }, - logLevel: 'warn', - path: optionsExtended.outputPath, - publicPath: optionsExtended.deployUrl, - compress: !!optionsExtended.minify, - pruneSource: false, - reduceInlineStyles: false, - mergeStylesheets: false, - // Note: if `preload` changes to anything other than `media`, the logic in - // `embedLinkedStylesheet` will have to be updated. - preload: 'media', - noscriptFallback: true, - inlineFonts: true, - }); - } - public override readFile(path: string): Promise { - const readAsset = this.optionsExtended.readAsset; +interface BeastiesExtendedInstance { + readonly warnings: string[]; + readonly errors: string[]; + process(html: string): Promise; +} - return readAsset ? readAsset(path) : readFile(path, 'utf-8'); +let beastiesClassPromise: + | Promise< + new ( + options: InlineCriticalCssProcessorOptions & InlineCriticalCssProcessOptions, + ) => BeastiesExtendedInstance + > + | undefined; + +async function getBeastiesClass(): Promise< + new ( + options: InlineCriticalCssProcessorOptions & InlineCriticalCssProcessOptions, + ) => BeastiesExtendedInstance +> { + if (beastiesClassPromise) { + return beastiesClassPromise; } - /** - * Override of the Beasties `embedLinkedStylesheet` method - * that makes it work with Angular's CSP APIs. - */ - override async embedLinkedStylesheet( - link: PartialHTMLElement, - document: PartialDocument, - ): Promise { - if (link.getAttribute('media') === 'print' && link.next?.name === 'noscript') { - // Workaround for https://github.com/GoogleChromeLabs/critters/issues/64 - // NB: this is only needed for the webpack based builders. - const media = link.getAttribute('onload')?.match(MEDIA_SET_HANDLER_PATTERN); - if (media) { - link.removeAttribute('onload'); - link.setAttribute('media', media[1]); - link?.next?.remove(); - } - } - - const returnValue = await super.embedLinkedStylesheet(link, document); - const cspNonce = this.findCspNonce(document); - - if (cspNonce || this.optionsExtended.autoCsp) { - const beastiesMedia = link.getAttribute('onload')?.match(MEDIA_SET_HANDLER_PATTERN); + beastiesClassPromise = import('beasties').then(({ default: Beasties }) => { + return class BeastiesExtended + extends (Beasties as unknown as new (options: unknown) => BeastiesBase) + implements BeastiesExtendedInstance + { + private _warnings?: string[]; + private _errors?: string[]; - if (beastiesMedia) { - // If there's a Beasties-generated `onload` handler and the file has an Angular CSP nonce, - // we have to remove the handler, because it's incompatible with CSP. We save the value - // in a different attribute and we generate a script tag with the nonce that uses - // `addEventListener` to apply the media query instead. - link.removeAttribute('onload'); - link.setAttribute(CSP_MEDIA_ATTR, beastiesMedia[1]); - this.conditionallyInsertCspLoadingScript(document, cspNonce, link); + get warnings(): string[] { + return (this._warnings ??= []); } - // Ideally we would hook in at the time Beasties inserts the `style` tags, but there isn't - // a way of doing that at the moment so we fall back to doing it any time a `link` tag is - // inserted. We mitigate it by only iterating the direct children of the `` which - // should be pretty shallow. - if (cspNonce) { - document.head.children.forEach((child) => { - if (child.tagName === 'style' && !child.hasAttribute('nonce')) { - child.setAttribute('nonce', cspNonce); - } + get errors(): string[] { + return (this._errors ??= []); + } + private addedCspScriptsDocuments = new WeakSet(); + private documentNonces = new WeakMap(); + + constructor( + private readonly optionsExtended: InlineCriticalCssProcessorOptions & + InlineCriticalCssProcessOptions, + ) { + super({ + logger: { + warn: (s: string) => this.warnings.push(s), + error: (s: string) => this.errors.push(s), + info: () => {}, + }, + logLevel: 'warn', + path: optionsExtended.outputPath, + publicPath: optionsExtended.deployUrl, + compress: !!optionsExtended.minify, + pruneSource: false, + reduceInlineStyles: false, + mergeStylesheets: false, + // Note: if `preload` changes to anything other than `media`, the logic in + // `embedLinkedStylesheet` will have to be updated. + preload: 'media', + noscriptFallback: true, + inlineFonts: true, }); } - } - return returnValue; - } + public override readFile(path: string): Promise { + const readAsset = this.optionsExtended.readAsset; - /** - * Finds the CSP nonce for a specific document. - */ - private findCspNonce(document: PartialDocument): string | null { - if (this.documentNonces.has(document)) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - return this.documentNonces.get(document)!; - } + return readAsset ? readAsset(path) : readFile(path, 'utf-8'); + } - // HTML attribute are case-insensitive, but the parser used by Beasties is case-sensitive. - const nonceElement = document.querySelector('[ngCspNonce], [ngcspnonce]'); - const cspNonce = - nonceElement?.getAttribute('ngCspNonce') || nonceElement?.getAttribute('ngcspnonce') || null; + /** + * Override of the Beasties `embedLinkedStylesheet` method + * that makes it work with Angular's CSP APIs. + */ + override async embedLinkedStylesheet( + link: PartialHTMLElement, + document: PartialDocument, + ): Promise { + if (link.getAttribute('media') === 'print' && link.next?.name === 'noscript') { + // Workaround for https://github.com/GoogleChromeLabs/critters/issues/64 + // NB: this is only needed for the webpack based builders. + const media = link.getAttribute('onload')?.match(MEDIA_SET_HANDLER_PATTERN); + if (media) { + link.removeAttribute('onload'); + link.setAttribute('media', media[1]); + link?.next?.remove(); + } + } + + const returnValue = await super.embedLinkedStylesheet(link, document); + const cspNonce = this.findCspNonce(document); + + if (cspNonce || this.optionsExtended.autoCsp) { + const beastiesMedia = link.getAttribute('onload')?.match(MEDIA_SET_HANDLER_PATTERN); + + if (beastiesMedia) { + // If there's a Beasties-generated `onload` handler and the file has an Angular CSP nonce, + // we have to remove the handler, because it's incompatible with CSP. We save the value + // in a different attribute and we generate a script tag with the nonce that uses + // `addEventListener` to apply the media query instead. + link.removeAttribute('onload'); + link.setAttribute(CSP_MEDIA_ATTR, beastiesMedia[1]); + this.conditionallyInsertCspLoadingScript(document, cspNonce, link); + } - this.documentNonces.set(document, cspNonce); + // Ideally we would hook in at the time Beasties inserts the `style` tags, but there isn't + // a way of doing that at the moment so we fall back to doing it any time a `link` tag is + // inserted. We mitigate it by only iterating the direct children of the `` which + // should be pretty shallow. + if (cspNonce) { + document.head.children.forEach((child) => { + if (child.tagName === 'style' && !child.hasAttribute('nonce')) { + child.setAttribute('nonce', cspNonce); + } + }); + } + } - return cspNonce; - } + return returnValue; + } - /** - * Inserts the `script` tag that swaps the critical CSS at runtime, - * if one hasn't been inserted into the document already. - */ - private conditionallyInsertCspLoadingScript( - document: PartialDocument, - nonce: string | null, - link: PartialHTMLElement, - ): void { - if (this.addedCspScriptsDocuments.has(document)) { - return; - } + /** + * Finds the CSP nonce for a specific document. + */ + private findCspNonce(document: PartialDocument): string | null { + if (this.documentNonces.has(document)) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return this.documentNonces.get(document)!; + } + + // HTML attribute are case-insensitive, but the parser used by Beasties is case-sensitive. + const nonceElement = document.querySelector('[ngCspNonce], [ngcspnonce]'); + const cspNonce = + nonceElement?.getAttribute('ngCspNonce') || + nonceElement?.getAttribute('ngcspnonce') || + null; + + this.documentNonces.set(document, cspNonce); + + return cspNonce; + } - const script = document.createElement('script'); - script.textContent = LINK_LOAD_SCRIPT_CONTENT; - if (nonce) { - script.setAttribute('nonce', nonce); - } + /** + * Inserts the `script` tag that swaps the critical CSS at runtime, + * if one hasn't been inserted into the document already. + */ + private conditionallyInsertCspLoadingScript( + document: PartialDocument, + nonce: string | null, + link: PartialHTMLElement, + ): void { + if (this.addedCspScriptsDocuments.has(document)) { + return; + } + + const script = document.createElement('script'); + script.textContent = LINK_LOAD_SCRIPT_CONTENT; + if (nonce) { + script.setAttribute('nonce', nonce); + } + + // Prepend the script to the head since it needs to + // run as early as possible, before the `link` tags. + document.head.insertBefore(script, link); + this.addedCspScriptsDocuments.add(document); + } + }; + }); - // Prepend the script to the head since it needs to - // run as early as possible, before the `link` tags. - document.head.insertBefore(script, link); - this.addedCspScriptsDocuments.add(document); - } + return beastiesClassPromise; } export class InlineCriticalCssProcessor { @@ -245,7 +281,8 @@ export class InlineCriticalCssProcessor { html: string, options: InlineCriticalCssProcessOptions, ): Promise<{ content: string; warnings: string[]; errors: string[] }> { - const beasties = new BeastiesExtended({ ...this.options, ...options }); + const BeastiesClass = await getBeastiesClass(); + const beasties = new BeastiesClass({ ...this.options, ...options }); const content = await beasties.process(html); return { diff --git a/packages/angular/build/src/utils/server-rendering/esm-in-memory-loader/loader-hooks.ts b/packages/angular/build/src/utils/server-rendering/esm-in-memory-loader/loader-hooks.ts index 1d0d9df32d30..6f21bfbc739b 100644 --- a/packages/angular/build/src/utils/server-rendering/esm-in-memory-loader/loader-hooks.ts +++ b/packages/angular/build/src/utils/server-rendering/esm-in-memory-loader/loader-hooks.ts @@ -16,6 +16,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url'; * @note For some unknown reason, setting `globalThis.ngServerMode = true` does not work when using ESM loader hooks. */ const NG_SERVER_MODE_INIT_BYTES = new TextEncoder().encode('var ngServerMode=true;'); +const UTF8_DECODER = new TextDecoder(); /** * Node.js ESM loader to redirect imports to in memory files. @@ -25,12 +26,12 @@ const NG_SERVER_MODE_INIT_BYTES = new TextEncoder().encode('var ngServerMode=tru const MEMORY_URL_SCHEME = 'memory://'; export interface ESMInMemoryFileLoaderWorkerData { - outputFiles: Record; + outputFiles: Record; workspaceRoot: string; } let memoryVirtualRootUrl: string; -let outputFiles: Record; +let outputFiles: Record; export function initialize(data: ESMInMemoryFileLoaderWorkerData) { // This path does not actually exist but is used to overlay the in memory files with the @@ -84,7 +85,7 @@ export function resolve( } catch {} if ( - specifierUrl?.pathname && + specifierUrl?.href.startsWith(memoryVirtualRootUrl) && Object.hasOwn(outputFiles, specifierUrl.href.slice(memoryVirtualRootUrl.length)) ) { return { @@ -114,12 +115,14 @@ export async function load(url: string, context: { format?: string | null }, nex // Load the file from memory if the URL is based in the virtual root if (url.startsWith(memoryVirtualRootUrl)) { - const source = outputFiles[url.slice(memoryVirtualRootUrl.length)]; - assert(source !== undefined, 'Resolved in-memory ESM file should always exist: ' + url); + const rawSource = outputFiles[url.slice(memoryVirtualRootUrl.length)]; + assert(rawSource !== undefined, 'Resolved in-memory ESM file should always exist: ' + url); + + const source = typeof rawSource === 'string' ? rawSource : UTF8_DECODER.decode(rawSource); // In-memory files have already been transformer during bundling and can be returned directly return { - format, + format: format ?? 'module', shortCircuit: true, source, }; @@ -128,14 +131,15 @@ export async function load(url: string, context: { format?: string | null }, nex // Only module files potentially require transformation. Angular libraries that would // need linking are ESM only. if (format === 'module' && isFileProtocol(url)) { - const filePath = fileURLToPath(url); - let source = await readFile(filePath); - - if (filePath.includes('@angular/')) { - // Prepend 'var ngServerMode=true;' to the source. - source = Buffer.concat([NG_SERVER_MODE_INIT_BYTES, source]); + // Check url instead of filePath so the check is robust across Windows and POSIX path separators. + if (!url.includes('/@angular/')) { + return nextLoad(url, context); } + const filePath = fileURLToPath(url); + const fileBytes = await readFile(filePath); + const source = Buffer.concat([NG_SERVER_MODE_INIT_BYTES, fileBytes]); + return { format, shortCircuit: true, diff --git a/packages/angular/build/src/utils/server-rendering/esm-in-memory-loader/loader-hooks_spec.ts b/packages/angular/build/src/utils/server-rendering/esm-in-memory-loader/loader-hooks_spec.ts new file mode 100644 index 000000000000..2b7622145378 --- /dev/null +++ b/packages/angular/build/src/utils/server-rendering/esm-in-memory-loader/loader-hooks_spec.ts @@ -0,0 +1,246 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { initialize, load, resolve } from './loader-hooks'; +import { createSharedServerFiles } from './utils'; + +describe('esm-in-memory-loader loader-hooks', () => { + const workspaceRoot = '/mock/workspace/root'; + const sharedFiles = createSharedServerFiles({ + 'main.server.mjs': 'export const main = true;', + 'chunk-abc.mjs': 'export const chunk = "abc";', + 'nested/chunk-sub.mjs': 'export const sub = "sub";', + 'utf8.mjs': 'export const text = "🔥 UTF-8 🚀";', + 'empty.mjs': '', + }); + + beforeEach(() => { + initialize({ + workspaceRoot, + outputFiles: sharedFiles, + }); + }); + + describe('resolve', () => { + it('should resolve memory:// URLs into virtual filesystem URLs', () => { + const nextResolve = jasmine.createSpy('nextResolve'); + const memoryUrl = new URL('./main.server.mjs', 'memory://').href; + const result = resolve(memoryUrl, { parentURL: undefined }, nextResolve); + + expect(nextResolve).not.toHaveBeenCalled(); + expect(result.format).toBe('module'); + expect(result.shortCircuit).toBeTrue(); + expect(result.url).toContain('/.angular/prerender-root/'); + expect(result.url).toContain('/main.server.mjs'); + }); + + it('should fail when memory:// URL is malformed', () => { + const nextResolve = jasmine.createSpy('nextResolve'); + expect(() => { + resolve('memory://::invalid', { parentURL: undefined }, nextResolve); + }).toThrowMatching((err: Error) => + err.message.includes('External code attempted to use malformed memory scheme'), + ); + expect(nextResolve).not.toHaveBeenCalled(); + }); + + it('should resolve relative specifiers within in-memory files', () => { + const memoryUrl = new URL('./main.server.mjs', 'memory://').href; + const rootResolve = resolve(memoryUrl, { parentURL: undefined }, () => {}); + const parentURL = rootResolve.url; + + const nextResolve = jasmine.createSpy('nextResolve'); + const result = resolve('./chunk-abc.mjs', { parentURL }, nextResolve); + + expect(nextResolve).not.toHaveBeenCalled(); + expect(result.format).toBe('module'); + expect(result.shortCircuit).toBeTrue(); + expect(result.url).toContain('/chunk-abc.mjs'); + }); + + it('should resolve relative specifiers navigating parent directories within in-memory files', () => { + const memoryUrl = new URL('./nested/chunk-sub.mjs', 'memory://').href; + const rootResolve = resolve(memoryUrl, { parentURL: undefined }, () => {}); + const parentURL = rootResolve.url; + + const nextResolve = jasmine.createSpy('nextResolve'); + const result = resolve('../chunk-abc.mjs', { parentURL }, nextResolve); + + expect(nextResolve).not.toHaveBeenCalled(); + expect(result.format).toBe('module'); + expect(result.shortCircuit).toBeTrue(); + expect(result.url).toContain('/chunk-abc.mjs'); + }); + + it('should fail when relative specifier from in-memory file does not exist', () => { + const memoryUrl = new URL('./main.server.mjs', 'memory://').href; + const rootResolve = resolve(memoryUrl, { parentURL: undefined }, () => {}); + const parentURL = rootResolve.url; + + const nextResolve = jasmine.createSpy('nextResolve'); + expect(() => { + resolve('./non-existent.mjs', { parentURL }, nextResolve); + }).toThrowMatching((err: Error) => + err.message.includes('In-memory ESM relative file should always exist'), + ); + expect(nextResolve).not.toHaveBeenCalled(); + }); + + it('should rewrite parentURL to index.js in virtual root for bare package specifiers', () => { + const memoryUrl = new URL('./main.server.mjs', 'memory://').href; + const rootResolve = resolve(memoryUrl, { parentURL: undefined }, () => {}); + const parentURL = rootResolve.url; + + const nextResolve = jasmine + .createSpy('nextResolve') + .and.returnValue({ url: 'file:///some/node_modules/@angular/core/index.js' }); + const result = resolve('@angular/core', { parentURL }, nextResolve); + + expect(nextResolve).toHaveBeenCalledWith( + '@angular/core', + jasmine.objectContaining({ + parentURL: jasmine.stringMatching(/\/\.angular\/prerender-root\/[^/]+\/index\.js$/), + }), + ); + expect(result.url).toBe('file:///some/node_modules/@angular/core/index.js'); + }); + + it('should delegate to nextResolve for external non-memory URLs', () => { + const nextResolve = jasmine + .createSpy('nextResolve') + .and.returnValue({ url: 'file:///some/ext/pkg' }); + const result = resolve('some-pkg', { parentURL: 'file:///some/ext/file.js' }, nextResolve); + + expect(nextResolve).toHaveBeenCalledWith('some-pkg', { + parentURL: 'file:///some/ext/file.js', + }); + expect(result.url).toBe('file:///some/ext/pkg'); + }); + }); + + describe('load', () => { + it('should load in-memory file source from SharedArrayBuffer backed Uint8Array', async () => { + const memoryUrl = new URL('./main.server.mjs', 'memory://').href; + const rootResolve = resolve(memoryUrl, { parentURL: undefined }, () => {}); + const nextLoad = jasmine.createSpy('nextLoad'); + + const result = await load(rootResolve.url, { format: 'module' }, nextLoad); + + expect(nextLoad).not.toHaveBeenCalled(); + expect(result.format).toBe('module'); + expect(result.shortCircuit).toBeTrue(); + expect(result.source).toBe('export const main = true;'); + }); + + it('should load in-memory file with multi-byte UTF-8 characters', async () => { + const memoryUrl = new URL('./utf8.mjs', 'memory://').href; + const rootResolve = resolve(memoryUrl, { parentURL: undefined }, () => {}); + const nextLoad = jasmine.createSpy('nextLoad'); + + const result = await load(rootResolve.url, { format: 'module' }, nextLoad); + + expect(nextLoad).not.toHaveBeenCalled(); + expect(result.format).toBe('module'); + expect(result.shortCircuit).toBeTrue(); + expect(result.source).toBe('export const text = "🔥 UTF-8 🚀";'); + }); + + it('should load in-memory file with empty content', async () => { + const memoryUrl = new URL('./empty.mjs', 'memory://').href; + const rootResolve = resolve(memoryUrl, { parentURL: undefined }, () => {}); + const nextLoad = jasmine.createSpy('nextLoad'); + + const result = await load(rootResolve.url, { format: 'module' }, nextLoad); + + expect(nextLoad).not.toHaveBeenCalled(); + expect(result.format).toBe('module'); + expect(result.shortCircuit).toBeTrue(); + expect(result.source).toBe(''); + }); + + it('should load in-memory file source with non-zero byteOffset in Uint8Array', async () => { + const target = 'export const sliced = 42;'; + const fullBuffer = Buffer.from(`__PADDING__${target}__MORE__`); + const offset = Buffer.byteLength('__PADDING__', 'utf-8'); + const length = Buffer.byteLength(target, 'utf-8'); + const subView = new Uint8Array(fullBuffer.buffer, fullBuffer.byteOffset + offset, length); + + initialize({ + workspaceRoot, + outputFiles: { + 'sliced.mjs': subView, + }, + }); + + const memoryUrl = new URL('./sliced.mjs', 'memory://').href; + const rootResolve = resolve(memoryUrl, { parentURL: undefined }, () => {}); + const nextLoad = jasmine.createSpy('nextLoad'); + + const result = await load(rootResolve.url, { format: 'module' }, nextLoad); + + expect(nextLoad).not.toHaveBeenCalled(); + expect(result.format).toBe('module'); + expect(result.shortCircuit).toBeTrue(); + expect(result.source).toBe(target); + }); + + it('should load in-memory file source when outputFiles contain string values', async () => { + initialize({ + workspaceRoot, + outputFiles: { + 'string-file.mjs': 'export const fromString = 1;', + }, + }); + + const memoryUrl = new URL('./string-file.mjs', 'memory://').href; + const rootResolve = resolve(memoryUrl, { parentURL: undefined }, () => {}); + const nextLoad = jasmine.createSpy('nextLoad'); + + const result = await load(rootResolve.url, { format: 'module' }, nextLoad); + + expect(nextLoad).not.toHaveBeenCalled(); + expect(result.format).toBe('module'); + expect(result.shortCircuit).toBeTrue(); + expect(result.source).toBe('export const fromString = 1;'); + }); + + it('should reject when in-memory file does not exist in outputFiles', async () => { + const memoryUrl = new URL('./main.server.mjs', 'memory://').href; + const rootResolve = resolve(memoryUrl, { parentURL: undefined }, () => {}); + const nonExistentVirtualUrl = rootResolve.url.replace('main.server.mjs', 'missing.mjs'); + const nextLoad = jasmine.createSpy('nextLoad'); + + await expectAsync( + load(nonExistentVirtualUrl, { format: 'module' }, nextLoad), + ).toBeRejectedWithError(/Resolved in-memory ESM file should always exist/); + expect(nextLoad).not.toHaveBeenCalled(); + }); + + it('should delegate to nextLoad for non-angular file URLs', async () => { + const nextLoad = jasmine.createSpy('nextLoad').and.resolveTo({ format: 'module' }); + const result = await load( + 'file:///workspace/node_modules/rxjs/index.js', + { format: 'module' }, + nextLoad, + ); + + expect(nextLoad).toHaveBeenCalledWith('file:///workspace/node_modules/rxjs/index.js', { + format: 'module', + }); + expect(result.format).toBe('module'); + }); + + it('should delegate to nextLoad for non-memory non-file URLs', async () => { + const nextLoad = jasmine.createSpy('nextLoad').and.resolveTo({ format: 'builtin' }); + const result = await load('node:fs', { format: 'builtin' }, nextLoad); + + expect(nextLoad).toHaveBeenCalledWith('node:fs'); + expect(result.format).toBe('builtin'); + }); + }); +}); diff --git a/packages/angular/build/src/utils/server-rendering/esm-in-memory-loader/utils.ts b/packages/angular/build/src/utils/server-rendering/esm-in-memory-loader/utils.ts index 3af354f6ba0f..7eb2bd1f5c49 100644 --- a/packages/angular/build/src/utils/server-rendering/esm-in-memory-loader/utils.ts +++ b/packages/angular/build/src/utils/server-rendering/esm-in-memory-loader/utils.ts @@ -11,3 +11,41 @@ import { pathToFileURL } from 'node:url'; export const IMPORT_EXEC_ARGV = '--import=' + pathToFileURL(join(__dirname, 'register-hooks.js')).href; + +/** + * Creates a shared zero-copy `Uint8Array` backed by a `SharedArrayBuffer` for the given file content. + */ +export function createSharedFile(content: string | Uint8Array): Uint8Array { + if (typeof content === 'string') { + const byteLength = Buffer.byteLength(content, 'utf-8'); + const sab = new SharedArrayBuffer(byteLength); + Buffer.from(sab).write(content, 'utf-8'); + + return new Uint8Array(sab); + } + + if (content.buffer instanceof SharedArrayBuffer) { + return content; + } + + const sab = new SharedArrayBuffer(content.byteLength); + const view = new Uint8Array(sab); + view.set(content); + + return view; +} + +/** + * Creates shared zero-copy `Uint8Array` views backed by `SharedArrayBuffer` for all output files. + */ +export function createSharedServerFiles( + outputFiles: Record, +): Record { + const sharedFiles: Record = {}; + + for (const [key, value] of Object.entries(outputFiles)) { + sharedFiles[key] = createSharedFile(value); + } + + return sharedFiles; +} diff --git a/packages/angular/build/src/utils/server-rendering/esm-in-memory-loader/utils_spec.ts b/packages/angular/build/src/utils/server-rendering/esm-in-memory-loader/utils_spec.ts new file mode 100644 index 000000000000..b7bb2844b66e --- /dev/null +++ b/packages/angular/build/src/utils/server-rendering/esm-in-memory-loader/utils_spec.ts @@ -0,0 +1,147 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { createSharedFile, createSharedServerFiles } from './utils'; + +describe('server-rendering shared file utilities', () => { + describe('createSharedFile', () => { + it('should create a Uint8Array backed by SharedArrayBuffer from string', () => { + const text = 'console.log("hello world");'; + const shared = createSharedFile(text); + + expect(shared instanceof Uint8Array).toBeTrue(); + expect(shared.buffer instanceof SharedArrayBuffer).toBeTrue(); + expect(shared.byteLength).toBe(Buffer.byteLength(text, 'utf-8')); + + const decoded = Buffer.from(shared.buffer, shared.byteOffset, shared.byteLength).toString( + 'utf-8', + ); + expect(decoded).toBe(text); + }); + + it('should correctly handle multi-byte UTF-8 characters', () => { + const text = 'export const greeting = "🚀 Привет, мир! 🌍";'; + const shared = createSharedFile(text); + + expect(shared.buffer instanceof SharedArrayBuffer).toBeTrue(); + const decoded = Buffer.from(shared.buffer, shared.byteOffset, shared.byteLength).toString( + 'utf-8', + ); + expect(decoded).toBe(text); + }); + + it('should handle empty string', () => { + const shared = createSharedFile(''); + + expect(shared.buffer instanceof SharedArrayBuffer).toBeTrue(); + expect(shared.byteLength).toBe(0); + const decoded = Buffer.from(shared.buffer, shared.byteOffset, shared.byteLength).toString( + 'utf-8', + ); + expect(decoded).toBe(''); + }); + + it('should handle empty Uint8Array', () => { + const shared = createSharedFile(new Uint8Array(0)); + + expect(shared.buffer instanceof SharedArrayBuffer).toBeTrue(); + expect(shared.byteLength).toBe(0); + }); + + it('should return the same Uint8Array if already backed by SharedArrayBuffer', () => { + const text = 'export default 42;'; + const shared1 = createSharedFile(text); + const shared2 = createSharedFile(shared1); + + expect(shared2).toBe(shared1); + expect(shared2.buffer).toBe(shared1.buffer); + }); + + it('should return existing Uint8Array view if already backed by SharedArrayBuffer with non-zero byteOffset', () => { + const text = '0123456789abcdef'; + const shared1 = createSharedFile(text); + const subView = new Uint8Array(shared1.buffer, 4, 8); + const shared2 = createSharedFile(subView); + + expect(shared2).toBe(subView); + expect(shared2.byteOffset).toBe(4); + expect(shared2.byteLength).toBe(8); + + const decoded = Buffer.from(shared2.buffer, shared2.byteOffset, shared2.byteLength).toString( + 'utf-8', + ); + expect(decoded).toBe('456789ab'); + }); + + it('should convert standard Uint8Array to SharedArrayBuffer backed Uint8Array', () => { + const regularBuffer = Buffer.from('export const test = true;'); + expect(regularBuffer.buffer instanceof SharedArrayBuffer).toBeFalse(); + + const shared = createSharedFile(regularBuffer); + expect(shared.buffer instanceof SharedArrayBuffer).toBeTrue(); + + const decoded = Buffer.from(shared.buffer, shared.byteOffset, shared.byteLength).toString( + 'utf-8', + ); + expect(decoded).toBe('export const test = true;'); + }); + + it('should handle Uint8Array slice with non-zero byteOffset from standard ArrayBuffer', () => { + const target = 'export const data = 123;'; + const fullBuffer = Buffer.from(`__PREFIX__${target}__SUFFIX__`); + const offset = Buffer.byteLength('__PREFIX__', 'utf-8'); + const length = Buffer.byteLength(target, 'utf-8'); + const subView = new Uint8Array(fullBuffer.buffer, fullBuffer.byteOffset + offset, length); + + const shared = createSharedFile(subView); + expect(shared.buffer instanceof SharedArrayBuffer).toBeTrue(); + expect(shared.byteLength).toBe(length); + + const decoded = Buffer.from(shared.buffer, shared.byteOffset, shared.byteLength).toString( + 'utf-8', + ); + expect(decoded).toBe(target); + }); + }); + + describe('createSharedServerFiles', () => { + it('should convert all entries in a record to SharedArrayBuffer backed Uint8Arrays', () => { + const files: Record = { + 'main.server.mjs': 'export default function() {}', + 'chunk-1.mjs': 'export const x = 1;', + 'render-utils.mjs': Buffer.from('export const helper = () => true;'), + }; + + const sharedFiles = createSharedServerFiles(files); + + expect(Object.keys(sharedFiles)).toEqual([ + 'main.server.mjs', + 'chunk-1.mjs', + 'render-utils.mjs', + ]); + + for (const [key, shared] of Object.entries(sharedFiles)) { + expect(shared instanceof Uint8Array).toBeTrue(); + expect(shared.buffer instanceof SharedArrayBuffer).toBeTrue(); + + const expectedText = + typeof files[key] === 'string' ? files[key] : (files[key] as Buffer).toString('utf-8'); + + const decoded = Buffer.from(shared.buffer, shared.byteOffset, shared.byteLength).toString( + 'utf-8', + ); + expect(decoded).toBe(expectedText); + } + }); + + it('should handle empty file map', () => { + const sharedFiles = createSharedServerFiles({}); + expect(Object.keys(sharedFiles).length).toBe(0); + }); + }); +}); diff --git a/packages/angular/build/src/utils/server-rendering/manifest.ts b/packages/angular/build/src/utils/server-rendering/manifest.ts index bd294b1e7e4b..1ee430a76333 100644 --- a/packages/angular/build/src/utils/server-rendering/manifest.ts +++ b/packages/angular/build/src/utils/server-rendering/manifest.ts @@ -7,8 +7,8 @@ */ import type { Metafile } from 'esbuild'; +import { Buffer } from 'node:buffer'; import { extname } from 'node:path'; -import { runInThisContext } from 'node:vm'; import { NormalizedApplicationBuildOptions } from '../../builders/application/options'; import { type BuildOutputFile, @@ -174,9 +174,14 @@ export function generateAngularServerAppManifest( ), ); - // This is needed because JavaScript engines script parser convert `\r\n` to `\n` in template literals, - // which can result in an incorrect byte length. - const size = runInThisContext(`new TextEncoder().encode(\`${escapedContent}\`).byteLength`); + // JavaScript engine script parsers normalize `\r\n` (2 bytes in UTF-8) to `\n` (1 byte in UTF-8) in template literals. + // Subtracting the count of `\r\n` occurrences avoids allocating any temporary strings or match arrays for large assets. + let size = Buffer.byteLength(file.text); + let pos = file.text.indexOf('\r\n'); + while (pos !== -1) { + size--; + pos = file.text.indexOf('\r\n', pos + 2); + } serverAssets[file.path] = `{size: ${size}, hash: '${file.hash}', text: () => import('./${jsChunkFilePath}').then(m => m.default)}`; diff --git a/packages/angular/build/src/utils/server-rendering/prerender.ts b/packages/angular/build/src/utils/server-rendering/prerender.ts index b4f81f03a800..37e29f02385b 100644 --- a/packages/angular/build/src/utils/server-rendering/prerender.ts +++ b/packages/angular/build/src/utils/server-rendering/prerender.ts @@ -16,7 +16,11 @@ import { assertIsError } from '../error'; import { toPosixPath } from '../path'; import { addLeadingSlash, addTrailingSlash, joinUrlParts, stripLeadingSlash } from '../url'; import { WorkerPool } from '../worker-pool'; -import { IMPORT_EXEC_ARGV } from './esm-in-memory-loader/utils'; +import { + IMPORT_EXEC_ARGV, + createSharedFile, + createSharedServerFiles, +} from './esm-in-memory-loader/utils'; import { SERVER_APP_MANIFEST_FILENAME } from './manifest'; import { RouteRenderMode, @@ -25,7 +29,7 @@ import { SerializableRouteTreeNode, WritableSerializableRouteTreeNode, } from './models'; -import type { RenderWorkerData } from './render-worker'; +import type { RenderResult, RenderWorkerData } from './render-worker'; import { generateRedirectStaticPage } from './utils'; type PrerenderOptions = NormalizedApplicationBuildOptions['prerenderOptions']; @@ -63,7 +67,7 @@ export async function prerenderPages( errors: string[]; serializableRouteTreeNode: SerializableRouteTreeNode; }> { - const outputFilesForWorker: Record = {}; + const rawOutputFiles: Record = {}; const serverBundlesSourceMaps = new Map(); const warnings: string[] = []; const errors: string[] = []; @@ -77,23 +81,25 @@ export async function prerenderPages( if (extname(path) === '.map') { serverBundlesSourceMaps.set(path.slice(0, -4), text); } else { - outputFilesForWorker[path] = text; + rawOutputFiles[path] = text; } } // Inline sourcemap into JS file. This is needed to make Node.js resolve sourcemaps // when using `--enable-source-maps` when using in memory files. for (const [filePath, map] of serverBundlesSourceMaps) { - const jsContent = outputFilesForWorker[filePath]; + const jsContent = rawOutputFiles[filePath]; if (jsContent) { - outputFilesForWorker[filePath] = + rawOutputFiles[filePath] = jsContent + - `\n//# sourceMappingURL=` + + '\n//# sourceMappingURL=' + `data:application/json;base64,${Buffer.from(map).toString('base64')}`; } } serverBundlesSourceMaps.clear(); + const outputFilesForWorker = createSharedServerFiles(rawOutputFiles); + const assetsReversed: Record = {}; for (const { source, destination } of assets) { // Assets are not stored with baseHref when using i18n, @@ -169,9 +175,13 @@ export async function prerenderPages( // We could re-generate it from the start, but that would require a number of options to be passed down. const manifest = outputFilesForWorker[SERVER_APP_MANIFEST_FILENAME]; if (manifest) { - outputFilesForWorker[SERVER_APP_MANIFEST_FILENAME] = manifest.replace( - 'routes: undefined,', - `routes: ${JSON.stringify(serializableRouteTreeNodeForPrerender, undefined, 2)},`, + const manifestText = new TextDecoder().decode(manifest); + + outputFilesForWorker[SERVER_APP_MANIFEST_FILENAME] = createSharedFile( + manifestText.replace( + 'routes: undefined,', + `routes: ${JSON.stringify(serializableRouteTreeNodeForPrerender, undefined, 2)},`, + ), ); } @@ -204,7 +214,7 @@ async function renderPages( serializableRouteTreeNode: SerializableRouteTreeNode, maxThreads: number, workspaceRoot: string, - outputFilesForWorker: Record, + outputFilesForWorker: Record, assetFilesForWorker: Record, outputMode: OutputMode | undefined, appShellRoute: string | undefined, @@ -214,15 +224,59 @@ async function renderPages( }> { const output: PrerenderOutput = {}; const errors: string[] = []; - const workerExecArgv = [IMPORT_EXEC_ARGV]; + const baseHrefPathnameWithLeadingSlash = new URL(baseHref, 'http://localhost').pathname; + const appShellRouteWithoutBaseHref = appShellRoute + ? addTrailingSlash(appShellRoute).startsWith(baseHrefPathnameWithLeadingSlash) + ? addLeadingSlash(appShellRoute.slice(baseHrefPathnameWithLeadingSlash.length)) + : addLeadingSlash(appShellRoute) + : undefined; + + const routesToRender: { route: string; outPath: string; isAppShell: boolean }[] = []; + + for (const { route, redirectTo } of serializableRouteTreeNode) { + // Remove the base href from the file output path. + const routeWithoutBaseHref = addTrailingSlash(route).startsWith( + baseHrefPathnameWithLeadingSlash, + ) + ? addLeadingSlash(route.slice(baseHrefPathnameWithLeadingSlash.length)) + : route; + + const outPath = stripLeadingSlash(posix.join(routeWithoutBaseHref, 'index.html')); + + if (typeof redirectTo === 'string') { + output[outPath] = { content: generateRedirectStaticPage(redirectTo), appShellRoute: false }; + + continue; + } + + routesToRender.push({ + route, + outPath, + isAppShell: appShellRouteWithoutBaseHref === routeWithoutBaseHref, + }); + } + + if (routesToRender.length === 0) { + return { + errors, + output, + }; + } + + // Batch routes to reduce IPC overhead while ensuring enough batches exist for load balancing across worker threads. + const batchSize = Math.max(1, Math.min(50, Math.ceil(routesToRender.length / (maxThreads * 4)))); + const numBatches = Math.ceil(routesToRender.length / batchSize); + const effectiveMaxThreads = Math.min(numBatches, maxThreads); + + const workerExecArgv = [IMPORT_EXEC_ARGV]; if (sourcemap) { workerExecArgv.push('--enable-source-maps'); } const renderWorker = new WorkerPool({ filename: require.resolve('./render-worker'), - maxThreads: Math.min(serializableRouteTreeNode.length, maxThreads), + maxThreads: effectiveMaxThreads, workerData: { workspaceRoot, outputFiles: outputFilesForWorker, @@ -238,45 +292,46 @@ async function renderPages( }); try { - const renderingPromises: Promise[] = []; - const appShellRouteWithLeadingSlash = appShellRoute && addLeadingSlash(appShellRoute); - const baseHrefPathnameWithLeadingSlash = new URL(baseHref, 'http://localhost').pathname; - - for (const { route, redirectTo } of serializableRouteTreeNode) { - // Remove the base href from the file output path. - const routeWithoutBaseHref = addTrailingSlash(route).startsWith( - baseHrefPathnameWithLeadingSlash, - ) - ? addLeadingSlash(route.slice(baseHrefPathnameWithLeadingSlash.length)) - : route; - - const outPath = stripLeadingSlash(posix.join(routeWithoutBaseHref, 'index.html')); - - if (typeof redirectTo === 'string') { - output[outPath] = { content: generateRedirectStaticPage(redirectTo), appShellRoute: false }; + const routeOutPathMap = new Map(); + for (const item of routesToRender) { + routeOutPathMap.set(item.route, item); + } - continue; - } + const renderingPromises: Promise[] = []; - const render: Promise = renderWorker.run({ url: route }); - const renderResult: Promise = render - .then((content) => { - if (content !== null) { - output[outPath] = { - content, - appShellRoute: appShellRouteWithLeadingSlash === routeWithoutBaseHref, - }; + for (let i = 0; i < routesToRender.length; i += batchSize) { + const batch = routesToRender.slice(i, i + batchSize); + const urls = batch.map((item) => item.route); + const renderBatchPromise: Promise = renderWorker.run(urls); + const batchResultPromise = renderBatchPromise + .then((results) => { + for (const { url, content, error } of results) { + if (error) { + errors.push(`An error occurred while prerendering route '${url}'.\n\n${error}`); + continue; + } + + if (content !== null) { + const routeInfo = routeOutPathMap.get(url); + if (routeInfo) { + output[routeInfo.outPath] = { + content, + appShellRoute: routeInfo.isAppShell, + }; + } + } } }) .catch((err) => { assertIsError(err); - errors.push( - `An error occurred while prerendering route '${route}'.\n\n${err.stack ?? err.message ?? err.code ?? err}`, - ); - void renderWorker.destroy(); + for (const url of urls) { + errors.push( + `An error occurred while prerendering route '${url}'.\n\n${err.stack ?? err.message ?? err.code ?? err}`, + ); + } }); - renderingPromises.push(renderResult); + renderingPromises.push(batchResultPromise); } await Promise.all(renderingPromises); @@ -293,7 +348,7 @@ async function renderPages( async function getAllRoutes( workspaceRoot: string, baseHref: string, - outputFilesForWorker: Record, + outputFilesForWorker: Record, assetFilesForWorker: Record, appShellOptions: AppShellOptions | undefined, prerenderOptions: PrerenderOptions | undefined, diff --git a/packages/angular/build/src/utils/server-rendering/render-worker.ts b/packages/angular/build/src/utils/server-rendering/render-worker.ts index 7ded0550b826..b8f01c440c4b 100644 --- a/packages/angular/build/src/utils/server-rendering/render-worker.ts +++ b/packages/angular/build/src/utils/server-rendering/render-worker.ts @@ -8,6 +8,7 @@ import { workerData } from 'node:worker_threads'; import type { OutputMode } from '../../builders/application/schema'; +import { assertIsError } from '../error'; import type { ESMInMemoryFileLoaderWorkerData } from './esm-in-memory-loader/loader-hooks'; import { patchFetchToLoadInMemoryAssets } from './fetch-patch'; import { DEFAULT_URL, launchServer } from './launch-server'; @@ -20,10 +21,14 @@ export interface RenderWorkerData extends ESMInMemoryFileLoaderWorkerData { hasSsrEntry: boolean; } -export interface RenderOptions { +export interface RenderResultItem { url: string; + content: string | null; + error?: string; } +export type RenderResult = RenderResultItem[]; + /** * This is passed as workerData when setting up the worker via the `piscina` package. */ @@ -35,16 +40,12 @@ const { outputMode, hasSsrEntry } = workerData as { let serverURL = DEFAULT_URL; /** - * Renders each route in routes and writes them to //index.html. + * Renders a single route URL. */ -async function renderPage({ url }: RenderOptions): Promise { - const { ɵgetOrCreateAngularServerApp: getOrCreateAngularServerApp } = - await loadEsmModuleFromMemory('./main.server.mjs'); - - const angularServerApp = getOrCreateAngularServerApp({ - allowStaticRouteRender: true, - }); - +async function renderPage( + url: string, + angularServerApp: { handle: (request: Request) => Promise }, +): Promise { const response = await angularServerApp.handle( new Request(new URL(url, serverURL), { signal: AbortSignal.timeout(30_000) }), ); @@ -58,6 +59,35 @@ async function renderPage({ url }: RenderOptions): Promise { return location ? generateRedirectStaticPage(location) : response.text(); } +/** + * Renders routes in batch or individual URL. + */ +async function renderPages(urls: string[]): Promise { + const { ɵgetOrCreateAngularServerApp: getOrCreateAngularServerApp } = + await loadEsmModuleFromMemory('./main.server.mjs'); + + const angularServerApp = getOrCreateAngularServerApp({ + allowStaticRouteRender: true, + }); + + const results: RenderResult = []; + for (const currentUrl of urls) { + try { + const content = await renderPage(currentUrl, angularServerApp); + results.push({ url: currentUrl, content }); + } catch (err) { + assertIsError(err); + results.push({ + url: currentUrl, + content: null, + error: err.stack ?? err.message ?? err.code ?? `${err}`, + }); + } + } + + return results; +} + async function initialize() { // Load the compiler because `@angular/ssr/node` depends on `@angular/` packages, // which must be processed by the runtime linker, even if they are not used. @@ -69,7 +99,7 @@ async function initialize() { patchFetchToLoadInMemoryAssets(serverURL); - return renderPage; + return renderPages; } export default initialize(); diff --git a/packages/angular/build/src/utils/source-map.ts b/packages/angular/build/src/utils/source-map.ts index 3b91bfb5b5ad..4fa97dd222c1 100644 --- a/packages/angular/build/src/utils/source-map.ts +++ b/packages/angular/build/src/utils/source-map.ts @@ -11,17 +11,95 @@ import { existsSync, readFileSync } from 'node:fs'; import { dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; +export const SOURCEMAP_COMMENT_PREFIX = '//# sourceMappingURL='; +export const SOURCEMAP_COMMENT_BYTES = Buffer.from(SOURCEMAP_COMMENT_PREFIX); + +/** + * Checks for a single trailing `//# sourceMappingURL=` comment on a raw buffer. + * + * @param data The raw byte buffer to inspect. + * @returns An object containing the sliced code buffer and URL snippet if a single trailing comment exists, + * `null` if no sourcemap comment exists in the buffer, or `undefined` if multiple/non-trailing comments exist. + */ +export function findTrailingSourceMapComment( + data: Uint8Array, +): { code: Uint8Array; urlLine: string } | null | undefined { + const dataBuffer = Buffer.isBuffer(data) + ? data + : Buffer.from(data.buffer, data.byteOffset, data.byteLength); + + const firstIndex = dataBuffer.indexOf(SOURCEMAP_COMMENT_BYTES); + if (firstIndex === -1) { + return null; + } + + const lastIndex = dataBuffer.lastIndexOf(SOURCEMAP_COMMENT_BYTES); + // Skip any preceding horizontal whitespace (spaces/tabs) to find the start of the line. + let prevIdx = lastIndex - 1; + while (prevIdx >= 0 && (dataBuffer[prevIdx] === 32 || dataBuffer[prevIdx] === 9)) { + prevIdx--; + } + // Ensure the comment starts at the beginning of a line or the start of the file, + // preventing false positives for occurrences inside inline string literals or code. + const isLineStart = prevIdx < 0 || dataBuffer[prevIdx] === 10 || dataBuffer[prevIdx] === 13; + + if (firstIndex === lastIndex && isLineStart) { + const urlLine = dataBuffer + .subarray(lastIndex + SOURCEMAP_COMMENT_BYTES.length) + .toString('utf-8'); + + return { + code: dataBuffer.subarray(0, prevIdx < 0 ? 0 : prevIdx + 1), + urlLine, + }; + } + + return undefined; +} + /** * Removes `//# sourceMappingURL=` comments safely from the given JavaScript code, * ignoring any occurrences that are inside string literals, template literals, or block comments. * - * It uses a lightweight state-machine parser to accurately handle nested template literals. + * For raw Uint8Array / Buffer inputs, it optimizes performance by inspecting trailing byte sequences + * to slice the buffer directly without full string decoding. * - * @param code The JavaScript source code. + * @param code The JavaScript source code as a string or Uint8Array. * @returns The code with top-level sourcemap comments removed. */ -export function removeSourceMappingURL(code: string): string { +export function removeSourceMappingURL(code: string): string; +export function removeSourceMappingURL(code: Uint8Array): Uint8Array; +export function removeSourceMappingURL(code: string | Uint8Array): string | Uint8Array { + if (typeof code === 'string') { + return removeSourceMappingURLFromString(code); + } + + const trailing = findTrailingSourceMapComment(code); + if (trailing === null) { + return code; + } + + if (trailing && isTrailingSourceMapComment(trailing.urlLine)) { + return trailing.code; + } + + // Fallback to full decode and state-machine stripping for multiple comments or non-trailing comments + const dataBuffer = Buffer.isBuffer(code) + ? code + : Buffer.from(code.buffer, code.byteOffset, code.byteLength); + const text = dataBuffer.toString('utf-8'); + const stripped = removeSourceMappingURLFromString(text); + + return stripped === text ? code : Buffer.from(stripped, 'utf-8'); +} + +function removeSourceMappingURLFromString(code: string): string { + if (!code.includes(SOURCEMAP_COMMENT_PREFIX)) { + return code; + } + const result: string[] = []; + let lastCopiedIndex = 0; let i = 0; const len = code.length; @@ -30,7 +108,6 @@ export function removeSourceMappingURL(code: string): string { let currentState: 'normal' | 'string_double' | 'string_single' | 'template' | 'comment_block' | 'comment_line' = 'normal'; - let isStrippingComment = false; while (i < len) { const char = code[i]; @@ -39,20 +116,19 @@ export function removeSourceMappingURL(code: string): string { if (currentState === 'normal') { if (char === '/' && nextChar === '*') { currentState = 'comment_block'; - result.push('/*'); i += 2; continue; } if (char === '/' && nextChar === '/') { // Detect if the comment is escaped (e.g. inside a regex literal like `/\/\/#/`). let isEscaped = false; - let prevIdx = result.length - 1; - while (prevIdx >= 0 && /\s/.test(result[prevIdx])) { + let prevIdx = i - 1; + while (prevIdx >= 0 && /\s/.test(code[prevIdx])) { prevIdx--; } - if (prevIdx >= 0 && result[prevIdx] === '\\') { + if (prevIdx >= 0 && code[prevIdx] === '\\') { let bsCount = 0; - while (prevIdx >= 0 && result[prevIdx] === '\\') { + while (prevIdx >= 0 && code[prevIdx] === '\\') { bsCount++; prevIdx--; } @@ -61,35 +137,36 @@ export function removeSourceMappingURL(code: string): string { } } - if (!isEscaped && code.startsWith('//# sourceMappingURL=', i)) { - currentState = 'comment_line'; - isStrippingComment = true; - i += 21; // Skip the '//# sourceMappingURL=' prefix + if (!isEscaped && code.startsWith(SOURCEMAP_COMMENT_PREFIX, i)) { + if (i > lastCopiedIndex) { + result.push(code.slice(lastCopiedIndex, i)); + } + // Skip the rest of the comment line up to the newline + i += SOURCEMAP_COMMENT_PREFIX.length; + while (i < len && code[i] !== '\n' && code[i] !== '\r') { + i++; + } + lastCopiedIndex = i; continue; } else { currentState = 'comment_line'; - isStrippingComment = false; - result.push('//'); i += 2; continue; } } if (char === '"') { currentState = 'string_double'; - result.push('"'); i++; continue; } if (char === "'") { currentState = 'string_single'; - result.push("'"); i++; continue; } if (char === '`') { currentState = 'template'; stack.push({ type: 'template', braceDepth: 0 }); - result.push('`'); i++; continue; } @@ -98,7 +175,6 @@ export function removeSourceMappingURL(code: string): string { if (top) { top.braceDepth++; } - result.push('{'); i++; continue; } @@ -110,43 +186,35 @@ export function removeSourceMappingURL(code: string): string { // Exiting a template literal interpolation ${ ... } stack.pop(); currentState = 'template'; - result.push('}'); i++; continue; } } - result.push('}'); i++; continue; } - result.push(char); i++; } else if (currentState === 'string_double') { if (char === '\\') { - result.push(char, nextChar || ''); i += 2; continue; } if (char === '"') { currentState = 'normal'; } - result.push(char); i++; } else if (currentState === 'string_single') { if (char === '\\') { - result.push(char, nextChar || ''); i += 2; continue; } if (char === "'") { currentState = 'normal'; } - result.push(char); i++; } else if (currentState === 'template') { if (char === '\\') { - result.push(char, nextChar || ''); i += 2; continue; } @@ -154,7 +222,6 @@ export function removeSourceMappingURL(code: string): string { // Entering template literal interpolation context currentState = 'normal'; stack.push({ type: 'template', braceDepth: 0 }); - result.push('${'); i += 2; continue; } @@ -162,101 +229,129 @@ export function removeSourceMappingURL(code: string): string { stack.pop(); currentState = 'normal'; } - result.push(char); i++; } else if (currentState === 'comment_block') { if (char === '*' && nextChar === '/') { currentState = 'normal'; - result.push('*/'); i += 2; continue; } - result.push(char); i++; } else if (currentState === 'comment_line') { if (char === '\n' || char === '\r') { currentState = 'normal'; - isStrippingComment = false; - result.push(char); - i++; - continue; - } - if (!isStrippingComment) { - result.push(char); } i++; } } + if (lastCopiedIndex === 0) { + return code; + } + + if (lastCopiedIndex < len) { + result.push(code.slice(lastCopiedIndex)); + } + return result.join(''); } /** - * Finds, resolves, and loads the input sourcemap referenced in the code's trailing - * sourceMappingURL comment, if present. Supports inline base64 data URIs, local absolute - * file URLs, and relative/absolute filesystem paths. + * Extracts the base64 payload from an inline sourcemap data URI line and verifies + * that only trailing whitespace follows the payload. + * + * @returns The base64 payload string if valid and trailing, or `undefined` otherwise. */ -export function loadInputSourceMap(filename: string, code: string): EncodedSourceMap | undefined { - // Locate the last sourceMappingURL comment using lastIndexOf to avoid scanning - // the entire file with a regular expression (significant for large files). - const lastSourceMapIndex = code.lastIndexOf('//# sourceMappingURL='); - if (lastSourceMapIndex === -1) { +function extractTrailingBase64Payload(urlLine: string): string | undefined { + if (!urlLine.startsWith('data:application/json;')) { return undefined; } - const urlLine = code.slice(lastSourceMapIndex + 21); - - // Inline base64-encoded sourcemaps can be extremely large (up to megabytes). - // Parse them without regular expressions to avoid heavy backtracking and allocations. - if (urlLine.startsWith('data:application/json;')) { - const base64StartIndex = urlLine.indexOf('base64,'); - if (base64StartIndex === -1) { - return undefined; - } + const base64StartIndex = urlLine.indexOf('base64,'); + if (base64StartIndex === -1) { + return undefined; + } - const payloadStart = base64StartIndex + 7; - let payloadEnd = urlLine.length; - // Find the first trailing whitespace character that marks the end of the base64 payload. - for (let i = payloadStart; i < urlLine.length; i++) { - const char = urlLine[i]; - if (char === ' ' || char === '\r' || char === '\n' || char === '\t') { - payloadEnd = i; - break; - } + const payloadStart = base64StartIndex + 7; + let payloadEnd = urlLine.length; + // Find the first trailing whitespace character that marks the end of the base64 payload. + for (let i = payloadStart; i < urlLine.length; i++) { + const char = urlLine[i]; + if (char === ' ' || char === '\r' || char === '\n' || char === '\t') { + payloadEnd = i; + break; } + } - // Verify that everything after the base64 payload is trailing whitespace - // to ensure this is a valid trailing sourceMappingURL comment at the end of the file. - for (let i = payloadEnd; i < urlLine.length; i++) { - const char = urlLine[i]; - if (char !== ' ' && char !== '\r' && char !== '\n' && char !== '\t') { - return undefined; - } + // Verify that everything after the base64 payload is trailing whitespace + // to ensure this is a valid trailing sourceMappingURL comment at the end of the file. + for (let i = payloadEnd; i < urlLine.length; i++) { + const char = urlLine[i]; + if (char !== ' ' && char !== '\r' && char !== '\n' && char !== '\t') { + return undefined; } + } - try { - // Extract the base64 payload and decode it directly into binary memory. - const base64Content = urlLine.slice(payloadStart, payloadEnd); + return urlLine.slice(payloadStart, payloadEnd); +} - return JSON.parse(Buffer.from(base64Content, 'base64').toString('utf-8')) as EncodedSourceMap; - } catch { - return undefined; - } +/** + * Extracts the URL from an external sourcemap comment line and verifies + * that only trailing whitespace follows the URL. + * + * @returns The URL string if valid and trailing, or `undefined` otherwise. + */ +function extractTrailingUrl(urlLine: string): string | undefined { + if (urlLine.startsWith('data:')) { + return undefined; } - // Non-inline sourcemap comments (always small, typically < 200 characters). - const urlMatch = /^([^\r\n\s]+)/.exec(urlLine); + const urlMatch = /^([^\r\n\s'"`]+)/.exec(urlLine); if (!urlMatch) { return undefined; } - const url = urlMatch[1]; - const remaining = urlLine.slice(url.length); - // Verify there is only whitespace after the URL to the end of the file. + const remaining = urlLine.slice(urlMatch[1].length); if (!/^\s*$/.test(remaining)) { return undefined; } + return urlMatch[1]; +} + +/** + * Checks whether a `//# sourceMappingURL=` URL line snippet represents a valid trailing comment at the end of the file. + */ +export function isTrailingSourceMapComment(urlLine: string): boolean { + return ( + extractTrailingBase64Payload(urlLine) !== undefined || extractTrailingUrl(urlLine) !== undefined + ); +} + +/** + * Resolves and loads the input sourcemap referenced in a `//# sourceMappingURL=` URL line snippet. + * Supports inline base64 data URIs, local absolute file URLs, and relative/absolute filesystem paths. + */ +export function loadInputSourceMapFromUrl( + filename: string, + urlLine: string, +): EncodedSourceMap | undefined { + // Inline base64-encoded sourcemaps can be extremely large (up to megabytes). + // Parse them without regular expressions to avoid heavy backtracking and allocations. + const base64Payload = extractTrailingBase64Payload(urlLine); + if (base64Payload !== undefined) { + try { + return JSON.parse(Buffer.from(base64Payload, 'base64').toString('utf-8')) as EncodedSourceMap; + } catch { + return undefined; + } + } + + const url = extractTrailingUrl(urlLine); + if (!url) { + return undefined; + } + if (url.startsWith('file://')) { // Local absolute file URL scheme. try { @@ -277,3 +372,34 @@ export function loadInputSourceMap(filename: string, code: string): EncodedSourc return undefined; } + +/** + * Finds, resolves, and loads the input sourcemap referenced in the code's trailing + * sourceMappingURL comment, if present. Supports inline base64 data URIs, local absolute + * file URLs, and relative/absolute filesystem paths. + */ +export function loadInputSourceMap(filename: string, code: string): EncodedSourceMap | undefined { + // Locate the last sourceMappingURL comment using lastIndexOf to avoid scanning + // the entire file with a regular expression (significant for large files). + const lastSourceMapIndex = code.lastIndexOf(SOURCEMAP_COMMENT_PREFIX); + if (lastSourceMapIndex === -1) { + return undefined; + } + + if (lastSourceMapIndex > 0) { + // Skip any preceding horizontal whitespace (spaces/tabs) to find the start of the line. + let prevIdx = lastSourceMapIndex - 1; + while (prevIdx >= 0 && (code[prevIdx] === ' ' || code[prevIdx] === '\t')) { + prevIdx--; + } + // Ensure the comment starts at the beginning of a line, preventing false positives within code or strings. + if (prevIdx >= 0 && code[prevIdx] !== '\n' && code[prevIdx] !== '\r') { + return undefined; + } + } + + return loadInputSourceMapFromUrl( + filename, + code.slice(lastSourceMapIndex + SOURCEMAP_COMMENT_PREFIX.length), + ); +} diff --git a/packages/angular/build/src/utils/source-map_spec.ts b/packages/angular/build/src/utils/source-map_spec.ts index 7b424b019403..a5680d61fa4f 100644 --- a/packages/angular/build/src/utils/source-map_spec.ts +++ b/packages/angular/build/src/utils/source-map_spec.ts @@ -6,7 +6,12 @@ * found in the LICENSE file at https://angular.dev/license */ -import { removeSourceMappingURL } from './source-map'; +import { + isTrailingSourceMapComment, + loadInputSourceMap, + loadInputSourceMapFromUrl, + removeSourceMappingURL, +} from './source-map'; describe('removeSourceMappingURL', () => { it('should remove top-level sourcemap comments', () => { @@ -87,4 +92,134 @@ describe('removeSourceMappingURL', () => { const code = '// # sourceMappingURL=main.js.map\n//# sourceMappingURL=main.js.map'; expect(removeSourceMappingURL(code)).toBe('// # sourceMappingURL=main.js.map\n'); }); + + it('should return the exact input string when no sourcemap comment is present', () => { + const code = 'const x = 1;\nconsole.log(x);'; + expect(removeSourceMappingURL(code)).toBe(code); + }); + + it('should handle sourcemap comments with CRLF newlines', () => { + const code = 'console.log("hello");\r\n//# sourceMappingURL=main.js.map\r\nconst next = 2;'; + expect(removeSourceMappingURL(code)).toBe('console.log("hello");\r\n\r\nconst next = 2;'); + }); + + describe('with Uint8Array / Buffer inputs', () => { + it('should strip trailing sourcemap comment from Uint8Array buffer', () => { + const buffer = Buffer.from( + 'console.log("hello");\n//# sourceMappingURL=main.js.map', + 'utf-8', + ); + const result = removeSourceMappingURL(buffer); + + expect(Buffer.from(result).toString('utf-8')).toBe('console.log("hello");\n'); + }); + + it('should return exact input buffer when no sourcemap comment is present', () => { + const buffer = Buffer.from('console.log("hello");\nconst x = 1;', 'utf-8'); + const result = removeSourceMappingURL(buffer); + + expect(result).toBe(buffer); + }); + + it('should handle multiple sourcemap comments in a buffer via fallback', () => { + const buffer = Buffer.from( + '//# sourceMappingURL=first.js.map\nconsole.log("mid");\n//# sourceMappingURL=second.js.map', + 'utf-8', + ); + const result = removeSourceMappingURL(buffer); + + expect(Buffer.from(result).toString('utf-8')).toBe('\nconsole.log("mid");\n'); + }); + + it('should not strip sourcemap comments inside template strings in a buffer', () => { + const buffer = Buffer.from('const str = `\n//# sourceMappingURL=inline.js.map\n`;', 'utf-8'); + const result = removeSourceMappingURL(buffer); + + expect(Buffer.from(result).toString('utf-8')).toBe(buffer.toString('utf-8')); + }); + }); +}); + +describe('loadInputSourceMapFromUrl', () => { + it('should decode inline base64 sourcemaps', () => { + const map = { version: 3, sources: ['foo.ts'], mappings: 'AAAA;' }; + const base64 = Buffer.from(JSON.stringify(map)).toString('base64'); + const urlLine = `data:application/json;charset=utf-8;base64,${base64}\n`; + + expect(loadInputSourceMapFromUrl('/src/foo.js', urlLine)).toEqual(map as never); + }); + + it('should return undefined for invalid base64 payloads', () => { + expect( + loadInputSourceMapFromUrl('/src/foo.js', 'data:application/json;base64,invalid!!!'), + ).toBeUndefined(); + }); + + it('should return undefined when no base64 marker is found in data URI', () => { + expect( + loadInputSourceMapFromUrl('/src/foo.js', 'data:application/json;utf8,{}'), + ).toBeUndefined(); + }); +}); + +describe('loadInputSourceMap', () => { + it('should extract and decode sourcemap from source string', () => { + const map = { version: 3, sources: ['foo.ts'], mappings: 'AAAA;' }; + const base64 = Buffer.from(JSON.stringify(map)).toString('base64'); + const code = `console.log("hello");\n//# sourceMappingURL=data:application/json;base64,${base64}\n`; + + expect(loadInputSourceMap('/src/foo.js', code)).toEqual(map as never); + }); + + it('should return undefined when no sourceMappingURL comment is present', () => { + expect(loadInputSourceMap('/src/foo.js', 'console.log("hello");')).toBeUndefined(); + }); + + it('should return undefined for comments inside template strings with code after them', () => { + const map = { version: 3, sources: ['foo.ts'], mappings: 'AAAA;' }; + const base64 = Buffer.from(JSON.stringify(map)).toString('base64'); + const code = `const str = \`\n//# sourceMappingURL=data:application/json;base64,${base64}\n\`;\nconsole.log(str);`; + + expect(loadInputSourceMap('/src/foo.js', code)).toBeUndefined(); + }); + + it('should return undefined for comments inside template strings ending with backticks', () => { + const map = { version: 3, sources: ['foo.ts'], mappings: 'AAAA;' }; + const base64 = Buffer.from(JSON.stringify(map)).toString('base64'); + const code = `const str = \`\n//# sourceMappingURL=data:application/json;base64,${base64}\n\`;`; + + expect(loadInputSourceMap('/src/foo.js', code)).toBeUndefined(); + }); + + it('should return undefined for single-line template literals', () => { + const map = { version: 3, sources: ['foo.ts'], mappings: 'AAAA;' }; + const base64 = Buffer.from(JSON.stringify(map)).toString('base64'); + const code = `const str = \`//# sourceMappingURL=data:application/json;base64,${base64}\`;`; + + expect(loadInputSourceMap('/src/foo.js', code)).toBeUndefined(); + }); +}); + +describe('isTrailingSourceMapComment', () => { + it('should return true for valid inline data URIs at end of file', () => { + const map = { version: 3, sources: ['foo.ts'], mappings: 'AAAA;' }; + const base64 = Buffer.from(JSON.stringify(map)).toString('base64'); + const urlLine = `data:application/json;charset=utf-8;base64,${base64}\n`; + + expect(isTrailingSourceMapComment(urlLine)).toBe(true); + }); + + it('should return true for external sourcemap URLs at end of file', () => { + expect(isTrailingSourceMapComment('main.js.map\n')).toBe(true); + expect(isTrailingSourceMapComment('main.js.map')).toBe(true); + }); + + it('should return false when followed by non-whitespace characters', () => { + expect(isTrailingSourceMapComment('main.js.map\n`;\nconsole.log("hi");')).toBe(false); + expect(isTrailingSourceMapComment('main.js.map`;')).toBe(false); + }); + + it('should return false for invalid data URI format', () => { + expect(isTrailingSourceMapComment('data:application/json;utf8,{}')).toBe(false); + }); }); diff --git a/packages/angular/cli/lib/init.ts b/packages/angular/cli/lib/init.ts index ab6e9cf48589..97d410890955 100644 --- a/packages/angular/cli/lib/init.ts +++ b/packages/angular/cli/lib/init.ts @@ -10,8 +10,6 @@ import { readFile } from 'node:fs/promises'; import { createRequire } from 'node:module'; import * as path from 'node:path'; import { SemVer, major } from 'semver'; -import { colors } from '../src/utilities/color'; -import { isWarningEnabled } from '../src/utilities/config'; import { disableVersionCheck } from '../src/utilities/environment-options'; import { VERSION } from '../src/utilities/version'; @@ -39,6 +37,9 @@ let forceExit = false; // Ignore failure to change directory } } + + // Ensure Windows CreateProcess does not search the current directory for bare executable names. + process.env['NoDefaultCurrentDirectoryInExePath'] = '1'; } /** @@ -48,6 +49,7 @@ let forceExit = false; * See: https://github.com/browserslist/browserslist/blob/819c4337456996d19db6ba953014579329e9c6e1/node.js#L324 */ process.env.BROWSERSLIST_IGNORE_OLD_DATA = '1'; + const rawCommandName = process.argv[2]; /** @@ -121,15 +123,23 @@ let forceExit = false; cli.VERSION.major - globalVersion.major <= 1 ) { cli = await import('./cli'); - } else if (await isWarningEnabled('versionMismatch')) { - // Otherwise, use local version and warn if global is newer than local - const warning = - `Your global Angular CLI version (${globalVersion}) is greater than your local ` + - `version (${localVersion}). The local Angular CLI version is used.\n\n` + - 'To disable this warning use "ng config -g cli.warnings.versionMismatch false".'; - - // eslint-disable-next-line no-console - console.error(colors.yellow(warning)); + } else { + try { + const { isWarningEnabled } = await import('../src/utilities/config'); + if (await isWarningEnabled('versionMismatch')) { + // Otherwise, use local version and warn if global is newer than local + const warning = + `Your global Angular CLI version (${globalVersion}) is greater than your local ` + + `version (${localVersion}). The local Angular CLI version is used.\n\n` + + 'To disable this warning use "ng config -g cli.warnings.versionMismatch false".'; + + const { colors } = await import('../src/utilities/color'); + // eslint-disable-next-line no-console + console.error(colors.yellow(warning)); + } + } catch { + // Ignore errors during warning check to avoid falling back to global CLI + } } } } catch { diff --git a/packages/angular/cli/package.json b/packages/angular/cli/package.json index 4840834b4bed..8af94acdb2f7 100644 --- a/packages/angular/cli/package.json +++ b/packages/angular/cli/package.json @@ -15,7 +15,7 @@ "@angular-devkit/architect": "workspace:0.0.0-EXPERIMENTAL-PLACEHOLDER", "@angular-devkit/core": "workspace:0.0.0-PLACEHOLDER", "@angular-devkit/schematics": "workspace:0.0.0-PLACEHOLDER", - "@inquirer/prompts": "8.5.2", + "@inquirer/prompts": "8.6.0", "@listr2/prompt-adapter-inquirer": "4.2.5", "@modelcontextprotocol/server": "2.0.0", "@schematics/angular": "workspace:0.0.0-PLACEHOLDER", diff --git a/packages/angular/cli/src/commands/mcp/tools/best-practices.ts b/packages/angular/cli/src/commands/mcp/tools/best-practices.ts index dca61eb700b3..0b40c5c0c207 100644 --- a/packages/angular/cli/src/commands/mcp/tools/best-practices.ts +++ b/packages/angular/cli/src/commands/mcp/tools/best-practices.ts @@ -20,6 +20,7 @@ import { createRequire } from 'node:module'; import { dirname, isAbsolute, join, relative, resolve } from 'node:path'; import { z } from 'zod'; import { VERSION } from '../../../utilities/version'; +import { isAllowedWorkspacePath } from '../workspace-utils'; import { type McpToolContext, declareTool } from './tool-registry'; const bestPracticesInputSchema = z.object({ @@ -86,13 +87,36 @@ async function getBundledBestPractices(): Promise { * * @param workspacePath The absolute path to the user's `angular.json` file. * @param logger The MCP tool context logger for reporting warnings. + * @param server The MCP server context, used to enforce the client's declared roots. * @returns A promise that resolves to an object containing the guide's content and source, * or `undefined` if the guide could not be resolved. */ async function getVersionSpecificBestPractices( workspacePath: string, logger: McpToolContext['logger'], + server: McpToolContext['server'], ): Promise<{ content: string; source: string } | undefined> { + if (server) { + let isAllowed: boolean; + try { + isAllowed = await isAllowedWorkspacePath(server, workspacePath); + } catch (e) { + logger.warn( + `Failed to verify workspace path '${workspacePath}': ` + + `${e instanceof Error ? e.message : e}. Falling back to the bundled guide.`, + ); + + return undefined; + } + + if (!isAllowed) { + throw new Error( + `Workspace path is outside the allowed MCP roots: ${workspacePath}. ` + + "You can use 'list_projects' to find available workspaces.", + ); + } + } + // 1. Resolve the path to package.json let pkgJsonPath: string; try { @@ -175,7 +199,7 @@ async function getVersionSpecificBestPractices( * @param context The MCP tool context, containing the logger. * @returns An async function that serves as the tool's executor. */ -function createBestPracticesHandler({ logger }: McpToolContext) { +function createBestPracticesHandler({ logger, server }: McpToolContext) { let bundledBestPractices: Promise; return async (input: BestPracticesInput) => { @@ -184,7 +208,11 @@ function createBestPracticesHandler({ logger }: McpToolContext) { // First, try to get the version-specific guide. if (input.workspacePath) { - const versionSpecific = await getVersionSpecificBestPractices(input.workspacePath, logger); + const versionSpecific = await getVersionSpecificBestPractices( + input.workspacePath, + logger, + server, + ); if (versionSpecific) { content = versionSpecific.content; source = versionSpecific.source; diff --git a/packages/angular/cli/src/commands/mcp/tools/run-target/build-target-strategy.ts b/packages/angular/cli/src/commands/mcp/tools/run-target/build-target-strategy.ts index 1fbd8f83e47a..2e280927f466 100644 --- a/packages/angular/cli/src/commands/mcp/tools/run-target/build-target-strategy.ts +++ b/packages/angular/cli/src/commands/mcp/tools/run-target/build-target-strategy.ts @@ -29,7 +29,7 @@ export class BuildTargetStrategy implements TargetStrategy { ): Promise { const args = ['build', input.projectName]; if (input.configuration) { - args.push('-c', input.configuration); + args.push(`--configuration=${input.configuration}`); } args.push(...serializeOptions(input.options)); diff --git a/packages/angular/cli/src/commands/mcp/tools/run-target/generic-target-strategy.ts b/packages/angular/cli/src/commands/mcp/tools/run-target/generic-target-strategy.ts index e2cbc816c330..365253131248 100644 --- a/packages/angular/cli/src/commands/mcp/tools/run-target/generic-target-strategy.ts +++ b/packages/angular/cli/src/commands/mcp/tools/run-target/generic-target-strategy.ts @@ -46,7 +46,7 @@ export class GenericTargetStrategy implements TargetStrategy { } if (input.configuration) { - args.push('-c', input.configuration); + args.push(`--configuration=${input.configuration}`); } let options = input.options; diff --git a/packages/angular/cli/src/commands/mcp/tools/run-target/run-target_spec.ts b/packages/angular/cli/src/commands/mcp/tools/run-target/run-target_spec.ts index 67b1fffef27e..91ccbac4584a 100644 --- a/packages/angular/cli/src/commands/mcp/tools/run-target/run-target_spec.ts +++ b/packages/angular/cli/src/commands/mcp/tools/run-target/run-target_spec.ts @@ -42,7 +42,7 @@ describe('Run Target Tool', () => { mockContext.workspace.extensions['defaultProject'] = 'my-app'; await runTarget({ target: 'build', configuration: 'production' }, mockContext); expect(mockHost.executeNgCommand).toHaveBeenCalledWith( - ['build', 'my-app', '-c', 'production'], + ['build', 'my-app', '--configuration=production'], { cwd: '/test', }, @@ -53,7 +53,7 @@ describe('Run Target Tool', () => { mockContext.workspace.extensions['defaultProject'] = 'my-app'; await runTarget({ target: 'storybook', configuration: 'docs' }, mockContext); expect(mockHost.executeNgCommand).toHaveBeenCalledWith( - ['run', 'my-app:storybook', '-c', 'docs'], + ['run', 'my-app:storybook', '--configuration=docs'], { cwd: '/test' }, ); }); diff --git a/packages/angular/cli/src/commands/mcp/tools/run-target/unit-test-strategy.ts b/packages/angular/cli/src/commands/mcp/tools/run-target/unit-test-strategy.ts index 77ca5797e71c..1023cdd7956c 100644 --- a/packages/angular/cli/src/commands/mcp/tools/run-target/unit-test-strategy.ts +++ b/packages/angular/cli/src/commands/mcp/tools/run-target/unit-test-strategy.ts @@ -28,7 +28,7 @@ export class UnitTestTargetStrategy implements TargetStrategy { ): Promise { const args = ['test', input.projectName]; if (input.configuration) { - args.push('-c', input.configuration); + args.push(`--configuration=${input.configuration}`); } const builder = input.targetDefinition?.builder; diff --git a/packages/angular/cli/src/commands/mcp/tools/run-target/unit-test-strategy_spec.ts b/packages/angular/cli/src/commands/mcp/tools/run-target/unit-test-strategy_spec.ts index f1467f0002c9..4e63eed148a0 100644 --- a/packages/angular/cli/src/commands/mcp/tools/run-target/unit-test-strategy_spec.ts +++ b/packages/angular/cli/src/commands/mcp/tools/run-target/unit-test-strategy_spec.ts @@ -65,7 +65,15 @@ describe('UnitTestTargetStrategy', () => { ); expect(mockHost.executeNgCommand).toHaveBeenCalledWith( - ['test', 'my-app', '-c', 'ci', '--browsers', 'ChromeHeadless', '--watch', 'false'], + [ + 'test', + 'my-app', + '--configuration=ci', + '--browsers', + 'ChromeHeadless', + '--watch', + 'false', + ], { cwd: '/test' }, ); }); diff --git a/packages/angular/cli/src/commands/mcp/workspace-utils.ts b/packages/angular/cli/src/commands/mcp/workspace-utils.ts index 6cc245ff1dbc..94c4c55655c0 100644 --- a/packages/angular/cli/src/commands/mcp/workspace-utils.ts +++ b/packages/angular/cli/src/commands/mcp/workspace-utils.ts @@ -110,7 +110,7 @@ async function getAllowedWorkspaceRoots(server: McpToolContext['server']): Promi .filter((root): root is string => root !== null); } -async function isAllowedWorkspacePath( +export async function isAllowedWorkspacePath( server: McpToolContext['server'], workspacePath: string, ): Promise { diff --git a/packages/angular/cli/src/commands/update/utilities/migration.ts b/packages/angular/cli/src/commands/update/utilities/migration.ts index 21331364bc2f..83a1492eaa10 100644 --- a/packages/angular/cli/src/commands/update/utilities/migration.ts +++ b/packages/angular/cli/src/commands/update/utilities/migration.ts @@ -13,10 +13,11 @@ import { FileSystemSchematicDescription, NodeWorkflow, } from '@angular-devkit/schematics/tools'; +import { figures } from 'listr2'; import { SpawnSyncReturns } from 'node:child_process'; import * as semver from 'semver'; import { subscribeToWorkflow } from '../../../command-builder/utilities/schematic-workflow'; -import { colors, figures } from '../../../utilities/color'; +import { colors } from '../../../utilities/color'; import { assertIsError } from '../../../utilities/error'; import { writeErrorToLogFile } from '../../../utilities/log-file'; import { formatFiles } from '../../../utilities/prettier'; diff --git a/packages/angular/cli/src/utilities/color.ts b/packages/angular/cli/src/utilities/color.ts index 3915d99ce248..13333dbc4b57 100644 --- a/packages/angular/cli/src/utilities/color.ts +++ b/packages/angular/cli/src/utilities/color.ts @@ -7,8 +7,23 @@ */ import { WriteStream } from 'node:tty'; +import { styleText } from 'node:util'; -export { color as colors, figures } from 'listr2'; +export const colors = Object.freeze({ + black: (text: string) => styleText('black', text), + blue: (text: string) => styleText('blue', text), + bold: (text: string) => styleText('bold', text), + cyan: (text: string) => styleText('cyan', text), + dim: (text: string) => styleText('dim', text), + gray: (text: string) => styleText('gray', text), + green: (text: string) => styleText('green', text), + italic: (text: string) => styleText('italic', text), + magenta: (text: string) => styleText('magenta', text), + red: (text: string) => styleText('red', text), + underline: (text: string) => styleText('underline', text), + white: (text: string) => styleText('white', text), + yellow: (text: string) => styleText('yellow', text), +}); export function supportColor(stream: NodeJS.WritableStream = process.stdout): boolean { if (stream instanceof WriteStream) { diff --git a/packages/angular/ssr/node/src/request.ts b/packages/angular/ssr/node/src/request.ts index 4bd754b268cc..4f604b6a9d0e 100644 --- a/packages/angular/ssr/node/src/request.ts +++ b/packages/angular/ssr/node/src/request.ts @@ -55,9 +55,18 @@ export function createWebRequestFromNodeRequest( const { headers, method = 'GET' } = nodeRequest; const withBody = method !== 'GET' && method !== 'HEAD'; const referrer = headers.referer && URL.canParse(headers.referer) ? headers.referer : undefined; + const controller = new AbortController(); + if (nodeRequest.aborted) { + controller.abort(); + } else { + const onAbort = () => controller.abort(); + nodeRequest.once('aborted', onAbort); + nodeRequest.once('close', () => nodeRequest.off('aborted', onAbort)); + } return new Request(createRequestUrl(nodeRequest, trustProxyHeadersNormalized), { method, + signal: controller.signal, headers: createRequestHeaders(headers), body: withBody ? nodeRequest : undefined, duplex: withBody ? 'half' : undefined, diff --git a/packages/angular/ssr/node/src/response.ts b/packages/angular/ssr/node/src/response.ts index 90ed5bb31a67..d3b0cf403090 100644 --- a/packages/angular/ssr/node/src/response.ts +++ b/packages/angular/ssr/node/src/response.ts @@ -17,13 +17,11 @@ import type { Http2ServerResponse } from 'node:http2'; */ function isResponseDestroyedOrClosed(destination: ServerResponse | Http2ServerResponse): boolean { return ( - Boolean(destination.destroyed) || - Boolean(destination.closed) || - Boolean(destination.writableEnded) || + destination.destroyed || + destination.closed || + destination.writableEnded || ('stream' in destination && - (!destination.stream || - Boolean(destination.stream.destroyed) || - Boolean(destination.stream.closed))) + (!destination.stream || destination.stream.destroyed || destination.stream.closed)) ); } diff --git a/packages/angular/ssr/node/test/request_http1_spec.ts b/packages/angular/ssr/node/test/request_http1_spec.ts index 87f25f918ef7..9f11fe390ed4 100644 --- a/packages/angular/ssr/node/test/request_http1_spec.ts +++ b/packages/angular/ssr/node/test/request_http1_spec.ts @@ -189,4 +189,42 @@ describe('createWebRequestFromNodeRequest (HTTP/1.1)', () => { expect(await webRequest.text()).toBe(''); }); }); + + describe('abort handling', () => { + it('should abort the web request signal when the node request is aborted', async () => { + const nodeRequest = await extractNodeRequest(() => { + request({ + hostname: 'localhost', + port, + path: '/abort', + method: 'GET', + }).end(); + }); + + const webRequest = createWebRequestFromNodeRequest(nodeRequest); + expect(webRequest.signal.aborted).toBeFalse(); + + nodeRequest.emit('aborted'); + + expect(webRequest.signal.aborted).toBeTrue(); + }); + + it('should create an aborted web request signal when the node request is already aborted', async () => { + const nodeRequest = await extractNodeRequest(() => { + request({ + hostname: 'localhost', + port, + path: '/already-aborted', + method: 'GET', + }).end(); + }); + + Object.defineProperty(nodeRequest, 'aborted', { get: () => true, configurable: true }); + + const webRequest = createWebRequestFromNodeRequest(nodeRequest); + expect(webRequest.signal.aborted).toBeTrue(); + + delete (nodeRequest as { aborted?: boolean }).aborted; + }); + }); }); diff --git a/packages/angular/ssr/node/test/request_http2_spec.ts b/packages/angular/ssr/node/test/request_http2_spec.ts index 7079a385daaf..e652c59f6461 100644 --- a/packages/angular/ssr/node/test/request_http2_spec.ts +++ b/packages/angular/ssr/node/test/request_http2_spec.ts @@ -188,4 +188,42 @@ describe('createWebRequestFromNodeRequest (HTTP/2)', () => { expect(await webRequest.text()).toBe(''); }); }); + + describe('abort handling', () => { + it('should abort the web request signal when the node request is aborted', async () => { + const nodeRequest = await extractNodeRequest(() => { + client + .request({ + ':path': '/abort', + ':method': 'GET', + }) + .end(); + }); + + const webRequest = createWebRequestFromNodeRequest(nodeRequest); + expect(webRequest.signal.aborted).toBeFalse(); + + nodeRequest.emit('aborted'); + + expect(webRequest.signal.aborted).toBeTrue(); + }); + + it('should create an aborted web request signal when the node request is already aborted', async () => { + const nodeRequest = await extractNodeRequest(() => { + client + .request({ + ':path': '/already-aborted', + ':method': 'GET', + }) + .end(); + }); + + Object.defineProperty(nodeRequest, 'aborted', { get: () => true, configurable: true }); + + const webRequest = createWebRequestFromNodeRequest(nodeRequest); + expect(webRequest.signal.aborted).toBeTrue(); + + delete (nodeRequest as { aborted?: boolean }).aborted; + }); + }); }); diff --git a/packages/angular/ssr/package.json b/packages/angular/ssr/package.json index d1ad2c16d2d7..96800a3e72e4 100644 --- a/packages/angular/ssr/package.json +++ b/packages/angular/ssr/package.json @@ -37,12 +37,12 @@ }, "devDependencies": { "@angular-devkit/schematics": "workspace:*", - "@angular/common": "22.2.0-next.0", - "@angular/compiler": "22.2.0-next.0", - "@angular/core": "22.2.0-next.0", - "@angular/platform-browser": "22.2.0-next.0", - "@angular/platform-server": "22.2.0-next.0", - "@angular/router": "22.2.0-next.0", + "@angular/common": "22.2.0-next.3", + "@angular/compiler": "22.2.0-next.3", + "@angular/core": "22.2.0-next.3", + "@angular/platform-browser": "22.2.0-next.3", + "@angular/platform-server": "22.2.0-next.3", + "@angular/router": "22.2.0-next.3", "@schematics/angular": "workspace:*", "beasties": "0.4.3" }, diff --git a/packages/angular/ssr/src/app.ts b/packages/angular/ssr/src/app.ts index a9ca7dd05fe3..3e0f33be8ba7 100644 --- a/packages/angular/ssr/src/app.ts +++ b/packages/angular/ssr/src/app.ts @@ -366,10 +366,18 @@ export class AngularServerApp { // Use a stream to send the response before finishing rendering and inling critical CSS, improving performance via header flushing. const stream = new ReadableStream({ start: async (controller) => { - const renderedHtml = await result.content(); - const finalHtml = await this.inlineCriticalCssWithCache(renderedHtml, url); - controller.enqueue(finalHtml); - controller.close(); + try { + const renderedHtml = await result.content(); + const finalHtml = await this.inlineCriticalCssWithCache(renderedHtml, url); + controller.enqueue(finalHtml); + controller.close(); + } catch (error) { + result.destroy(); + controller.error(error); + } + }, + cancel: () => { + result.destroy(); }, }); diff --git a/packages/angular/ssr/src/utils/ng.ts b/packages/angular/ssr/src/utils/ng.ts index 55d054bd2387..4ae3a7f4ed0f 100644 --- a/packages/angular/ssr/src/utils/ng.ts +++ b/packages/angular/ssr/src/utils/ng.ts @@ -34,8 +34,7 @@ import { addTrailingSlash, joinUrlParts, stripIndexHtmlFromURL, stripTrailingSla * - A function that returns a `Promise`, which resolves with the root application reference. */ export type AngularBootstrap = - | Type - | ((context: BootstrapContext) => Promise); + Type | ((context: BootstrapContext) => Promise); /** * Renders an Angular application or module to an HTML string. @@ -60,7 +59,12 @@ export async function renderAngular( serverContext: string, ): Promise< | { hasNavigationError: true } - | { hasNavigationError: boolean; redirectTo?: string; content: () => Promise } + | { + hasNavigationError: boolean; + redirectTo?: string; + content: () => Promise; + destroy: () => void; + } > { // A request to `http://www.example.com/page/index.html` will render the Angular route corresponding to `http://www.example.com/page`. const urlToRender = stripIndexHtmlFromURL(url); @@ -134,6 +138,7 @@ export async function renderAngular( } return { + destroy: () => void asyncDestroyPlatform(platformRef), hasNavigationError, redirectTo, content: () => @@ -177,6 +182,10 @@ export function isNgModule(value: AngularBootstrap): value is Type { * @param platformRef - The platform reference to be destroyed. */ function asyncDestroyPlatform(platformRef: PlatformRef): Promise { + if (platformRef.destroyed) { + return Promise.resolve(); + } + return new Promise((resolve) => { setTimeout(() => { if (!platformRef.destroyed) { diff --git a/packages/angular/ssr/test/app_spec.ts b/packages/angular/ssr/test/app_spec.ts index de4b1bcb988e..1e3d40d3ede8 100644 --- a/packages/angular/ssr/test/app_spec.ts +++ b/packages/angular/ssr/test/app_spec.ts @@ -14,6 +14,7 @@ import '@angular/compiler'; import { APP_BASE_HREF } from '@angular/common'; import { Component, PlatformRef, REQUEST, RESPONSE_INIT, inject } from '@angular/core'; import { ActivatedRoute, CanActivateFn, Router } from '@angular/router'; +import { setTimeout } from 'node:timers/promises'; import { AngularServerApp } from '../src/app'; import { RenderMode } from '../src/routes/route-config'; import { setAngularAppTestingManifest } from './testing-utils'; @@ -365,6 +366,16 @@ describe('AngularServerApp', () => { expect(await response?.text()).toContain('Home works'); }); + it('should destroy the platform when the response stream is cancelled', async () => { + const destroySpy = spyOn(PlatformRef.prototype, 'destroy').and.callThrough(); + const response = await app.handle(new Request('http://localhost/home')); + expect(response?.body).toBeInstanceOf(ReadableStream); + await response?.body?.cancel(); + // Wait for the macrotask queue to clear since destroy is called asynchronously + await setTimeout(0); + expect(destroySpy).toHaveBeenCalled(); + }); + describe('APP_BASE_HREF / X-Forwarded-Prefix', () => { const headers = new Headers({ 'X-Forwarded-Prefix': '/base/' }); diff --git a/packages/angular_devkit/build_angular/BUILD.bazel b/packages/angular_devkit/build_angular/BUILD.bazel index 308ee73e52f3..0a40b409a54c 100644 --- a/packages/angular_devkit/build_angular/BUILD.bazel +++ b/packages/angular_devkit/build_angular/BUILD.bazel @@ -175,7 +175,6 @@ ts_project( "//:node_modules/@types/node", "//:node_modules/@types/picomatch", "//:node_modules/@types/semver", - "//:node_modules/@types/watchpack", "//:node_modules/esbuild", "//:node_modules/esbuild-wasm", "//:node_modules/karma", diff --git a/packages/angular_devkit/build_angular/package.json b/packages/angular_devkit/build_angular/package.json index bd47aeba1515..4b9531956a86 100644 --- a/packages/angular_devkit/build_angular/package.json +++ b/packages/angular_devkit/build_angular/package.json @@ -28,30 +28,30 @@ "browserslist": "^4.26.0", "copy-webpack-plugin": "14.0.0", "css-loader": "7.1.4", - "esbuild-wasm": "0.28.1", + "esbuild-wasm": "0.28.2", "http-proxy-middleware": "4.2.0", "istanbul-lib-instrument": "6.0.3", "jsonc-parser": "3.3.1", "karma-source-map-support": "1.4.0", - "less": "4.8.1", + "less": "4.9.0", "less-loader": "13.0.0", "license-webpack-plugin": "4.0.2", "loader-utils": "3.3.1", "mini-css-extract-plugin": "2.10.2", - "open": "11.0.0", + "open": "11.0.1", "ora": "9.4.1", "picomatch": "4.0.5", - "piscina": "5.3.0", - "postcss": "8.5.25", + "piscina": "5.3.1", + "postcss": "8.5.26", "postcss-loader": "8.2.1", "resolve-url-loader": "5.0.0", "rxjs": "7.8.2", - "sass": "1.102.0", + "sass": "1.103.1", "sass-loader": "17.0.0", "semver": "7.8.5", "source-map-loader": "5.0.0", "source-map-support": "0.5.21", - "terser": "5.49.0", + "terser": "5.50.0", "tinyglobby": "0.2.17", "tslib": "2.8.1", "webpack": "5.109.2", @@ -61,13 +61,13 @@ "webpack-subresource-integrity": "5.1.0" }, "optionalDependencies": { - "esbuild": "0.28.1" + "esbuild": "0.28.2" }, "devDependencies": { "@angular/ssr": "workspace:*", "browser-sync": "3.0.4", - "ng-packagr": "22.2.0-next.2", - "undici": "8.9.0" + "ng-packagr": "22.2.0-next.3", + "undici": "8.10.0" }, "peerDependencies": { "@angular/compiler-cli": "0.0.0-ANGULAR-FW-PEER-DEP", diff --git a/packages/angular_devkit/schematics/package.json b/packages/angular_devkit/schematics/package.json index 40e641caa240..b42434d7994f 100644 --- a/packages/angular_devkit/schematics/package.json +++ b/packages/angular_devkit/schematics/package.json @@ -15,7 +15,7 @@ "dependencies": { "@angular-devkit/core": "workspace:0.0.0-PLACEHOLDER", "jsonc-parser": "3.3.1", - "magic-string": "1.1.0", + "magic-string": "1.2.2", "ora": "9.4.1", "rxjs": "7.8.2" } diff --git a/packages/angular_devkit/schematics_cli/package.json b/packages/angular_devkit/schematics_cli/package.json index 97ab57c4fce7..93c7f0b014e3 100644 --- a/packages/angular_devkit/schematics_cli/package.json +++ b/packages/angular_devkit/schematics_cli/package.json @@ -18,6 +18,6 @@ "dependencies": { "@angular-devkit/core": "workspace:0.0.0-PLACEHOLDER", "@angular-devkit/schematics": "workspace:0.0.0-PLACEHOLDER", - "@inquirer/prompts": "8.5.2" + "@inquirer/prompts": "8.6.0" } } diff --git a/packages/ngtools/webpack/package.json b/packages/ngtools/webpack/package.json index 8eef88646405..7713b54577bb 100644 --- a/packages/ngtools/webpack/package.json +++ b/packages/ngtools/webpack/package.json @@ -17,8 +17,8 @@ }, "devDependencies": { "@angular-devkit/core": "workspace:0.0.0-PLACEHOLDER", - "@angular/compiler": "22.2.0-next.0", - "@angular/compiler-cli": "22.2.0-next.0", + "@angular/compiler": "22.2.0-next.3", + "@angular/compiler-cli": "22.2.0-next.3", "typescript": "6.0.3", "webpack": "5.109.2" } diff --git a/packages/schematics/angular/ai-config/index.ts b/packages/schematics/angular/ai-config/index.ts index 7dab2fccbdef..6da0214f5f36 100644 --- a/packages/schematics/angular/ai-config/index.ts +++ b/packages/schematics/angular/ai-config/index.ts @@ -19,7 +19,11 @@ const AGENTS_MD_CFG: ContextFileInfo = { const AI_TOOLS: { [key in Exclude]: ContextFileInfo[] } = { ['claude-code']: [ - AGENTS_MD_CFG, + { + type: ContextFileType.BestPracticesMd, + name: 'CLAUDE.md', + directory: '.', + }, { type: ContextFileType.McpConfig, name: '.mcp.json', diff --git a/packages/schematics/angular/ai-config/index_spec.ts b/packages/schematics/angular/ai-config/index_spec.ts index f4408e96444c..b2eae216246f 100644 --- a/packages/schematics/angular/ai-config/index_spec.ts +++ b/packages/schematics/angular/ai-config/index_spec.ts @@ -32,9 +32,9 @@ describe('AI Config Schematic', () => { workspaceTree = await schematicRunner.runSchematic('workspace', workspaceOptions); }); - it('should create Angular MCP server config and AGENTS.md for Claude Code', async () => { + it('should create Angular MCP server config and CLAUDE.md for Claude Code', async () => { const tree = await runAiConfigSchematic([ConfigTool.ClaudeCode]); - expect(tree.exists('AGENTS.md')).toBeTruthy(); + expect(tree.exists('CLAUDE.md')).toBeTruthy(); expect(tree.exists('.mcp.json')).toBeTruthy(); }); @@ -89,7 +89,7 @@ describe('AI Config Schematic', () => { it('should omit best practices creation, if the file already exists', async () => { const customContent = 'custom user content'; - workspaceTree.create('AGENTS.md', customContent); + workspaceTree.create('CLAUDE.md', customContent); const messages: string[] = []; const loggerSubscription = schematicRunner.logger.subscribe((x) => messages.push(x.message)); @@ -97,9 +97,9 @@ describe('AI Config Schematic', () => { try { const tree = await runAiConfigSchematic([ConfigTool.ClaudeCode]); - expect(tree.readContent('AGENTS.md')).toBe(customContent); + expect(tree.readContent('CLAUDE.md')).toBe(customContent); expect(messages).toContain( - `Skipping configuration file for 'ClaudeCode' at './AGENTS.md' because it already exists.\n` + + `Skipping configuration file for 'ClaudeCode' at './CLAUDE.md' because it already exists.\n` + 'This is to prevent overwriting a potentially customized file. ' + 'If you want to regenerate it with Angular recommended defaults, please delete the existing file and re-run the command.\n' + 'You can review the latest recommendations at https://angular.dev/ai/develop-with-ai.\n', diff --git a/packages/schematics/angular/ai-config/schema.json b/packages/schematics/angular/ai-config/schema.json index 4cb63468ae53..66818095d68d 100644 --- a/packages/schematics/angular/ai-config/schema.json +++ b/packages/schematics/angular/ai-config/schema.json @@ -4,7 +4,7 @@ "title": "Angular AI Config File Options Schema", "type": "object", "additionalProperties": false, - "description": "Generates AI configuration files for Angular projects. This schematic creates AGENTS.md file and Angular MCP server configuration, improving the quality of AI-generated code and suggestions.", + "description": "Generates AI configuration files for Angular projects. This schematic creates instruction files (e.g. AGENTS.md, CLAUDE.md) and Angular MCP server configuration, improving the quality of AI-generated code and suggestions.", "properties": { "tool": { "type": "array", @@ -20,7 +20,7 @@ }, { "value": "claude-code", - "label": "Claude Code [ `AGENTS.md` + Angular MCP server config ]" + "label": "Claude Code [ `CLAUDE.md` + Angular MCP server config ]" }, { "value": "cursor", @@ -40,7 +40,7 @@ } ] }, - "description": "Specifies which AI tools to generate configuration files (AGENTS.md, MCP server config) for.", + "description": "Specifies which AI tools to generate configuration files (AGENTS.md, CLAUDE.md, MCP server config) for.", "items": { "type": "string", "enum": ["none", "claude-code", "cursor", "gemini-cli", "open-ai-codex", "vscode"] diff --git a/packages/schematics/angular/application/files/module-files/src/app/app__suffix__.spec.ts.template b/packages/schematics/angular/application/files/module-files/src/app/app__suffix__.spec.ts.template index dfe31b1010c6..b12a559c7068 100644 --- a/packages/schematics/angular/application/files/module-files/src/app/app__suffix__.spec.ts.template +++ b/packages/schematics/angular/application/files/module-files/src/app/app__suffix__.spec.ts.template @@ -11,7 +11,8 @@ describe('App', () => { declarations: [ App ], - }).compileComponents(); + }) + .compileComponents(); }); it('should create the app', () => { @@ -20,7 +21,7 @@ describe('App', () => { expect(app).toBeTruthy(); }); - it('should render title', <% if(zoneless) { %>async <% } %>() => { + it('should render title', <% if (zoneless) { %>async <% } %>() => { const fixture = TestBed.createComponent(App); <%= zoneless ? 'await fixture.whenStable();' : 'fixture.detectChanges();' %> const compiled = fixture.nativeElement as HTMLElement; diff --git a/packages/schematics/angular/application/files/module-files/src/app/app__suffix__.ts.template b/packages/schematics/angular/application/files/module-files/src/app/app__suffix__.ts.template index 055586955b75..ef9aebaa0e14 100644 --- a/packages/schematics/angular/application/files/module-files/src/app/app__suffix__.ts.template +++ b/packages/schematics/angular/application/files/module-files/src/app/app__suffix__.ts.template @@ -1,7 +1,10 @@ import { Component, signal } from '@angular/core'; @Component({ - selector: '<%= selector %>',<% if(inlineTemplate) { %> + selector: '<%= selector %>', + standalone: false,<% if (inlineStyle) { %> + styles: [],<% } else { %> + styleUrl: './app<%= suffix %>.<%= style %>',<% } %><% if (inlineTemplate) { %> template: `

Hello, {{ title() }}

Congratulations! Your app is running. 🎉

@@ -11,9 +14,6 @@ import { Component, signal } from '@angular/core'; } %> `,<% } else { %> templateUrl: './app<%= suffix %>.html',<% } %> - standalone: false,<% if(inlineStyle) { %> - styles: []<% } else { %> - styleUrl: './app<%= suffix %>.<%= style %>'<% } %> }) export class App { protected readonly title = signal('<%= name %>'); diff --git a/packages/schematics/angular/application/files/module-files/src/app/app__typeSeparator__module.ts.template b/packages/schematics/angular/application/files/module-files/src/app/app__typeSeparator__module.ts.template index f7ad6f2cb515..1efc27f522e6 100644 --- a/packages/schematics/angular/application/files/module-files/src/app/app__typeSeparator__module.ts.template +++ b/packages/schematics/angular/application/files/module-files/src/app/app__typeSeparator__module.ts.template @@ -1,6 +1,5 @@ -import { NgModule, provideBrowserGlobalErrorListeners<% if(!zoneless) { %>, provideZoneChangeDetection<% } %> } from '@angular/core'; -import { BrowserModule } from '@angular/platform-browser'; -<% if (routing) { %> +import { NgModule, provideBrowserGlobalErrorListeners<% if (!zoneless) { %>, provideZoneChangeDetection<% } %> } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser';<% if (routing) { %> import { AppRoutingModule } from './app-routing<%= typeSeparator %>module';<% } %> import { App } from './app<%= suffix %>'; @@ -13,7 +12,7 @@ import { App } from './app<%= suffix %>'; AppRoutingModule<% } %> ], providers: [ - provideBrowserGlobalErrorListeners(),<% if(!zoneless) { %> + provideBrowserGlobalErrorListeners(),<% if (!zoneless) { %> provideZoneChangeDetection({ eventCoalescing: true }),<% } %> ], bootstrap: [App] diff --git a/packages/schematics/angular/application/files/module-files/src/main.ts.template b/packages/schematics/angular/application/files/module-files/src/main.ts.template index f74887b16867..0fcb878b1e23 100644 --- a/packages/schematics/angular/application/files/module-files/src/main.ts.template +++ b/packages/schematics/angular/application/files/module-files/src/main.ts.template @@ -1,8 +1,8 @@ -<% if(!!viewEncapsulation) { %>import { ViewEncapsulation } from '@angular/core'; -<% }%>import { platformBrowser } from '@angular/platform-browser'; +<% if (!!viewEncapsulation) { %>import { ViewEncapsulation } from '@angular/core'; +<% } %>import { platformBrowser } from '@angular/platform-browser'; import { AppModule } from './app/app<%= typeSeparator %>module'; platformBrowser().bootstrapModule(AppModule, { - <% if(!!viewEncapsulation) { %> defaultEncapsulation: ViewEncapsulation.<%= viewEncapsulation %><% } %> + <% if (!!viewEncapsulation) { %> defaultEncapsulation: ViewEncapsulation.<%= viewEncapsulation %><% } %> }) .catch(err => console.error(err)); diff --git a/packages/schematics/angular/application/files/standalone-files/src/app/app.config.ts.template b/packages/schematics/angular/application/files/standalone-files/src/app/app.config.ts.template index 8f0e1b0dc23a..20201fdb80ca 100644 --- a/packages/schematics/angular/application/files/standalone-files/src/app/app.config.ts.template +++ b/packages/schematics/angular/application/files/standalone-files/src/app/app.config.ts.template @@ -1,12 +1,11 @@ -import { ApplicationConfig, provideBrowserGlobalErrorListeners<% if(!zoneless) { %>, provideZoneChangeDetection<% } %> } from '@angular/core';<% if (routing) { %> +import { ApplicationConfig, provideBrowserGlobalErrorListeners<% if (!zoneless) { %>, provideZoneChangeDetection<% } %> } from '@angular/core';<% if (routing) { %> import { provideRouter } from '@angular/router'; - import { routes } from './app.routes';<% } %> export const appConfig: ApplicationConfig = { providers: [ - provideBrowserGlobalErrorListeners(),<% if(!zoneless) { %> + provideBrowserGlobalErrorListeners(),<% if (!zoneless) { %> provideZoneChangeDetection({ eventCoalescing: true }),<% } %> - <% if (routing) {%>provideRouter(routes)<% } %> + <% if (routing) { %>provideRouter(routes)<% } %> ] }; diff --git a/packages/schematics/angular/application/files/standalone-files/src/app/app__suffix__.spec.ts.template b/packages/schematics/angular/application/files/standalone-files/src/app/app__suffix__.spec.ts.template index e6944dc73ccd..306911a0f500 100644 --- a/packages/schematics/angular/application/files/standalone-files/src/app/app__suffix__.spec.ts.template +++ b/packages/schematics/angular/application/files/standalone-files/src/app/app__suffix__.spec.ts.template @@ -5,7 +5,8 @@ describe('App', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [App], - }).compileComponents(); + }) + .compileComponents(); }); it('should create the app', () => { @@ -14,7 +15,7 @@ describe('App', () => { expect(app).toBeTruthy(); }); - it('should render title', <% if(zoneless) { %>async <% } %>() => { + it('should render title', <% if (zoneless) { %>async <% } %>() => { const fixture = TestBed.createComponent(App); <%= zoneless ? 'await fixture.whenStable();' : 'fixture.detectChanges();' %> const compiled = fixture.nativeElement as HTMLElement; diff --git a/packages/schematics/angular/application/files/standalone-files/src/app/app__suffix__.ts.template b/packages/schematics/angular/application/files/standalone-files/src/app/app__suffix__.ts.template index ce8010ecfbce..dfdb04abcb76 100644 --- a/packages/schematics/angular/application/files/standalone-files/src/app/app__suffix__.ts.template +++ b/packages/schematics/angular/application/files/standalone-files/src/app/app__suffix__.ts.template @@ -1,9 +1,11 @@ -import { Component, signal } from '@angular/core';<% if(routing) { %> +import { Component, signal } from '@angular/core';<% if (routing) { %> import { RouterOutlet } from '@angular/router';<% } %> @Component({ - selector: '<%= selector %>', - imports: [<% if(routing) { %>RouterOutlet<% } %>],<% if(inlineTemplate) { %> + imports: [<% if (routing) { %>RouterOutlet<% } %>], + selector: '<%= selector %>',<% if (inlineStyle) { %> + styles: [],<% } else { %> + styleUrl: './app<%= suffix %>.<%= style %>',<% } %><% if (inlineTemplate) { %> template: `

Hello, {{ title() }}

@@ -11,9 +13,7 @@ import { RouterOutlet } from '@angular/router';<% } %> %><% } %> `,<% } else { %> - templateUrl: './app<%= suffix %>.html',<% } if(inlineStyle) { %> - styles: [],<% } else { %> - styleUrl: './app<%= suffix %>.<%= style %>'<% } %> + templateUrl: './app<%= suffix %>.html',<% } %> }) export class App { protected readonly title = signal('<%= name %>'); diff --git a/packages/schematics/angular/component/files/__name@dasherize@if-flat__/__name@dasherize__.__type@dasherize__.__style__.template b/packages/schematics/angular/component/files/__name@dasherize@if-flat__/__name@dasherize__.__type@dasherize__.__style__.template index 2ccecd1220b9..4f8512fa3752 100644 --- a/packages/schematics/angular/component/files/__name@dasherize@if-flat__/__name@dasherize__.__type@dasherize__.__style__.template +++ b/packages/schematics/angular/component/files/__name@dasherize@if-flat__/__name@dasherize__.__type@dasherize__.__style__.template @@ -1,4 +1,4 @@ -<% if(displayBlock){ if(style != 'sass') { %>:host { +<% if (displayBlock) { if (style != 'sass') { %>:host { display: block; } <% } else { %>\:host diff --git a/packages/schematics/angular/component/files/__name@dasherize@if-flat__/__name@dasherize__.__type@dasherize__.spec.ts.template b/packages/schematics/angular/component/files/__name@dasherize@if-flat__/__name@dasherize__.__type@dasherize__.spec.ts.template index 74d858ea1e64..13be0cdf9856 100644 --- a/packages/schematics/angular/component/files/__name@dasherize@if-flat__/__name@dasherize__.__type@dasherize__.spec.ts.template +++ b/packages/schematics/angular/component/files/__name@dasherize@if-flat__/__name@dasherize__.__type@dasherize__.spec.ts.template @@ -1,6 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import <% if(!exportDefault) { %>{ <% }%><%= classifiedName %> <% if(!exportDefault) {%>} <% }%>from './<%= dasherize(name) %><%= type ? '.' + dasherize(type): '' %>'; +import <% if (!exportDefault) { %>{ <% } %><%= classifiedName %> <% if (!exportDefault) { %>} <% } %>from './<%= dasherize(name) %><%= type ? '.' + dasherize(type): '' %>'; describe('<%= classifiedName %>', () => { let component: <%= classifiedName %>; @@ -10,7 +9,7 @@ describe('<%= classifiedName %>', () => { await TestBed.configureTestingModule({ <%= standalone ? 'imports' : 'declarations' %>: [<%= classifiedName %>] }) - .compileComponents(); + .compileComponents(); fixture = TestBed.createComponent(<%= classifiedName %>); component = fixture.componentInstance; diff --git a/packages/schematics/angular/component/files/__name@dasherize@if-flat__/__name@dasherize__.__type@dasherize__.ts.template b/packages/schematics/angular/component/files/__name@dasherize@if-flat__/__name@dasherize__.__type@dasherize__.ts.template index d46cd8233862..7b3424c5be17 100644 --- a/packages/schematics/angular/component/files/__name@dasherize@if-flat__/__name@dasherize__.__type@dasherize__.ts.template +++ b/packages/schematics/angular/component/files/__name@dasherize@if-flat__/__name@dasherize__.__type@dasherize__.ts.template @@ -1,24 +1,23 @@ -import { <% if(changeDetection !== 'OnPush') { %>ChangeDetectionStrategy, <% }%>Component<% if(!!viewEncapsulation) { %>, ViewEncapsulation<% }%> } from '@angular/core'; +import { <% if (changeDetection !== 'OnPush') { %>ChangeDetectionStrategy, <% } %>Component<% if (!!viewEncapsulation) { %>, ViewEncapsulation<% } %> } from '@angular/core'; -@Component({<% if(!skipSelector) {%> - selector: '<%= selector %>',<%}%><% if(standalone) {%> - imports: [],<%} else { %> - standalone: false,<% }%><% if(inlineTemplate) { %> +@Component({<% if (changeDetection !== 'OnPush') { %> + changeDetection: ChangeDetectionStrategy.<%= changeDetection %>,<% } %><% if (!!viewEncapsulation) { %> + encapsulation: ViewEncapsulation.<%= viewEncapsulation %>,<% } %><% if (standalone) { %> + imports: [],<% } %><% if (!skipSelector) { %> + selector: '<%= selector %>',<% } %><% if (!standalone) { %> + standalone: false,<% } %><% if (inlineStyle) { %> + styles: `<% if (displayBlock) { %> + :host { + display: block; + } + <% } %>`,<% } else if (style !== 'none') { %> + styleUrl: './<%= dasherize(name) %><%= type ? '.' + dasherize(type): '' %>.<%= style %>',<% } %><% if (inlineTemplate) { %> template: `

<%= dasherize(name) %> works!

`,<% } else { %> - templateUrl: './<%= dasherize(name) %><%= type ? '.' + dasherize(type): '' %><%= ngext %>.html',<% } if(inlineStyle) { %> - styles: `<% if(displayBlock){ %> - :host { - display: block; - } - <% } %>`,<% } else if (style !== 'none') { %> - styleUrl: './<%= dasherize(name) %><%= type ? '.' + dasherize(type): '' %>.<%= style %>',<% } %><% if(!!viewEncapsulation) { %> - encapsulation: ViewEncapsulation.<%= viewEncapsulation %>,<% } if (changeDetection !== 'OnPush') { %> - changeDetection: ChangeDetectionStrategy.<%= changeDetection %>,<% } %> + templateUrl: './<%= dasherize(name) %><%= type ? '.' + dasherize(type): '' %><%= ngext %>.html',<% } %> }) -export <% if(exportDefault) {%>default <%}%>class <%= classifiedName %> { - +export <% if (exportDefault) { %>default <% } %>class <%= classifiedName %> { } diff --git a/packages/schematics/angular/directive/files/__name@dasherize__.__type@dasherize__.ts.template b/packages/schematics/angular/directive/files/__name@dasherize__.__type@dasherize__.ts.template index eff23c7e350a..d932838b1f8e 100644 --- a/packages/schematics/angular/directive/files/__name@dasherize__.__type@dasherize__.ts.template +++ b/packages/schematics/angular/directive/files/__name@dasherize__.__type@dasherize__.ts.template @@ -1,8 +1,8 @@ import { Directive } from '@angular/core'; @Directive({ - selector: '[<%= selector %>]',<% if(!standalone) {%> - standalone: false,<%}%> + selector: '[<%= selector %>]',<% if (!standalone) { %> + standalone: false,<% } %> }) export class <%= classifiedName %> { } diff --git a/packages/schematics/angular/guard/implements-files/__name@dasherize____typeSeparator__guard.spec.ts.template b/packages/schematics/angular/guard/implements-files/__name@dasherize____typeSeparator__guard.spec.ts.template index fefa3afee2b0..0bea60fdd710 100644 --- a/packages/schematics/angular/guard/implements-files/__name@dasherize____typeSeparator__guard.spec.ts.template +++ b/packages/schematics/angular/guard/implements-files/__name@dasherize____typeSeparator__guard.spec.ts.template @@ -1,5 +1,4 @@ import { TestBed } from '@angular/core/testing'; - import { <%= classify(name) %>Guard } from './<%= dasherize(name) %><%= typeSeparator %>guard'; describe('<%= classify(name) %>Guard', () => { diff --git a/packages/schematics/angular/guard/index.ts b/packages/schematics/angular/guard/index.ts index 467fe6198935..8e8d5c75610c 100644 --- a/packages/schematics/angular/guard/index.ts +++ b/packages/schematics/angular/guard/index.ts @@ -34,7 +34,7 @@ export default function (options: GuardOptions): Rule { const routerNamedImports: string[] = [...options.implements, 'MaybeAsync', 'GuardResult']; if (options.implements.includes(GuardInterface.CanMatch)) { - routerNamedImports.push('Route', 'subPath'); + routerNamedImports.push('Route', 'UrlSegment'); if (options.implements.length > 1) { routerNamedImports.push(...commonRouterNameImports); diff --git a/packages/schematics/angular/guard/index_spec.ts b/packages/schematics/angular/guard/index_spec.ts index 3b0c0da2059b..79689f8c71f8 100644 --- a/packages/schematics/angular/guard/index_spec.ts +++ b/packages/schematics/angular/guard/index_spec.ts @@ -165,7 +165,7 @@ describe('Guard Schematic', () => { const options = { ...defaultOptions, implements: implementationOptions, functional: false }; const tree = await schematicRunner.runSchematic('guard', options, appTree); const fileString = tree.readContent('/projects/bar/src/app/foo-guard.ts'); - const expectedImports = `import { CanMatch, GuardResult, MaybeAsync, Route, subPath } from '@angular/router';`; + const expectedImports = `import { CanMatch, GuardResult, MaybeAsync, Route, UrlSegment } from '@angular/router';`; expect(fileString).toContain(expectedImports); }); @@ -198,7 +198,7 @@ describe('Guard Schematic', () => { const fileString = tree.readContent('/projects/bar/src/app/foo-guard.ts'); const expectedImports = `import { ActivatedRouteSnapshot, CanActivate, CanActivateChild, CanMatch, GuardResult, ` + - `MaybeAsync, Route, RouterStateSnapshot, subPath } from '@angular/router';`; + `MaybeAsync, Route, RouterStateSnapshot, UrlSegment } from '@angular/router';`; expect(fileString).toContain(expectedImports); }); diff --git a/packages/schematics/angular/guard/type-files/__name@dasherize____typeSeparator__guard.spec.ts.template b/packages/schematics/angular/guard/type-files/__name@dasherize____typeSeparator__guard.spec.ts.template index 9bad0a553eb4..a9420dc90d56 100644 --- a/packages/schematics/angular/guard/type-files/__name@dasherize____typeSeparator__guard.spec.ts.template +++ b/packages/schematics/angular/guard/type-files/__name@dasherize____typeSeparator__guard.spec.ts.template @@ -1,6 +1,5 @@ import { TestBed } from '@angular/core/testing'; import { <%= guardType %> } from '@angular/router'; - import { <%= camelize(name) %>Guard } from './<%= dasherize(name) %><%= typeSeparator %>guard'; describe('<%= camelize(name) %>Guard', () => { diff --git a/packages/schematics/angular/interceptor/class-files/__name@dasherize____typeSeparator__interceptor.spec.ts.template b/packages/schematics/angular/interceptor/class-files/__name@dasherize____typeSeparator__interceptor.spec.ts.template index 9af595489571..b836eb209e36 100755 --- a/packages/schematics/angular/interceptor/class-files/__name@dasherize____typeSeparator__interceptor.spec.ts.template +++ b/packages/schematics/angular/interceptor/class-files/__name@dasherize____typeSeparator__interceptor.spec.ts.template @@ -1,5 +1,4 @@ import { TestBed } from '@angular/core/testing'; - import { <%= classify(name) %>Interceptor } from './<%= dasherize(name) %><%= typeSeparator %>interceptor'; describe('<%= classify(name) %>Interceptor', () => { diff --git a/packages/schematics/angular/interceptor/class-files/__name@dasherize____typeSeparator__interceptor.ts.template b/packages/schematics/angular/interceptor/class-files/__name@dasherize____typeSeparator__interceptor.ts.template index fffab4ddf988..427ddd774765 100755 --- a/packages/schematics/angular/interceptor/class-files/__name@dasherize____typeSeparator__interceptor.ts.template +++ b/packages/schematics/angular/interceptor/class-files/__name@dasherize____typeSeparator__interceptor.ts.template @@ -9,9 +9,6 @@ import { Observable } from 'rxjs'; @Injectable() export class <%= classify(name) %>Interceptor implements HttpInterceptor { - - constructor() {} - intercept(request: HttpRequest, next: HttpHandler): Observable> { return next.handle(request); } diff --git a/packages/schematics/angular/interceptor/functional-files/__name@dasherize____typeSeparator__interceptor.spec.ts.template b/packages/schematics/angular/interceptor/functional-files/__name@dasherize____typeSeparator__interceptor.spec.ts.template index ee1662c6530f..a01d79325e01 100755 --- a/packages/schematics/angular/interceptor/functional-files/__name@dasherize____typeSeparator__interceptor.spec.ts.template +++ b/packages/schematics/angular/interceptor/functional-files/__name@dasherize____typeSeparator__interceptor.spec.ts.template @@ -1,6 +1,5 @@ import { TestBed } from '@angular/core/testing'; import { HttpInterceptorFn } from '@angular/common/http'; - import { <%= camelize(name) %>Interceptor } from './<%= dasherize(name) %><%= typeSeparator %>interceptor'; describe('<%= camelize(name) %>Interceptor', () => { diff --git a/packages/schematics/angular/library/files/README.md.template b/packages/schematics/angular/library/files/README.md.template index 661e8958b9f6..d0c32c07f678 100644 --- a/packages/schematics/angular/library/files/README.md.template +++ b/packages/schematics/angular/library/files/README.md.template @@ -43,7 +43,7 @@ Once the project is built, you can publish your library by following these steps ## Running unit tests -To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command: +To execute unit tests with the [Vitest](https://vitest.dev/) test runner, use the following command: ```bash ng test diff --git a/packages/schematics/angular/ng-new/index_spec.ts b/packages/schematics/angular/ng-new/index_spec.ts index a9a6b4a1b6b2..8023c9b08531 100644 --- a/packages/schematics/angular/ng-new/index_spec.ts +++ b/packages/schematics/angular/ng-new/index_spec.ts @@ -109,7 +109,7 @@ describe('Ng New Schematic', () => { const tree = await schematicRunner.runSchematic('ng-new', options); const files = tree.files; - expect(files).toContain('/bar/AGENTS.md'); + expect(files).toContain('/bar/CLAUDE.md'); expect(files).toContain('/bar/.mcp.json'); expect(files).toContain('/bar/.gemini/GEMINI.md'); expect(files).toContain('/bar/.gemini/settings.json'); diff --git a/packages/schematics/angular/pipe/files/__name@dasherize____typeSeparator__pipe.ts.template b/packages/schematics/angular/pipe/files/__name@dasherize____typeSeparator__pipe.ts.template index 57765121531e..3348a8225ee9 100644 --- a/packages/schematics/angular/pipe/files/__name@dasherize____typeSeparator__pipe.ts.template +++ b/packages/schematics/angular/pipe/files/__name@dasherize____typeSeparator__pipe.ts.template @@ -1,13 +1,11 @@ import { Pipe, PipeTransform } from '@angular/core'; @Pipe({ - name: '<%= camelize(name) %>',<% if(!standalone) {%> - standalone: false,<%}%> + name: '<%= camelize(name) %>',<% if (!standalone) { %> + standalone: false,<% } %> }) export class <%= classify(name) %>Pipe implements PipeTransform { - transform(value: unknown, ...args: unknown[]): unknown { return null; } - } diff --git a/packages/schematics/angular/refactor/jasmine-vitest/test-file-transformer.integration_spec.ts b/packages/schematics/angular/refactor/jasmine-vitest/test-file-transformer.integration_spec.ts index 5b30e9f24f4b..ce667dce1afc 100644 --- a/packages/schematics/angular/refactor/jasmine-vitest/test-file-transformer.integration_spec.ts +++ b/packages/schematics/angular/refactor/jasmine-vitest/test-file-transformer.integration_spec.ts @@ -392,14 +392,14 @@ describe('Jasmine to Vitest Transformer - Integration Tests', () => { it('should handle fail()', () => { if (true) { - throw new Error('This should not have happened'); + expect.fail('This should not have happened'); } }); it('should handle fail() with a specific error', () => { try { expect(1).toBe(2); - throw new Error('Expected test to fail'); + expect.fail('Expected test to fail'); } catch (err) { expect(err.message).toBe('1 !== 2'); } diff --git a/packages/schematics/angular/refactor/jasmine-vitest/test-file-transformer.ts b/packages/schematics/angular/refactor/jasmine-vitest/test-file-transformer.ts index f652368b03f7..e436434f134a 100644 --- a/packages/schematics/angular/refactor/jasmine-vitest/test-file-transformer.ts +++ b/packages/schematics/angular/refactor/jasmine-vitest/test-file-transformer.ts @@ -144,6 +144,7 @@ const callExpressionTransformers = [ // **Stage 3: Global Functions & Cleanup** // These handle global Jasmine functions and catch-alls for unsupported APIs. + transformFail, transformTimerMocks, transformUnsupportedGlobalFunctions, transformUnsupportedJasmineCalls, @@ -168,7 +169,6 @@ const expressionStatementTransformers = [ transformCalledOnceWith, transformArrayWithExactContents, transformExpectNothing, - transformFail, transformJasmineMembers, ]; @@ -227,18 +227,16 @@ export function transformJasmineToVitest( } for (const transformer of callExpressionTransformers) { - if ( - !( - (options.browserMode && transformer === transformToHaveClass) || - (options.fakeAsync === false && - [ - transformFakeAsyncFlush, - transformFakeAsyncFlushMicrotasks, - transformFakeAsyncTick, - transformFakeAsyncTest, - ].includes(transformer)) - ) - ) { + if (!( + (options.browserMode && transformer === transformToHaveClass) || + (options.fakeAsync === false && + [ + transformFakeAsyncFlush, + transformFakeAsyncFlushMicrotasks, + transformFakeAsyncTick, + transformFakeAsyncTest, + ].includes(transformer)) + )) { transformedNode = transformer(transformedNode, refactorCtx); } } diff --git a/packages/schematics/angular/refactor/jasmine-vitest/test-file-transformer_add-imports_spec.ts b/packages/schematics/angular/refactor/jasmine-vitest/test-file-transformer_add-imports_spec.ts index f4b10d485920..cbe05226ef7b 100644 --- a/packages/schematics/angular/refactor/jasmine-vitest/test-file-transformer_add-imports_spec.ts +++ b/packages/schematics/angular/refactor/jasmine-vitest/test-file-transformer_add-imports_spec.ts @@ -178,4 +178,24 @@ describe('Jasmine to Vitest Transformer - addImports option', () => { `; await expectTransformation(input, expected, true); }); + + it('should add import for `expect` when `fail()` is used and addImports is true', async () => { + const input = ` + describe('My Suite', () => { + it('fails', () => { + fail('Something went wrong'); + }); + }); + `; + const expected = ` + import { describe, expect, it } from 'vitest'; + + describe('My Suite', () => { + it('fails', () => { + expect.fail('Something went wrong'); + }); + }); + `; + await expectTransformation(input, expected, true); + }); }); diff --git a/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-misc.ts b/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-misc.ts index f71353cc9783..fe7e944e53e1 100644 --- a/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-misc.ts +++ b/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-misc.ts @@ -90,29 +90,53 @@ export function transformTimerMocks(node: ts.Node, ctx: RefactorContext): ts.Nod return node; } -export function transformFail(node: ts.Node, { sourceFile, reporter }: RefactorContext): ts.Node { +export function transformFail( + node: ts.Node, + { sourceFile, reporter, pendingVitestValueImports }: RefactorContext, +): ts.Node { if ( - ts.isExpressionStatement(node) && - ts.isCallExpression(node.expression) && - ts.isIdentifier(node.expression.expression) && - node.expression.expression.text === 'fail' + ts.isCallExpression(node) && + ts.isIdentifier(node.expression) && + node.expression.text === 'fail' ) { - reporter.reportTransformation(sourceFile, node, 'Transformed `fail()` to `throw new Error()`.'); - - const arg = node.expression.arguments[0]; - let throwExpression: ts.Expression; - - if (arg && ts.isNewExpression(arg)) { - throwExpression = arg; - } else { - throwExpression = ts.factory.createNewExpression( - ts.factory.createIdentifier('Error'), - undefined, - arg ? [arg] : [], - ); + addVitestValueImport(pendingVitestValueImports, 'expect'); + reporter.reportTransformation(sourceFile, node, 'Transformed `fail()` to `expect.fail()`.'); + + const arg = node.arguments[0]; + let replacementArg: ts.Expression | undefined = arg; + let hasNonStringArg = false; + + if (arg) { + if (ts.isNewExpression(arg)) { + replacementArg = arg.arguments && arg.arguments.length > 0 ? arg.arguments[0] : undefined; + } else if ( + !ts.isStringLiteral(arg) && + !ts.isNoSubstitutionTemplateLiteral(arg) && + !ts.isTemplateExpression(arg) + ) { + replacementArg = ts.factory.createCallExpression( + ts.factory.createIdentifier('String'), + undefined, + [arg], + ); + hasNonStringArg = true; + } } - const replacement = ts.factory.createThrowStatement(throwExpression); + const replacement = ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier('expect'), + ts.factory.createIdentifier('fail'), + ), + undefined, + replacementArg ? [replacementArg] : [], + ); + + if (hasNonStringArg) { + const category = 'fail-non-string-argument'; + reporter.recordTodo(category, sourceFile, node); + addTodoComment(replacement, category); + } return ts.setOriginalNode(ts.setTextRange(replacement, node), node); } @@ -197,11 +221,7 @@ const UNSUPPORTED_GLOBAL_FUNCTION_CATEGORIES = new Set([ function isUnsupportedGlobalFunction( methodName: string, ): methodName is - | 'setSpecProperty' - | 'setSuiteProperty' - | 'throwUnless' - | 'throwUnlessAsync' - | 'getSpecProperty' { + 'setSpecProperty' | 'setSuiteProperty' | 'throwUnless' | 'throwUnlessAsync' | 'getSpecProperty' { return UNSUPPORTED_GLOBAL_FUNCTION_CATEGORIES.has(methodName as TodoCategory); } diff --git a/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-misc_spec.ts b/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-misc_spec.ts index a5b29f2d2b6a..49cb2d54475a 100644 --- a/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-misc_spec.ts +++ b/packages/schematics/angular/refactor/jasmine-vitest/transformers/jasmine-misc_spec.ts @@ -59,19 +59,31 @@ jasmine.clock().withMock(noop);`, describe('transformFail', () => { const testCases = [ { - description: 'should transform fail() to throw new Error()', + description: 'should transform fail() to expect.fail()', input: `fail('This should not happen');`, - expected: `throw new Error('This should not happen');`, + expected: `expect.fail('This should not happen');`, }, { - description: 'should transform fail() without a message to throw new Error()', + description: 'should transform fail() without a message to expect.fail()', input: `fail();`, - expected: `throw new Error();`, + expected: `expect.fail();`, }, { description: 'should transform fail() with an Error object', input: `fail(new TypeError('Invalid input'));`, - expected: `throw new TypeError('Invalid input');`, + expected: `expect.fail('Invalid input');`, + }, + { + description: 'should transform fail() with an empty Error object', + input: `fail(new Error());`, + expected: `expect.fail();`, + }, + { + description: 'should transform fail() with a non-string argument and add a TODO note', + input: `fail(err);`, + // eslint-disable-next-line max-len + expected: `// TODO: vitest-migration: expect.fail() only accepts a string message. Verify that converting this argument with String() produces the expected failure output. See: https://vitest.dev/api/expect.html#expect-fail +expect.fail(String(err));`, }, ]; diff --git a/packages/schematics/angular/refactor/jasmine-vitest/utils/todo-notes.ts b/packages/schematics/angular/refactor/jasmine-vitest/utils/todo-notes.ts index 598606d7bde6..0179a0314277 100644 --- a/packages/schematics/angular/refactor/jasmine-vitest/utils/todo-notes.ts +++ b/packages/schematics/angular/refactor/jasmine-vitest/utils/todo-notes.ts @@ -64,6 +64,12 @@ export const TODO_NOTES = { message: 'expect().nothing() has been removed because it is redundant in Vitest. Tests without assertions pass by default.', }, + 'fail-non-string-argument': { + message: + 'expect.fail() only accepts a string message. ' + + 'Verify that converting this argument with String() produces the expected failure output.', + url: 'https://vitest.dev/api/expect.html#expect-fail', + }, 'unsupported-jasmine-member': { message: (context: { name: string }): string => `jasmine.${context.name} is not supported.`, }, diff --git a/packages/schematics/angular/resolver/class-files/__name@dasherize____typeSeparator__resolver.spec.ts.template b/packages/schematics/angular/resolver/class-files/__name@dasherize____typeSeparator__resolver.spec.ts.template index af27433460e5..42e600625d0b 100644 --- a/packages/schematics/angular/resolver/class-files/__name@dasherize____typeSeparator__resolver.spec.ts.template +++ b/packages/schematics/angular/resolver/class-files/__name@dasherize____typeSeparator__resolver.spec.ts.template @@ -1,5 +1,4 @@ import { TestBed } from '@angular/core/testing'; - import { <%= classify(name) %>Resolver } from './<%= dasherize(name) %><%= typeSeparator %>resolver'; describe('<%= classify(name) %>Resolver', () => { diff --git a/packages/schematics/angular/resolver/functional-files/__name@dasherize____typeSeparator__resolver.spec.ts.template b/packages/schematics/angular/resolver/functional-files/__name@dasherize____typeSeparator__resolver.spec.ts.template index c9f42a1a0bd5..c5a62b14b577 100644 --- a/packages/schematics/angular/resolver/functional-files/__name@dasherize____typeSeparator__resolver.spec.ts.template +++ b/packages/schematics/angular/resolver/functional-files/__name@dasherize____typeSeparator__resolver.spec.ts.template @@ -1,6 +1,5 @@ import { TestBed } from '@angular/core/testing'; import { ResolveFn } from '@angular/router'; - import { <%= camelize(name) %>Resolver } from './<%= dasherize(name) %><%= typeSeparator %>resolver'; describe('<%= camelize(name) %>Resolver', () => { diff --git a/packages/schematics/angular/server/files/server-builder/ngmodule-src/app/app.module.server.ts.template b/packages/schematics/angular/server/files/server-builder/ngmodule-src/app/app.module.server.ts.template index eeffba7f902b..5e79f7aa57ad 100644 --- a/packages/schematics/angular/server/files/server-builder/ngmodule-src/app/app.module.server.ts.template +++ b/packages/schematics/angular/server/files/server-builder/ngmodule-src/app/app.module.server.ts.template @@ -1,6 +1,5 @@ import { NgModule } from '@angular/core'; import { ServerModule } from '@angular/platform-server'; - import { <%= appModuleName %> } from '<%= appModulePath %>'; import { <%= appComponentName %> } from '<%= appComponentPath %>'; diff --git a/packages/schematics/angular/service/files/__name@dasherize__.__type@dasherize__.spec.ts.template b/packages/schematics/angular/service/files/__name@dasherize__.__type@dasherize__.spec.ts.template index 168bb9ef23f2..0a85fae12489 100644 --- a/packages/schematics/angular/service/files/__name@dasherize__.__type@dasherize__.spec.ts.template +++ b/packages/schematics/angular/service/files/__name@dasherize__.__type@dasherize__.spec.ts.template @@ -1,5 +1,4 @@ import { TestBed } from '@angular/core/testing'; - import { <%= classifiedName %> } from './<%= dasherize(name) %><%= type ? '.' + dasherize(type) : '' %>'; describe('<%= classifiedName %>', () => { diff --git a/packages/schematics/angular/service/files/__name@dasherize__.__type@dasherize__.ts.template b/packages/schematics/angular/service/files/__name@dasherize__.__type@dasherize__.ts.template index 3b9a10da6ce4..0c9620cb0bcd 100644 --- a/packages/schematics/angular/service/files/__name@dasherize__.__type@dasherize__.ts.template +++ b/packages/schematics/angular/service/files/__name@dasherize__.__type@dasherize__.ts.template @@ -4,5 +4,4 @@ import { <%= injectable ? 'Injectable' : 'Service' %> } from '@angular/core'; providedIn: 'root', })<% } else { %>@Service()<% } %> export class <%= classifiedName %> { - } diff --git a/packages/schematics/angular/utility/latest-versions/package.json b/packages/schematics/angular/utility/latest-versions/package.json index 96b002df8b92..41ba63058ad4 100644 --- a/packages/schematics/angular/utility/latest-versions/package.json +++ b/packages/schematics/angular/utility/latest-versions/package.json @@ -13,10 +13,10 @@ "jasmine-spec-reporter": "~7.0.0", "karma-chrome-launcher": "~3.2.0", "karma-coverage": "~2.2.0", - "karma-jasmine-html-reporter": "~2.2.0", + "karma-jasmine-html-reporter": "~2.3.0", "karma-jasmine": "~5.1.0", "karma": "~6.4.0", - "jsdom": "^28.0.0", + "jsdom": "^30.0.0", "less": "^4.2.0", "postcss": "^8.5.3", "prettier": "^3.8.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0e3c3cd04d51..6bab452d7092 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,8 +14,8 @@ importers: .: dependencies: '@angular/compiler-cli': - specifier: 22.2.0-next.0 - version: 22.2.0-next.0(@angular/compiler@22.2.0-next.0)(typescript@6.0.3) + specifier: 22.2.0-next.3 + version: 22.2.0-next.3(@angular/compiler@22.2.0-next.3)(typescript@6.0.3) typescript: specifier: 6.0.3 version: 6.0.3 @@ -26,44 +26,44 @@ importers: built: true devDependencies: '@angular/animations': - specifier: 22.2.0-next.0 - version: 22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)) + specifier: 22.2.0-next.3 + version: 22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)) '@angular/cdk': - specifier: 22.1.0-rc.0 - version: 22.1.0-rc.0(@angular/common@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@22.2.0-next.0(@angular/animations@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2) + specifier: 22.2.0-next.2 + version: 22.2.0-next.2(@angular/common@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@22.2.0-next.3(@angular/animations@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2) '@angular/common': - specifier: 22.2.0-next.0 - version: 22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2) + specifier: 22.2.0-next.3 + version: 22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2) '@angular/compiler': - specifier: 22.2.0-next.0 - version: 22.2.0-next.0 + specifier: 22.2.0-next.3 + version: 22.2.0-next.3 '@angular/core': - specifier: 22.2.0-next.0 - version: 22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2) + specifier: 22.2.0-next.3 + version: 22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2) '@angular/forms': - specifier: 22.2.0-next.0 - version: 22.2.0-next.0(@angular/common@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@22.2.0-next.0(@angular/animations@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2) + specifier: 22.2.0-next.3 + version: 22.2.0-next.3(@angular/common@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@22.2.0-next.3(@angular/animations@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2) '@angular/localize': - specifier: 22.2.0-next.0 - version: 22.2.0-next.0(@angular/compiler-cli@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(typescript@6.0.3))(@angular/compiler@22.2.0-next.0) + specifier: 22.2.0-next.3 + version: 22.2.0-next.3(@angular/compiler-cli@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(typescript@6.0.3))(@angular/compiler@22.2.0-next.3) '@angular/material': - specifier: 22.1.0-rc.0 - version: 22.1.0-rc.0(1d924d51246212fc2acfd8877e0b8722) + specifier: 22.2.0-next.2 + version: 22.2.0-next.2(d4251d47b7bf7d31284dc64d049d8a95) '@angular/ng-dev': - specifier: https://github.com/angular/dev-infra-private-ng-dev-builds.git#dcf9b6776377d5d301e054111add3ef80f433b70 - version: https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/dcf9b6776377d5d301e054111add3ef80f433b70 + specifier: https://github.com/angular/dev-infra-private-ng-dev-builds.git#bcad06e14cb29b52b0856020af5e2ab868075602 + version: https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/bcad06e14cb29b52b0856020af5e2ab868075602 '@angular/platform-browser': - specifier: 22.2.0-next.0 - version: 22.2.0-next.0(@angular/animations@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)) + specifier: 22.2.0-next.3 + version: 22.2.0-next.3(@angular/animations@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)) '@angular/platform-server': - specifier: 22.2.0-next.0 - version: 22.2.0-next.0(@angular/common@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/compiler@22.2.0-next.0)(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@22.2.0-next.0(@angular/animations@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2) + specifier: 22.2.0-next.3 + version: 22.2.0-next.3(@angular/common@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/compiler@22.2.0-next.3)(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@22.2.0-next.3(@angular/animations@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2) '@angular/router': - specifier: 22.2.0-next.0 - version: 22.2.0-next.0(@angular/common@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@22.2.0-next.0(@angular/animations@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2) + specifier: 22.2.0-next.3 + version: 22.2.0-next.3(@angular/common@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@22.2.0-next.3(@angular/animations@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2) '@angular/service-worker': - specifier: 22.2.0-next.0 - version: 22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2) + specifier: 22.2.0-next.3 + version: 22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2) '@babel/core': specifier: 8.0.1 version: 8.0.1 @@ -78,34 +78,34 @@ importers: version: 0.28.0 '@eslint/compat': specifier: 2.1.0 - version: 2.1.0(eslint@10.8.0(jiti@2.7.0)(supports-color@11.0.0)) + version: 2.1.0(eslint@10.9.0(jiti@2.7.0)(supports-color@11.0.0)) '@eslint/eslintrc': specifier: 3.3.6 version: 3.3.6(supports-color@11.0.0) '@eslint/js': specifier: 10.0.1 - version: 10.0.1(eslint@10.8.0(jiti@2.7.0)(supports-color@11.0.0)) + version: 10.0.1(eslint@10.9.0(jiti@2.7.0)(supports-color@11.0.0)) '@rollup/plugin-alias': specifier: ^6.0.0 - version: 6.0.0(rollup@4.62.4) + version: 6.0.0(rollup@4.62.5) '@rollup/plugin-commonjs': specifier: ^29.0.0 - version: 29.0.3(rollup@4.62.4) + version: 29.0.3(rollup@4.62.5) '@rollup/plugin-json': specifier: ^6.1.0 - version: 6.1.0(rollup@4.62.4) + version: 6.1.0(rollup@4.62.5) '@rollup/plugin-node-resolve': specifier: 16.0.3 - version: 16.0.3(rollup@4.62.4) + version: 16.0.3(rollup@4.62.5) '@rollup/wasm-node': - specifier: 4.62.4 - version: 4.62.4 + specifier: 4.62.5 + version: 4.62.5 '@stylistic/eslint-plugin': specifier: ^5.0.0 - version: 5.10.0(eslint@10.8.0(jiti@2.7.0)(supports-color@11.0.0)) + version: 5.10.0(eslint@10.9.0(jiti@2.7.0)(supports-color@11.0.0)) '@tony.ganchev/eslint-plugin-header': specifier: ~3.4.0 - version: 3.4.4(eslint@10.8.0(jiti@2.7.0)(supports-color@11.0.0)) + version: 3.4.4(eslint@10.9.0(jiti@2.7.0)(supports-color@11.0.0)) '@types/babel__core': specifier: 7.20.5 version: 7.20.5 @@ -135,7 +135,7 @@ importers: version: 3.0.8 '@types/loader-utils': specifier: ^3.0.0 - version: 3.0.0(esbuild@0.28.1) + version: 3.0.0(esbuild@0.28.2) '@types/lodash': specifier: ^4.17.0 version: 4.17.25 @@ -154,9 +154,6 @@ importers: '@types/semver': specifier: ^7.3.12 version: 7.8.0 - '@types/watchpack': - specifier: ^2.4.4 - version: 2.4.5 '@types/yargs': specifier: ^17.0.20 version: 17.0.35 @@ -164,11 +161,11 @@ importers: specifier: ^21.0.0 version: 21.0.3 '@typescript-eslint/eslint-plugin': - specifier: 8.65.0 - version: 8.65.0(@typescript-eslint/parser@8.65.0(eslint@10.8.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0)(typescript@6.0.3))(eslint@10.8.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0)(typescript@6.0.3) + specifier: 8.67.0 + version: 8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.9.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0)(typescript@6.0.3))(eslint@10.9.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0)(typescript@6.0.3) '@typescript-eslint/parser': - specifier: 8.65.0 - version: 8.65.0(eslint@10.8.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0)(typescript@6.0.3) + specifier: 8.67.0 + version: 8.67.0(eslint@10.9.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0)(typescript@6.0.3) ajv: specifier: 8.20.0 version: 8.20.0 @@ -176,20 +173,20 @@ importers: specifier: 6.0.3 version: 6.0.3 esbuild: - specifier: 0.28.1 - version: 0.28.1 + specifier: 0.28.2 + version: 0.28.2 esbuild-wasm: - specifier: 0.28.1 - version: 0.28.1 + specifier: 0.28.2 + version: 0.28.2 eslint: - specifier: 10.8.0 - version: 10.8.0(jiti@2.7.0)(supports-color@11.0.0) + specifier: 10.9.0 + version: 10.9.0(jiti@2.7.0)(supports-color@11.0.0) eslint-config-prettier: specifier: 10.1.8 - version: 10.1.8(eslint@10.8.0(jiti@2.7.0)(supports-color@11.0.0)) + version: 10.1.8(eslint@10.9.0(jiti@2.7.0)(supports-color@11.0.0)) eslint-plugin-import: specifier: 2.32.0 - version: 2.32.0(@typescript-eslint/parser@8.65.0(eslint@10.8.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0)(typescript@6.0.3))(eslint@10.8.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0) + version: 2.32.0(@typescript-eslint/parser@8.67.0(eslint@10.9.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0)(typescript@6.0.3))(eslint@10.9.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0) express: specifier: 5.2.1 version: 5.2.1(supports-color@11.0.0) @@ -197,8 +194,8 @@ importers: specifier: 3.3.3 version: 3.3.3 globals: - specifier: 17.9.0 - version: 17.9.0 + specifier: 17.11.0 + version: 17.11.0 http-proxy: specifier: ^1.18.1 version: 1.18.1(debug@4.4.3(supports-color@11.0.0)) @@ -233,8 +230,8 @@ importers: specifier: ~5.1.0 version: 5.1.0(karma@6.4.4(bufferutil@4.1.0)(debug@4.4.3(supports-color@11.0.0))(supports-color@11.0.0)(utf-8-validate@6.0.6)) karma-jasmine-html-reporter: - specifier: ~2.2.0 - version: 2.2.0(jasmine-core@6.3.0)(karma-jasmine@5.1.0(karma@6.4.4(bufferutil@4.1.0)(debug@4.4.3(supports-color@11.0.0))(supports-color@11.0.0)(utf-8-validate@6.0.6)))(karma@6.4.4(bufferutil@4.1.0)(debug@4.4.3(supports-color@11.0.0))(supports-color@11.0.0)(utf-8-validate@6.0.6)) + specifier: ~2.3.0 + version: 2.3.0(jasmine-core@6.3.0)(karma-jasmine@5.1.0(karma@6.4.4(bufferutil@4.1.0)(debug@4.4.3(supports-color@11.0.0))(supports-color@11.0.0)(utf-8-validate@6.0.6)))(karma@6.4.4(bufferutil@4.1.0)(debug@4.4.3(supports-color@11.0.0))(supports-color@11.0.0)(utf-8-validate@6.0.6)) karma-source-map-support: specifier: 1.4.0 version: 1.4.0 @@ -242,29 +239,29 @@ importers: specifier: ^4.17.21 version: 4.18.1 magic-string: - specifier: 1.1.0 - version: 1.1.0 + specifier: 1.2.2 + version: 1.2.2 prettier: specifier: ^3.0.0 version: 3.9.6 puppeteer: - specifier: 25.4.0 - version: 25.4.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) + specifier: 25.8.0 + version: 25.8.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) quicktype-core: specifier: 26.0.0 version: 26.0.0 rollup: - specifier: 4.62.4 - version: 4.62.4 + specifier: 4.62.5 + version: 4.62.5 rollup-license-plugin: specifier: ~3.2.0 version: 3.2.1 rollup-plugin-dts: - specifier: 6.4.1 - version: 6.4.1(rollup@4.62.4)(typescript@6.0.3) + specifier: 6.5.1 + version: 6.5.1(rollup@4.62.5)(typescript@6.0.3) rollup-plugin-sourcemaps2: specifier: 0.5.8 - version: 0.5.8(@types/node@22.20.1)(rollup@4.62.4) + version: 0.5.8(@types/node@22.20.1)(rollup@4.62.5) semver: specifier: 7.8.5 version: 7.8.5 @@ -275,17 +272,17 @@ importers: specifier: 2.8.1 version: 2.8.1 undici: - specifier: 8.9.0 - version: 8.9.0 + specifier: 8.10.0 + version: 8.10.0 unenv: specifier: ^1.10.0 version: 1.10.0 verdaccio: - specifier: 6.9.2 - version: 6.9.2(encoding@0.1.13)(supports-color@11.0.0) + specifier: 6.10.0 + version: 6.10.0(encoding@0.1.13)(supports-color@11.0.0) verdaccio-auth-memory: specifier: ^13.0.0 - version: 13.1.1(supports-color@11.0.0) + version: 13.1.2(supports-color@11.0.0) zone.js: specifier: ^0.16.0 version: 0.16.2 @@ -305,8 +302,8 @@ importers: specifier: workspace:* version: link:../../../packages/angular/ssr '@vitest/coverage-v8': - specifier: 4.1.10 - version: 4.1.10(vitest@4.1.10) + specifier: 4.1.11 + version: 4.1.11(vitest@4.1.11) browser-sync: specifier: 3.0.4 version: 3.0.4(bufferutil@4.1.0)(debug@4.4.3(supports-color@11.0.0))(supports-color@11.0.0)(utf-8-validate@6.0.6) @@ -317,14 +314,14 @@ importers: specifier: 30.0.1 version: 30.0.1 ng-packagr: - specifier: 22.2.0-next.2 - version: 22.2.0-next.2(@angular/compiler-cli@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(typescript@6.0.3))(supports-color@11.0.0)(tslib@2.8.1)(typescript@6.0.3) + specifier: 22.2.0-next.3 + version: 22.2.0-next.3(@angular/compiler-cli@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(typescript@6.0.3))(supports-color@11.0.0)(tslib@2.8.1)(typescript@6.0.3) rxjs: specifier: 7.8.2 version: 7.8.2 vitest: - specifier: 4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@30.0.1)(less@4.8.1(supports-color@11.0.0))(sass@1.102.0)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0) + specifier: 4.1.11 + version: 4.1.11(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.0.1)(less@4.9.0(supports-color@11.0.0))(sass-embedded@1.103.1)(sass@1.103.1)(terser@5.50.0)(tsx@4.23.12)(yaml@2.9.0) packages/angular/build: dependencies: @@ -338,20 +335,26 @@ importers: specifier: 8.0.1 version: 8.0.1 '@inquirer/confirm': - specifier: 6.1.1 - version: 6.1.1(@types/node@22.20.1) + specifier: 6.2.0 + version: 6.2.0(@types/node@22.20.1) + '@parcel/watcher': + specifier: 2.6.0 + version: 2.6.0 '@vitejs/plugin-basic-ssl': specifier: 2.3.0 - version: 2.3.0(vite@8.2.0(@types/node@22.20.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.8.1(supports-color@11.0.0))(sass@1.102.0)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 2.3.0(vite@8.2.2(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.0(supports-color@11.0.0))(sass-embedded@1.103.1)(sass@1.103.1)(terser@5.50.0)(tsx@4.23.12)(yaml@2.9.0)) beasties: specifier: 0.4.3 version: 0.4.3 browserslist: specifier: ^4.26.0 - version: 4.28.7 + version: 4.28.8 + chokidar: + specifier: 5.0.0 + version: 5.0.0 esbuild: - specifier: 0.28.1 - version: 0.28.1 + specifier: 0.28.2 + version: 0.28.2 https-proxy-agent: specifier: 9.1.0 version: 9.1.0(supports-color@11.0.0) @@ -362,14 +365,14 @@ importers: specifier: 11.0.0 version: 11.0.0 magic-string: - specifier: 1.1.0 - version: 1.1.0 + specifier: 1.2.2 + version: 1.2.2 mrmime: specifier: 2.0.1 version: 2.0.1 oxc-parser: - specifier: 0.142.0 - version: 0.142.0 + specifier: 0.146.0 + version: 0.146.0 parse5-html-rewriting-stream: specifier: 8.0.1 version: 8.0.1 @@ -377,14 +380,17 @@ importers: specifier: 4.0.5 version: 4.0.5 piscina: - specifier: 5.3.0 - version: 5.3.0 + specifier: 5.3.1 + version: 5.3.1 rolldown: - specifier: 1.2.1 - version: 1.2.1 + specifier: 1.2.5 + version: 1.2.5 sass: - specifier: 1.102.0 - version: 1.102.0 + specifier: 1.103.1 + version: 1.103.1 + sass-embedded: + specifier: 1.103.1 + version: 1.103.1 semver: specifier: 7.8.5 version: 7.8.5 @@ -395,11 +401,11 @@ importers: specifier: 0.2.17 version: 0.2.17 vite: - specifier: 8.2.0 - version: 8.2.0(@types/node@22.20.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.8.1(supports-color@11.0.0))(sass@1.102.0)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0) - watchpack: - specifier: 2.5.2 - version: 2.5.2 + specifier: 8.2.2 + version: 8.2.2(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.0(supports-color@11.0.0))(sass-embedded@1.103.1)(sass@1.103.1)(terser@5.50.0)(tsx@4.23.12)(yaml@2.9.0) + xxhash-wasm: + specifier: 1.1.0 + version: 1.1.0 devDependencies: '@angular-devkit/core': specifier: workspace:* @@ -408,8 +414,8 @@ importers: specifier: workspace:* version: link:../ssr '@oxc-project/types': - specifier: 0.142.0 - version: 0.142.0 + specifier: 0.146.0 + version: 0.146.0 istanbul-lib-instrument: specifier: 6.0.3 version: 6.0.3(supports-color@11.0.0) @@ -417,23 +423,23 @@ importers: specifier: 30.0.1 version: 30.0.1 less: - specifier: 4.8.1 - version: 4.8.1(supports-color@11.0.0) + specifier: 4.9.0 + version: 4.9.0(supports-color@11.0.0) ng-packagr: - specifier: 22.2.0-next.2 - version: 22.2.0-next.2(@angular/compiler-cli@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(typescript@6.0.3))(supports-color@11.0.0)(tslib@2.8.1)(typescript@6.0.3) + specifier: 22.2.0-next.3 + version: 22.2.0-next.3(@angular/compiler-cli@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(typescript@6.0.3))(supports-color@11.0.0)(tslib@2.8.1)(typescript@6.0.3) postcss: - specifier: 8.5.25 - version: 8.5.25 + specifier: 8.5.26 + version: 8.5.26 rollup: - specifier: 4.62.4 - version: 4.62.4 + specifier: 4.62.5 + version: 4.62.5 rxjs: specifier: 7.8.2 version: 7.8.2 vitest: - specifier: 4.1.10 - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@30.0.1)(less@4.8.1(supports-color@11.0.0))(sass@1.102.0)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0) + specifier: 4.1.11 + version: 4.1.11(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.0.1)(less@4.9.0(supports-color@11.0.0))(sass-embedded@1.103.1)(sass@1.103.1)(terser@5.50.0)(tsx@4.23.12)(yaml@2.9.0) optionalDependencies: lmdb: specifier: 3.5.6 @@ -451,11 +457,11 @@ importers: specifier: workspace:0.0.0-PLACEHOLDER version: link:../../angular_devkit/schematics '@inquirer/prompts': - specifier: 8.5.2 - version: 8.5.2(@types/node@22.20.1) + specifier: 8.6.0 + version: 8.6.0(@types/node@22.20.1) '@listr2/prompt-adapter-inquirer': specifier: 4.2.5 - version: 4.2.5(@inquirer/prompts@8.5.2(@types/node@22.20.1))(@types/node@22.20.1)(listr2@11.0.0) + version: 4.2.5(@inquirer/prompts@8.6.0(@types/node@22.20.1))(@types/node@22.20.1)(listr2@11.0.0) '@modelcontextprotocol/server': specifier: 2.0.0 version: 2.0.0 @@ -506,23 +512,23 @@ importers: specifier: workspace:* version: link:../../angular_devkit/schematics '@angular/common': - specifier: 22.2.0-next.0 - version: 22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2) + specifier: 22.2.0-next.3 + version: 22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2) '@angular/compiler': - specifier: 22.2.0-next.0 - version: 22.2.0-next.0 + specifier: 22.2.0-next.3 + version: 22.2.0-next.3 '@angular/core': - specifier: 22.2.0-next.0 - version: 22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2) + specifier: 22.2.0-next.3 + version: 22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2) '@angular/platform-browser': - specifier: 22.2.0-next.0 - version: 22.2.0-next.0(@angular/animations@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)) + specifier: 22.2.0-next.3 + version: 22.2.0-next.3(@angular/animations@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)) '@angular/platform-server': - specifier: 22.2.0-next.0 - version: 22.2.0-next.0(@angular/common@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/compiler@22.2.0-next.0)(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@22.2.0-next.0(@angular/animations@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2) + specifier: 22.2.0-next.3 + version: 22.2.0-next.3(@angular/common@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/compiler@22.2.0-next.3)(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@22.2.0-next.3(@angular/animations@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2) '@angular/router': - specifier: 22.2.0-next.0 - version: 22.2.0-next.0(@angular/common@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@22.2.0-next.0(@angular/animations@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2) + specifier: 22.2.0-next.3 + version: 22.2.0-next.3(@angular/common@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@22.2.0-next.3(@angular/animations@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2) '@schematics/angular': specifier: workspace:* version: link:../../schematics/angular @@ -594,22 +600,22 @@ importers: version: 4.1.3 autoprefixer: specifier: 10.5.4 - version: 10.5.4(postcss@8.5.25) + version: 10.5.4(postcss@8.5.26) babel-loader: specifier: 10.1.1 - version: 10.1.1(@babel/core@8.0.1)(webpack@5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3)) + version: 10.1.1(@babel/core@8.0.1)(webpack@5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3)) browserslist: specifier: ^4.26.0 - version: 4.28.7 + version: 4.28.8 copy-webpack-plugin: specifier: 14.0.0 - version: 14.0.0(webpack@5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3)) + version: 14.0.0(webpack@5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3)) css-loader: specifier: 7.1.4 - version: 7.1.4(webpack@5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3)) + version: 7.1.4(webpack@5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3)) esbuild-wasm: - specifier: 0.28.1 - version: 0.28.1 + specifier: 0.28.2 + version: 0.28.2 http-proxy-middleware: specifier: 4.2.0 version: 4.2.0(supports-color@11.0.0) @@ -623,23 +629,23 @@ importers: specifier: 1.4.0 version: 1.4.0 less: - specifier: 4.8.1 - version: 4.8.1(supports-color@11.0.0) + specifier: 4.9.0 + version: 4.9.0(supports-color@11.0.0) less-loader: specifier: 13.0.0 - version: 13.0.0(less@4.8.1(supports-color@11.0.0))(webpack@5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3)) + version: 13.0.0(less@4.9.0(supports-color@11.0.0))(webpack@5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3)) license-webpack-plugin: specifier: 4.0.2 - version: 4.0.2(webpack@5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3)) + version: 4.0.2(webpack@5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3)) loader-utils: specifier: 3.3.1 version: 3.3.1 mini-css-extract-plugin: specifier: 2.10.2 - version: 2.10.2(webpack@5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3)) + version: 2.10.2(webpack@5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3)) open: - specifier: 11.0.0 - version: 11.0.0 + specifier: 11.0.1 + version: 11.0.1 ora: specifier: 9.4.1 version: 9.4.1 @@ -647,14 +653,14 @@ importers: specifier: 4.0.5 version: 4.0.5 piscina: - specifier: 5.3.0 - version: 5.3.0 + specifier: 5.3.1 + version: 5.3.1 postcss: - specifier: 8.5.25 - version: 8.5.25 + specifier: 8.5.26 + version: 8.5.26 postcss-loader: specifier: 8.2.1 - version: 8.2.1(postcss@8.5.25)(typescript@6.0.3)(webpack@5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3)) + version: 8.2.1(postcss@8.5.26)(typescript@6.0.3)(webpack@5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3)) resolve-url-loader: specifier: 5.0.0 version: 5.0.0 @@ -662,23 +668,23 @@ importers: specifier: 7.8.2 version: 7.8.2 sass: - specifier: 1.102.0 - version: 1.102.0 + specifier: 1.103.1 + version: 1.103.1 sass-loader: specifier: 17.0.0 - version: 17.0.0(sass@1.102.0)(webpack@5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3)) + version: 17.0.0(sass-embedded@1.103.1)(sass@1.103.1)(webpack@5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3)) semver: specifier: 7.8.5 version: 7.8.5 source-map-loader: specifier: 5.0.0 - version: 5.0.0(webpack@5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3)) + version: 5.0.0(webpack@5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3)) source-map-support: specifier: 0.5.21 version: 0.5.21 terser: - specifier: 5.49.0 - version: 5.49.0 + specifier: 5.50.0 + version: 5.50.0 tinyglobby: specifier: 0.2.17 version: 0.2.17 @@ -687,19 +693,19 @@ importers: version: 2.8.1 webpack: specifier: 5.109.2 - version: 5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3) + version: 5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3) webpack-dev-middleware: specifier: 8.1.1 - version: 8.1.1(tslib@2.8.1)(webpack@5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3)) + version: 8.1.1(webpack@5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3)) webpack-dev-server: specifier: 6.0.0 - version: 6.0.0(bufferutil@4.1.0)(supports-color@11.0.0)(tslib@2.8.1)(utf-8-validate@6.0.6)(webpack@5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3)) + version: 6.0.0(bufferutil@4.1.0)(supports-color@11.0.0)(utf-8-validate@6.0.6)(webpack@5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3)) webpack-merge: specifier: 6.0.1 version: 6.0.1 webpack-subresource-integrity: specifier: 5.1.0 - version: 5.1.0(webpack@5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3)) + version: 5.1.0(webpack@5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3)) devDependencies: '@angular/ssr': specifier: workspace:* @@ -708,15 +714,15 @@ importers: specifier: 3.0.4 version: 3.0.4(bufferutil@4.1.0)(debug@4.4.3(supports-color@11.0.0))(supports-color@11.0.0)(utf-8-validate@6.0.6) ng-packagr: - specifier: 22.2.0-next.2 - version: 22.2.0-next.2(@angular/compiler-cli@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(typescript@6.0.3))(supports-color@11.0.0)(tslib@2.8.1)(typescript@6.0.3) + specifier: 22.2.0-next.3 + version: 22.2.0-next.3(@angular/compiler-cli@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(typescript@6.0.3))(supports-color@11.0.0)(tslib@2.8.1)(typescript@6.0.3) undici: - specifier: 8.9.0 - version: 8.9.0 + specifier: 8.10.0 + version: 8.10.0 optionalDependencies: esbuild: - specifier: 0.28.1 - version: 0.28.1 + specifier: 0.28.2 + version: 0.28.2 packages/angular_devkit/build_webpack: dependencies: @@ -735,10 +741,10 @@ importers: version: link:../../ngtools/webpack webpack: specifier: 5.109.2 - version: 5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3) + version: 5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3) webpack-dev-server: specifier: 6.0.0 - version: 6.0.0(bufferutil@4.1.0)(supports-color@11.0.0)(tslib@2.8.1)(utf-8-validate@6.0.6)(webpack@5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3)) + version: 6.0.0(bufferutil@4.1.0)(supports-color@11.0.0)(utf-8-validate@6.0.6)(webpack@5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3)) packages/angular_devkit/core: dependencies: @@ -774,8 +780,8 @@ importers: specifier: 3.3.1 version: 3.3.1 magic-string: - specifier: 1.1.0 - version: 1.1.0 + specifier: 1.2.2 + version: 1.2.2 ora: specifier: 9.4.1 version: 9.4.1 @@ -792,8 +798,8 @@ importers: specifier: workspace:0.0.0-PLACEHOLDER version: link:../schematics '@inquirer/prompts': - specifier: 8.5.2 - version: 8.5.2(@types/node@22.20.1) + specifier: 8.6.0 + version: 8.6.0(@types/node@22.20.1) packages/ngtools/webpack: devDependencies: @@ -801,17 +807,17 @@ importers: specifier: workspace:0.0.0-PLACEHOLDER version: link:../../angular_devkit/core '@angular/compiler': - specifier: 22.2.0-next.0 - version: 22.2.0-next.0 + specifier: 22.2.0-next.3 + version: 22.2.0-next.3 '@angular/compiler-cli': - specifier: 22.2.0-next.0 - version: 22.2.0-next.0(@angular/compiler@22.2.0-next.0)(typescript@6.0.3) + specifier: 22.2.0-next.3 + version: 22.2.0-next.3(@angular/compiler@22.2.0-next.3)(typescript@6.0.3) typescript: specifier: 6.0.3 version: 6.0.3 webpack: specifier: 5.109.2 - version: 5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3) + version: 5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3) packages/schematics/angular: dependencies: @@ -858,48 +864,47 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} - '@angular/animations@22.2.0-next.0': - resolution: {integrity: sha512-L0uG7GtCtWuXA+ZRT+9vbQI2ojacCseMlUZ8Y1KcfXjl50rtlmsjjgmLDWLTKGHUG5fT18grfX01SQp03tnNlg==} + '@angular/animations@22.2.0-next.3': + resolution: {integrity: sha512-6NvI3B8d8d9XbZi2gMRwY+eBthu62uW8Gi5CHBSShlDmJc8KfYItPgVOXYYNL9im0jN1tReFiwHV4sds5vlgZA==} engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0} - deprecated: '@angular/animations is deprecated. Use `animate.enter` and `animate.leave` instead. For more information see: https://v22.angular.dev/guide/animations.' peerDependencies: - '@angular/core': 22.2.0-next.0 + '@angular/core': 22.2.0-next.3 - '@angular/cdk@22.1.0-rc.0': - resolution: {integrity: sha512-E0IbkeugB0fjXFHT2fjb3Cicqz8wt8SEAY/8GqtmP/CC5U9sXuzWLnH8JjkR/96i05WgNT0PfBnycYZEkI59Pw==} + '@angular/cdk@22.2.0-next.2': + resolution: {integrity: sha512-oe+Hf5h527tGQvjZMA6REkV9tuRW4WWNC9KM03AdQxHo8Kz19a4UqIExmFLKuFDjJqtZ45QA4AfeegD0mLeNOA==} peerDependencies: '@angular/common': ^22.0.0-0 || ^22.1.0-0 || ^22.2.0-0 || ^22.3.0-0 || ^23.0.0-0 '@angular/core': ^22.0.0-0 || ^22.1.0-0 || ^22.2.0-0 || ^22.3.0-0 || ^23.0.0-0 '@angular/platform-browser': ^22.0.0-0 || ^22.1.0-0 || ^22.2.0-0 || ^22.3.0-0 || ^23.0.0-0 rxjs: ^6.5.3 || ^7.4.0 - '@angular/common@22.2.0-next.0': - resolution: {integrity: sha512-EMhmI+JxYVUw1bA6adhrWbcjkuHCt4SJbSX6JL2vYmwyeM0c2Fn/J883ajfKcl8VNfqpyKVlD9SuLzZ2ynH3OA==} + '@angular/common@22.2.0-next.3': + resolution: {integrity: sha512-nX8hky9OuLeL7ar3OOCWaSmlWiBcDHfGvc8idsFohLNwjc5y/0KrGvR5zP6Z9HCUoxEQUeTJOaUGxY6U4209BQ==} engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0} peerDependencies: - '@angular/core': 22.2.0-next.0 + '@angular/core': 22.2.0-next.3 rxjs: ^6.5.3 || ^7.4.0 - '@angular/compiler-cli@22.2.0-next.0': - resolution: {integrity: sha512-YIZLmFVxbKQBUTjGSuPUEAzfMAxFSBtNFT8+EUja0rLX1eV1UEz3ScnvDYbjrM3WLEl58jqiv3AAOd2/cg7rNw==} + '@angular/compiler-cli@22.2.0-next.3': + resolution: {integrity: sha512-CuguonGetrbI5Ru1A/kCWJH9d4kHIo1vaMRh9mrfgO9qsS9q3MqgEcyPCYuNqKmi+oJQloD7tyK+oaFHgnWcCQ==} engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0} hasBin: true peerDependencies: - '@angular/compiler': 22.2.0-next.0 + '@angular/compiler': 22.2.0-next.3 typescript: '>=6.0 <6.1' peerDependenciesMeta: typescript: optional: true - '@angular/compiler@22.2.0-next.0': - resolution: {integrity: sha512-bhdoJLlsrUJwsYO2Ean8L49Go43v+b2a99eGBOrP+2DStb3jSBtc0STdrB9oMwhOGmo/hsQ1xRlm5O8I6o0KjA==} + '@angular/compiler@22.2.0-next.3': + resolution: {integrity: sha512-RdgU10sRXVPMGwEWVcmRcZbmlKXUwtFhc3yqEJwT1ZCCP9MQFQjz8YWy0l+yc8SMYfxVH77gQgiHaWyvtvvEdw==} engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0} - '@angular/core@22.2.0-next.0': - resolution: {integrity: sha512-H0T3UlRMSiw9Nvzj6cExb/n04vcxpg/ypbSoaGhih12OQH06A+2Oz5GJTj3jfo8rha023XqKJjxYGVmKNRtbfA==} + '@angular/core@22.2.0-next.3': + resolution: {integrity: sha512-C8IkKl58XblpONZwZoSkOkmX1c1sM9xLZSthpKzFiVnyA0ua2Ci7sVRSxPAAPaU9fKxYMUJ24w1Z+iJB7rU0yw==} engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0} peerDependencies: - '@angular/compiler': 22.2.0-next.0 + '@angular/compiler': 22.2.0-next.3 rxjs: ^6.5.3 || ^7.4.0 zone.js: ~0.15.0 || ~0.16.0 peerDependenciesMeta: @@ -908,78 +913,78 @@ packages: zone.js: optional: true - '@angular/forms@22.2.0-next.0': - resolution: {integrity: sha512-Uy6NRas4WNbJ5als+qNcZ0N4SRo/ldAohl2gPNrWzuDRQOb/aocVrPQZJQhu8i4fv7c37DU8uH9Szk3IBU9F4g==} + '@angular/forms@22.2.0-next.3': + resolution: {integrity: sha512-2ap4X3Ym89YflGgCR20ULFFI93zOOydUeCAP8ng0wxO7l0rjLjP+iDHP5RnD2izCphDdmHeQ6JSnOm4YA06gJQ==} engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0} peerDependencies: - '@angular/common': 22.2.0-next.0 - '@angular/core': 22.2.0-next.0 - '@angular/platform-browser': 22.2.0-next.0 + '@angular/common': 22.2.0-next.3 + '@angular/core': 22.2.0-next.3 + '@angular/platform-browser': 22.2.0-next.3 rxjs: ^6.5.3 || ^7.4.0 - '@angular/localize@22.2.0-next.0': - resolution: {integrity: sha512-2CVUA9mLGHPl9Kb98Apj7Gfc904e75kJ8g4X04QSkjWibOwvBf1XpHd1lS0giPjyx/ZbJrXEoldzTQtWL8vIQw==} + '@angular/localize@22.2.0-next.3': + resolution: {integrity: sha512-avaS0gRjyOXYppPqZcmQCaN82rGH/8dLIdA8bCyUsn6uGX3kCYuHCZHzyozwUiQErbkb4oQpygk6hDp9+sKubw==} engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0} hasBin: true peerDependencies: - '@angular/compiler': 22.2.0-next.0 - '@angular/compiler-cli': 22.2.0-next.0 + '@angular/compiler': 22.2.0-next.3 + '@angular/compiler-cli': 22.2.0-next.3 - '@angular/material@22.1.0-rc.0': - resolution: {integrity: sha512-bvnbsUA7UwIilDpZzfrm/537J5U1G5Ddzup0gZFhkFzrOAGhekNyfb5reGOt9ya1AzkorL+Cdh52rTUTglNnSA==} + '@angular/material@22.2.0-next.2': + resolution: {integrity: sha512-3OVQerQD4nQN6CyFepTAXOKMCTI5WOXwmvewMOV+VTkVLlGqFWEAMW4XG0bnaVdYWYtDD5mv4PT17mNfhhElFA==} peerDependencies: - '@angular/cdk': 22.1.0-rc.0 + '@angular/cdk': 22.2.0-next.2 '@angular/common': ^22.0.0-0 || ^22.1.0-0 || ^22.2.0-0 || ^22.3.0-0 || ^23.0.0-0 '@angular/core': ^22.0.0-0 || ^22.1.0-0 || ^22.2.0-0 || ^22.3.0-0 || ^23.0.0-0 '@angular/forms': ^22.0.0-0 || ^22.1.0-0 || ^22.2.0-0 || ^22.3.0-0 || ^23.0.0-0 '@angular/platform-browser': ^22.0.0-0 || ^22.1.0-0 || ^22.2.0-0 || ^22.3.0-0 || ^23.0.0-0 rxjs: ^6.5.3 || ^7.4.0 - '@angular/ng-dev@https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/dcf9b6776377d5d301e054111add3ef80f433b70': - resolution: {gitHosted: true, integrity: sha512-0g6HpWQk3yfpDvOWO9O0HgjGQR/SJQ+e5KRUZV/yWorJuI73zHoaTPhVGzLKnvo9UF0oY++VvkKmpww0iuCo0Q==, tarball: https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/dcf9b6776377d5d301e054111add3ef80f433b70} - version: 0.0.0-0b701b31038419ad73422a9d9d3dff43a725a0d2 + '@angular/ng-dev@https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/bcad06e14cb29b52b0856020af5e2ab868075602': + resolution: {gitHosted: true, integrity: sha512-fs92JkAu6Y2CXh1HEUDSTzgdVp12emRA/jEfoJG1d9V1t60tBZTsbnYyi3Zd8XkDy1Tu0l9Ne8xcHjQqwdhy8Q==, tarball: https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/bcad06e14cb29b52b0856020af5e2ab868075602} + version: 0.0.0-6d918a6dd670e28afd4ba54d00b805d4aba43dca hasBin: true - '@angular/platform-browser@22.2.0-next.0': - resolution: {integrity: sha512-qhZoQExwr9cp+U7Dlg6Gmd4OwQoG7fzG7mt7jjpjX5u1EhWMnegaqGN6rk0udiAm5FiDzzgBtikBEk7elN0pvg==} + '@angular/platform-browser@22.2.0-next.3': + resolution: {integrity: sha512-oNIJ/MJehV86A/u+I6J1czCS4gLYOpnys2uujfXAalJHMjUViBoqjw8WnrFapV73XINbucA3B34x/pZlDWrg5A==} engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0} peerDependencies: - '@angular/animations': 22.2.0-next.0 - '@angular/common': 22.2.0-next.0 - '@angular/core': 22.2.0-next.0 + '@angular/animations': 22.2.0-next.3 + '@angular/common': 22.2.0-next.3 + '@angular/core': 22.2.0-next.3 peerDependenciesMeta: '@angular/animations': optional: true - '@angular/platform-server@22.2.0-next.0': - resolution: {integrity: sha512-ufCXxsTHp8CzwzTSuqGM/B52y+F3PuDcsHF/K58ipVL7rTb2bVuo73rFePeHHG079egvKyC6moqceX5yK0OAZQ==} + '@angular/platform-server@22.2.0-next.3': + resolution: {integrity: sha512-dkwIyC+U2MfYDkgUiFYFiUCwrO0d4jxwCZF5PoBSHF2SGMoS1Q+ciWB+Jm8GPPRbWX0FaRyEfugqHoFq/XMSkw==} engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0} peerDependencies: - '@angular/common': 22.2.0-next.0 - '@angular/compiler': 22.2.0-next.0 - '@angular/core': 22.2.0-next.0 - '@angular/platform-browser': 22.2.0-next.0 + '@angular/common': 22.2.0-next.3 + '@angular/compiler': 22.2.0-next.3 + '@angular/core': 22.2.0-next.3 + '@angular/platform-browser': 22.2.0-next.3 rxjs: ^6.5.3 || ^7.4.0 - '@angular/router@22.2.0-next.0': - resolution: {integrity: sha512-QhRnVjFXjnfhTmBXPD/yK6FIAR/sKd5sYUjQZ0KLKYo8ig8PS/CnsIEUfm2WUyPG5dDPL3RnFf+BvHOuo5kfDg==} + '@angular/router@22.2.0-next.3': + resolution: {integrity: sha512-zmcJW9Z83FGWaEuOx9fJkdLWC5vaHropzZT1nFRu54LRXpj4/9+VZsoW5bTosxhP9wrgOGJWH+oqX/UkjFY0Wg==} engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0} peerDependencies: - '@angular/common': 22.2.0-next.0 - '@angular/core': 22.2.0-next.0 - '@angular/platform-browser': 22.2.0-next.0 + '@angular/common': 22.2.0-next.3 + '@angular/core': 22.2.0-next.3 + '@angular/platform-browser': 22.2.0-next.3 rxjs: ^6.5.3 || ^7.4.0 - '@angular/service-worker@22.2.0-next.0': - resolution: {integrity: sha512-+41io1mpCnAl88gQd+vhVmWRg62udPJq0QzUOI90Noz7Yi9OGv6J+1zVdSZYLlYBodSjyZaqOZze89uCubzpQg==} + '@angular/service-worker@22.2.0-next.3': + resolution: {integrity: sha512-TsVVyrV/uzsLE1espkFKQVqLubWuW1MDDmKBl567/ayGhs7ry8rLt19MvHubPbVRzDo0h3Pbx7/ddBf0i8apeg==} engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0} hasBin: true peerDependencies: - '@angular/core': 22.2.0-next.0 + '@angular/core': 22.2.0-next.3 rxjs: ^6.5.3 || ^7.4.0 - '@asamuzakjp/css-color@6.0.5': - resolution: {integrity: sha512-mbhpPMmnw/kwW19aRNmSUl1QzLbdGo1SCuE49BT98MNwqF6zaHb3o2owssFc/PEO/4t2UjqtCNwocuDtJornzA==} + '@asamuzakjp/css-color@6.0.7': + resolution: {integrity: sha512-vC/bk1Lz7Tn/EfU9/apOTBk80/8dyGyWMowPoV1tJ52muDGsDqt2HPT2klrFUiY60MQmQv9q8yIht15JnBgDGw==} engines: {node: ^22.13.0 || >=24.0.0} '@asamuzakjp/dom-selector@8.3.2': @@ -1562,24 +1567,27 @@ packages: resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} hasBin: true + '@bufbuild/protobuf@2.14.0': + resolution: {integrity: sha512-C3UGsiCwSprE2NKIIFA3hCDlpXTMCAXRZuEVp88L1GY36Y41+rYL5fryE+nOFhp4p4JPQvdV8PQ4DWgHgeTE+w==} + '@colors/colors@1.5.0': resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} - '@conventional-changelog/git-client@3.1.0': - resolution: {integrity: sha512-Tqa/gHco2WJWa740NRjOrfKVvzIqxkZpecb8bemaQ8sKM5PXb1UK4uTyTb/1wIqNuOVaDOFxyBdhTIQZn6gdjQ==} + '@conventional-changelog/git-client@3.1.2': + resolution: {integrity: sha512-jZqwnJwf7nboIlAcw/mkOjVa6DexCcUOgT2oOQgkoi3z9vR8tGFkcMy2BFcYwjhL9sYcDDXkRQDayiDieCoW7A==} engines: {node: '>=22'} peerDependencies: conventional-commits-filter: ^6.0.1 - conventional-commits-parser: ^7.0.1 + conventional-commits-parser: ^7.1.2 peerDependenciesMeta: conventional-commits-filter: optional: true conventional-commits-parser: optional: true - '@csstools/color-helpers@6.1.0': - resolution: {integrity: sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==} + '@csstools/color-helpers@6.1.1': + resolution: {integrity: sha512-gLNsunvwf3mCi5u5o46/Z/JcJMnhbHSaZ69rkgPzNM3J4s8hWwpPUQB6/tt0EDFyCiWzxANlx+2LJwpYj4zS1w==} engines: {node: '>=20.19.0'} '@csstools/css-calc@3.3.0': @@ -1589,8 +1597,8 @@ packages: '@csstools/css-parser-algorithms': ^4.0.0 '@csstools/css-tokenizer': ^4.0.0 - '@csstools/css-color-parser@4.1.10': - resolution: {integrity: sha512-UZhQLIUyJaaMepqehrCODwCg2KW25vFvLWBmqYFaPclYvvxzj/sG8LBOhBFCp11i9uE7t1EyS+RAoV9tztPFyw==} + '@csstools/css-color-parser@4.2.0': + resolution: {integrity: sha512-5+5LEmFuY1AjXdYhmgjTJogtQnP1evJ1zrBZGUNZ0thkpwnnmKxcHdAMn/OtFjAb25zA+jKDVYVRl+5G7rjv1A==} engines: {node: '>=20.19.0'} peerDependencies: '@csstools/css-parser-algorithms': ^4.0.0 @@ -1602,8 +1610,8 @@ packages: peerDependencies: '@csstools/css-tokenizer': ^4.0.0 - '@csstools/css-syntax-patches-for-csstree@1.1.7': - resolution: {integrity: sha512-fQ+05118eQS1cofO3aJpB5efgpBZMvIzwr/sbC8kDLVA5XLG8q1kJV5yzrUAI1f7lvhPnm8fgIjzFB8/O/5Dig==} + '@csstools/css-syntax-patches-for-csstree@1.1.8': + resolution: {integrity: sha512-CpMLjAvwQg3BL5S0IeqsZNMH7EQrEWi0kLKOC13ZBF0ZwERiLWlibNPJr8G1kdU3Ms/r2KiNrF81pUh2HwAHdg==} peerDependencies: css-tree: ^3.2.1 peerDependenciesMeta: @@ -1622,176 +1630,158 @@ packages: resolution: {integrity: sha512-Xc3VhU02wqZ1HvHRJUwL09HkZSTvidqY5Ya0NXBSYOxAp+Ln9dcJr9fySI+CkONzP3PekQo9WdzCv0PGER/mOA==} engines: {node: '>=14.17.0'} - '@emnapi/core@1.11.2': - resolution: {integrity: sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==} - - '@emnapi/core@2.0.0-alpha.3': - resolution: {integrity: sha512-AZypUeJ/yByuxyS7BlSNRDOMLMlROYtjYdIAuBmJssVz1UJDSeYxLrdizhXCFYhedC5bqd/ASy8EuNXbVVXp9g==} - - '@emnapi/runtime@1.11.2': - resolution: {integrity: sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==} - - '@emnapi/runtime@2.0.0-alpha.3': - resolution: {integrity: sha512-hFPAhMUjJD9BSyCANEISPOogeXC9Zo9ZQl7L6vKnaVsMkCtzznaW/naYypeyl0Gv5rYfWYsZbpixTMpjDJzQeA==} - - '@emnapi/wasi-threads@1.2.2': - resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} - - '@emnapi/wasi-threads@2.0.1': - resolution: {integrity: sha512-9DsSk+o5NBX0CCJT8s0EROGSGxjR/tKu6aBTaVyq+SjAEQH4XcdcRxPBRzsBLizTTJ49MJjF+jgu3qnO9GLQcQ==} - - '@esbuild/aix-ppc64@0.28.1': - resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} + '@esbuild/aix-ppc64@0.28.2': + resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.28.1': - resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} + '@esbuild/android-arm64@0.28.2': + resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.28.1': - resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} + '@esbuild/android-arm@0.28.2': + resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.28.1': - resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} + '@esbuild/android-x64@0.28.2': + resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.28.1': - resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} + '@esbuild/darwin-arm64@0.28.2': + resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.28.1': - resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} + '@esbuild/darwin-x64@0.28.2': + resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.28.1': - resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} + '@esbuild/freebsd-arm64@0.28.2': + resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.28.1': - resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} + '@esbuild/freebsd-x64@0.28.2': + resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.28.1': - resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} + '@esbuild/linux-arm64@0.28.2': + resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.28.1': - resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} + '@esbuild/linux-arm@0.28.2': + resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.28.1': - resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} + '@esbuild/linux-ia32@0.28.2': + resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.28.1': - resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} + '@esbuild/linux-loong64@0.28.2': + resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.28.1': - resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} + '@esbuild/linux-mips64el@0.28.2': + resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.28.1': - resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} + '@esbuild/linux-ppc64@0.28.2': + resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.28.1': - resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} + '@esbuild/linux-riscv64@0.28.2': + resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.28.1': - resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} + '@esbuild/linux-s390x@0.28.2': + resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.28.1': - resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} + '@esbuild/linux-x64@0.28.2': + resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.28.1': - resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} + '@esbuild/netbsd-arm64@0.28.2': + resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.28.1': - resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} + '@esbuild/netbsd-x64@0.28.2': + resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.28.1': - resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} + '@esbuild/openbsd-arm64@0.28.2': + resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.28.1': - resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} + '@esbuild/openbsd-x64@0.28.2': + resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.28.1': - resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} + '@esbuild/openharmony-arm64@0.28.2': + resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.28.1': - resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} + '@esbuild/sunos-x64@0.28.2': + resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.28.1': - resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} + '@esbuild/win32-arm64@0.28.2': + resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.28.1': - resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} + '@esbuild/win32-ia32@0.28.2': + resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.28.1': - resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} + '@esbuild/win32-x64@0.28.2': + resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -1857,30 +1847,32 @@ packages: '@noble/hashes': optional: true - '@firebase/ai@2.13.1': - resolution: {integrity: sha512-RhT/VViTPBSplhQSuEp62HhLvfsV+LowMh8ZUo5MMRDzG7oFtSget4Kmg5oHP50hDVyWQuQj6to9iPFEZk08Tw==} + '@firebase/ai@2.14.0': + resolution: {integrity: sha512-TYEQqCQUTyVHuG/HVi9vau6F9kvEaS49o/hmdn/yUuN6ZXQkwIml2nNJTIBfjNl/r9LOxwUNILgcOY16nxObug==} engines: {node: '>=20.0.0'} peerDependencies: '@firebase/app': 0.x '@firebase/app-types': 0.x - '@firebase/analytics-compat@0.2.28': - resolution: {integrity: sha512-lIAlqUUbBu93FJMlQfslryQtBwwzdzvp23ePC6FNgymXk6Ook5v4Uvc0vdutvoIeqmyA3LfP0ZeRFK8+11kOOQ==} + '@firebase/analytics-compat@0.2.29': + resolution: {integrity: sha512-allztvCvCUlItZzD97TiRAtGoFJzR1FQFmLxbaLc6PvgscqD9cl5NdKPTtka6keShVYXvCZJpzWcRoH4TME8rw==} peerDependencies: + '@firebase/app': 0.x '@firebase/app-compat': 0.x '@firebase/analytics-types@0.8.4': resolution: {integrity: sha512-zQ+XTgkwH6CY/eUSHJRP7e4LxM30RCxlCmob5sy2axs25GE3Ny0XdgpDscMTHHQIGqWkxPXad4w2Mw9sCgT8zQ==} - '@firebase/analytics@0.10.22': - resolution: {integrity: sha512-8BSaq/QRGU1+xyi8L2PTLTJU7MH9aMA72RQdIxrbhWFauOZY9OXo8f2YDN/972xA8d588tlnNVEQ2Mo69pT9Ow==} + '@firebase/analytics@0.10.23': + resolution: {integrity: sha512-34ALWXzWA6PTRUA5hipZmsm1RKzeecw5J1+qTCXsiMzwLqONC+GuTIQSdmm91MmTAEA+wG1Q5t0IFahcYQOqAA==} peerDependencies: '@firebase/app': 0.x - '@firebase/app-check-compat@0.4.5': - resolution: {integrity: sha512-JI17mVcZs34zO6ZeSCrw4U2iohqy+n6GIzkbmsA+TbVjmvFLkUKt3bs5M+qRBteQm/0IWzqSHYFzEQLzDTQebg==} + '@firebase/app-check-compat@0.4.6': + resolution: {integrity: sha512-2pzNEZEkX84jSqy6TH6FI1HSLA1lc7kakRUybBbKjg9YhIttPlW/XX3N9CDtChji2PTTPWVPZiWhB10exHfA+A==} engines: {node: '>=20.0.0'} peerDependencies: + '@firebase/app': 0.x '@firebase/app-compat': 0.x '@firebase/app-check-interop-types@0.3.4': @@ -1889,27 +1881,28 @@ packages: '@firebase/app-check-types@0.5.4': resolution: {integrity: sha512-xV7JsIyzVr15aA7f3Pi0rB9gdBuVubs89FGA8VkRYA4g0l78poADgdfrScgf7NndSg9mm7cR7PJyY0+t22KaGw==} - '@firebase/app-check@0.12.0': - resolution: {integrity: sha512-wMeT6HLWRAuW7Cp/5UjWBGKgjPNxWNOoNf4PRIv0weljoGMZVeqbUY7wNBWTI2/31cX1NlXx8gQruDLsUShB3Q==} + '@firebase/app-check@0.13.0': + resolution: {integrity: sha512-AbMttBKazQvGVXBZhQdVAdPzRhwHyJAY3Ghu5y2C7IZKIDIppzNYz0shTZ1mP4FBJa+28BuC4t+5h1Q6pT3Asg==} engines: {node: '>=20.0.0'} peerDependencies: '@firebase/app': 0.x - '@firebase/app-compat@0.5.15': - resolution: {integrity: sha512-HaiSM9TwbGIR4b7F6+UncHWlqdH89eeY7VUskaOGOlI2PxHS5Z+6hHsYGvNLy0SHDE6zyXO+3QSA6a4aqQxsqA==} + '@firebase/app-compat@0.5.16': + resolution: {integrity: sha512-shQq37O8qELDzvsVwYPlDXwD1zlcrZ0m2bpBF5ov2HSbY8x+AHsnL5TtJ2e1JAfkQN05qHao1AfabS69PN6GiA==} engines: {node: '>=20.0.0'} '@firebase/app-types@0.9.5': resolution: {integrity: sha512-YevqTjvo7Iujsa9Dwowmd6dSoElhzmD63ZSrq6bzjvQ6POjYgNjOFHLmNIgJs48eNO093NCERibuFnxbfOvU7A==} - '@firebase/app@0.15.1': - resolution: {integrity: sha512-iD9+Z5HcPo0Uop5f72/VYMeXwKucBhW7iFrISkJFvQ+lSZikTNgTz0FgAtaaTkAG0pEZSnCymA2Fu49n0rcufQ==} + '@firebase/app@0.16.0': + resolution: {integrity: sha512-G+ZGEyVP8YTb3ay6A+XpcYgFH3sTESHcnHU/EyTktodqhz2BHkLq+QEP7IVwjiMX0cxYwpVKip0/wC0KZcn9vQ==} engines: {node: '>=20.0.0'} - '@firebase/auth-compat@0.6.8': - resolution: {integrity: sha512-llcBREUC4iSNKZ6rvwud7Oz9Q7aAWU6KuQLa6pdu7Q+QAQsy4JLw6yFgxwtmzabsgznHmmcsX2UjHLLzqUxi3Q==} + '@firebase/auth-compat@0.6.9': + resolution: {integrity: sha512-/hHeTBmQ61+N5J1RECls+WfskZTY78JXr7aO5EMOfUpqJvDqvoS+568k0rp6Ss/4UWwBjadILs+H+SGy1zCS3A==} engines: {node: '>=20.0.0'} peerDependencies: + '@firebase/app': 0.x '@firebase/app-compat': 0.x '@firebase/auth-interop-types@0.2.5': @@ -1921,8 +1914,8 @@ packages: '@firebase/app-types': 0.x '@firebase/util': 1.x - '@firebase/auth@1.13.3': - resolution: {integrity: sha512-bqiq4uubDN2YyQkdvSWPQeJyXAv2O76ImF41En9b6UhV5JuBVYDoHYrrrE3NzIuGkpFMKagfhMRP4Vz6t+yQSQ==} + '@firebase/auth@1.13.4': + resolution: {integrity: sha512-s+NS1aV0DDyyfoIMeSz53HXnVTv7ufJjJfrP63XyaWHweJ5vOoxKWrTm5tO7S7PDqvyOa/Wi3oP0dgAo6JTMMA==} engines: {node: '>=20.0.0'} peerDependencies: '@firebase/app': 0.x @@ -1931,30 +1924,39 @@ packages: '@react-native-async-storage/async-storage': optional: true - '@firebase/component@0.7.3': - resolution: {integrity: sha512-wFofIaa2879ogD/WvkjYXJxRmfnL0scen6ORgaC3na1FNOR9ASIUANQdhqQcmWu/h77/pVHY7ch5flewa5Bcew==} + '@firebase/component@0.7.4': + resolution: {integrity: sha512-tLpOaaCol9ugUIYp2R3CbWPPA8Ajg/papX/XHEy8U52b/QXH3BbX8tTJX9aShDCjp+9sMAxMLD94i7lresdugQ==} engines: {node: '>=20.0.0'} - '@firebase/data-connect@0.7.1': - resolution: {integrity: sha512-2LbUU8mmSA63HknxQMmWHjpzuNLBKflvVwQc2tpoVKg0biWleNEJX031ELks0vzFs+dDjOUkCJR72RP6mQHFOg==} + '@firebase/data-connect@0.7.3': + resolution: {integrity: sha512-nHBFk3Ntl+NZCRIUG2d5j7I69P0otjyQ/duhVKLbw4+5cNke/F6RK1pdE5Jnf831/QOTs2Bd00LlxlZ+jNsb9w==} peerDependencies: '@firebase/app': 0.x - '@firebase/database-compat@2.1.4': - resolution: {integrity: sha512-3pK35F1MAgmqFJQlf2nhQl44vtAXQO1uaCaQOEUI9kCRtLFqi7N+QRKR7lFZPg+xIZIyubgxQaxY69YgfZRZWg==} + '@firebase/database-compat@2.1.6': + resolution: {integrity: sha512-mu7S/75UIajB1A5M9Vfojk69LttW55uABp9nHEtWrV/mIaSEwvoaIe9GySsEzS2EKFK5/3f5okcAuUbihhYeJg==} engines: {node: '>=20.0.0'} + peerDependencies: + '@firebase/app': 0.x + '@firebase/app-compat': 0.x + peerDependenciesMeta: + '@firebase/app': + optional: true + '@firebase/app-compat': + optional: true - '@firebase/database-types@1.0.20': - resolution: {integrity: sha512-kegbOk/w8iU64pr0q6k2ItyNGjnQBMHFhwS7ohdWI4W+pc0/zhhdGXTdFj6X1oxItRjPoYOsSQmERgBkn/ihxw==} + '@firebase/database-types@1.0.21': + resolution: {integrity: sha512-SX1jUqhttKgg/m9dYRTvqU9QvucBooziWfA986r4cpsbi4zlsvewe424j3Vpduwd6DG1MSAMfBVT2VqA61FnkA==} - '@firebase/database@1.1.3': - resolution: {integrity: sha512-XwWCa+E4TvNGpGwXrycLRNfdogADwFcvuhyow6wDWma9W54roaQIhe+4PM0KiLsIftBdSCGI7OKCXrdSRHbIhw==} + '@firebase/database@1.1.4': + resolution: {integrity: sha512-D+j4+8uhGtNd1tVD+X+c8JrC4ppStGJKyujSQt2NPwdN26QcCk0BeIxue+UqspHkHiFHyQOimwlzjLewGq6S+A==} engines: {node: '>=20.0.0'} - '@firebase/firestore-compat@0.4.11': - resolution: {integrity: sha512-W7o1WdwWq5aABK5Up2ncSvTQs/QGLR/fy7cVpFBNqhsXtxoMtflHf2xBIG6+aoptcuGAobddq4g2Sq27wqHaYw==} + '@firebase/firestore-compat@0.4.12': + resolution: {integrity: sha512-k2uX81Ao/S0jnFcWGPOQpKK1cPlJHvD9WIqh/RE1XBDP2yg5zhE4rHhSg1rtB11k39q3nKon9XLNDDrPjGclag==} engines: {node: '>=20.0.0'} peerDependencies: + '@firebase/app': 0.x '@firebase/app-compat': 0.x '@firebase/firestore-types@3.0.4': @@ -1963,30 +1965,32 @@ packages: '@firebase/app-types': 0.x '@firebase/util': 1.x - '@firebase/firestore@4.16.0': - resolution: {integrity: sha512-qdHMHMvMr0nRMuZyWNR/ArWa0YlPE3C4eAbmxTASJMYXAesKPL0Y54p70moggrNPzaK7MSIIq5RDJJyntQyIYA==} + '@firebase/firestore@4.17.0': + resolution: {integrity: sha512-P9tof6pyO1bnLlMWbux+5O7WFJqlb7OTPMKxxOiXKYiQl7mxykAvxr1BFCgWeEXUU7DZxQncyJ040B0IhFVZCg==} engines: {node: '>=20.0.0'} peerDependencies: '@firebase/app': 0.x - '@firebase/functions-compat@0.4.5': - resolution: {integrity: sha512-10qlUXGY25G5/1g9UihqksPp2po+ZqSE7LEizsrdUP7vrTmkysXxGSZCDyojSEp6mQe/ecRDdDDI+z4XRdb4wQ==} + '@firebase/functions-compat@0.4.6': + resolution: {integrity: sha512-dj9sOet+FIU91jeU4A3vGJoXHty7NqkSfjRLCwLgJXPDk1m72KFuxD3nlFgw/yXx/Fr7UjqzbxZ0LrIOdpx7+w==} engines: {node: '>=20.0.0'} peerDependencies: + '@firebase/app': 0.x '@firebase/app-compat': 0.x '@firebase/functions-types@0.6.4': resolution: {integrity: sha512-zV6kgqtduR4rUAdC/ilS7kmb93XD7bEZoJDlVBZqlOw2uGGGCNBQBuleww2rr0Ulr3L9o2TDjumEt68/l1f9DQ==} - '@firebase/functions@0.13.5': - resolution: {integrity: sha512-bWCx713f4kE/uFV7gdFOLBS7lDoiZj48MRkbAqe35gkXcCeWF4QjRNO07Jhmve7EJIoQOBczL29y2r8VRuN1kw==} + '@firebase/functions@0.13.6': + resolution: {integrity: sha512-9obLnzeQUivK5lmtGFOU2ucQ38BjTp+jpPtbfFp/mDsdVCvEpRqdWNvMMQ6aQwR4vcVc/utsvngm5BRkXbc7ZA==} engines: {node: '>=20.0.0'} peerDependencies: '@firebase/app': 0.x - '@firebase/installations-compat@0.2.22': - resolution: {integrity: sha512-C/zpAuTP5S9OgKSPvXRupw3hoY/JZSlA1wFjD/Sb7LIQE0FNbcMdO8Y4KXVEkjVzma/DDDDIAzxEXqKMAzc88w==} + '@firebase/installations-compat@0.2.23': + resolution: {integrity: sha512-isaXmjb9roM83eVeXAe+ZRNKYNsSo2s0aNM+cy04AAGEyVL/d8Aa11GwEXovRFeYjl9+1yRAOxRDTOukZRwTxA==} peerDependencies: + '@firebase/app': 0.x '@firebase/app-compat': 0.x '@firebase/installations-types@0.5.4': @@ -1994,8 +1998,8 @@ packages: peerDependencies: '@firebase/app-types': 0.x - '@firebase/installations@0.6.22': - resolution: {integrity: sha512-ef6nn3GGQTdReCfotRMG77PJZu8CqEbiK5pEoBnM0gTu/Z9v0i/az2p3HABsa/1beQmmyh1OsOjf7P5+pgwdZw==} + '@firebase/installations@0.6.23': + resolution: {integrity: sha512-MBkbcQfd+3qHjW+slsH4s7jH5qTdGlYpwqmxEZ7QcIpgDxu1SKyU0f+mCZhCt1BCacLNiOWF5L0R06N0LtlfMg==} peerDependencies: '@firebase/app': 0.x @@ -2003,49 +2007,53 @@ packages: resolution: {integrity: sha512-vZKLsqE1ABOy8OjQiE7cUTFn4gvaqlk88yp8N94Pk/sDpq61YqZGqmVFZTvOyflTwuYFcWirBdYGoJgbDaXKYQ==} engines: {node: '>=20.0.0'} - '@firebase/messaging-compat@0.2.27': - resolution: {integrity: sha512-JNOiu1PPgdHzEPEtoFiNxQuu0x9bm4bfETSQCpGfcTlgWkhlSK7uh7nlsjC10TQLUNgYetLmuutaYTh8aeYLVA==} + '@firebase/messaging-compat@0.2.28': + resolution: {integrity: sha512-/AmMqHRnSQhPsdeED3ocs+s30/tpFvZDiiwIYY2uXFRvLujo1fnbPOeCFoe4Y+dRy1LCSjpvJf+dy5ZTsxi1yg==} peerDependencies: + '@firebase/app': 0.x '@firebase/app-compat': 0.x '@firebase/messaging-interop-types@0.2.5': resolution: {integrity: sha512-tUEKnaAP2Y/MNIqgnriPpV6e5l13Vs/+p2yrd6NGlncPJT9O3a8muYZtdnWe+IJ4fgKLHJVC79n/asxk/N5Msw==} - '@firebase/messaging@0.13.0': - resolution: {integrity: sha512-GZoo0uGRvEbszo83xcgbjJp4FpkmBEr4l8Z4hi8gl+P1Spn/MTK3HapanMzSX4yUHuTEiF5hasWRxOaz+o5sxQ==} + '@firebase/messaging@0.13.1': + resolution: {integrity: sha512-kL8fdjbNBI7hprlXJrUjktDWosrpT4JtfwXtVVevImPF/rBRAsC+LS/jIs+kgQVuotnvMhaBCgAFipBoY9YU9g==} peerDependencies: '@firebase/app': 0.x - '@firebase/performance-compat@0.2.25': - resolution: {integrity: sha512-q6NjTXpIPoFuUmCmMN/maCdTgzT6aExs9xZo+PxfVLj6uLVGvpyAD6XWjmcrb7jChsFBYbq7E5dyNDF7Zhy9kA==} + '@firebase/performance-compat@0.2.26': + resolution: {integrity: sha512-jgoocXLN6ao26xWQ8pzosmzQ33uLzGBJQPNK0NTbVy1XvIHr5pfgBf9hWLOxsWe+R7sJq5bjD+8ybXprmt61mA==} peerDependencies: + '@firebase/app': 0.x '@firebase/app-compat': 0.x '@firebase/performance-types@0.2.4': resolution: {integrity: sha512-kJSEk7b0uhpcPRyL4SQ/GPujLqk52XNKcXlnsKDbWGAb9vugcLvOU3u6zfEdwd+d8hWJb5S5ZizV1JFFI0nkKg==} - '@firebase/performance@0.7.12': - resolution: {integrity: sha512-fe7nV8teUU3OBHlMUZ9Lw4gLhCW2k4m5Uc3pfWGV+fl8uwJQBGp9Q3lqsJ+HSrFu3Q2pJyLAgrClPGSKyDeYgQ==} + '@firebase/performance@0.7.13': + resolution: {integrity: sha512-1u6fuXP9cj0s+lkTFAspr/ttfPebPbEdpx+5Wdr4mPZbp8qH2KCMxOddEAR1ZMRa5GI0E7hDYSnolEmbqOFOAg==} peerDependencies: '@firebase/app': 0.x - '@firebase/remote-config-compat@0.2.27': - resolution: {integrity: sha512-FYwYWwSbUdza/pRX4NpSBm/Pimntum3jEIBpnDn5Ey1jHNWgjxrE8Z5SB4mCHd5wGCoYd3koJzxARl/VWIEx0Q==} + '@firebase/remote-config-compat@0.2.28': + resolution: {integrity: sha512-kEO9Gn6fbmVj7eNUtZ6d59mLgUDUD0qo7aCicGOWNfuRWTaUv3CF9DMYychO61zaEQ3cfA+CEny4V1E8A1gRGA==} peerDependencies: + '@firebase/app': 0.x '@firebase/app-compat': 0.x '@firebase/remote-config-types@0.5.1': resolution: {integrity: sha512-cX/1LT6KQwkXzck2eSzeKnuvXZCyr8qaPpDcikoJs7jmI+oBOXixpDLeDtWj1U6GNMkIoXrEDNoyT2Ypcyp5/A==} - '@firebase/remote-config@0.9.0': - resolution: {integrity: sha512-aNn6/eJhsSC+gXSToiXiYPv3ypLP9lFtzl+/q9kSOBPB7D6rae0Rt2uENZZLXGYbEgHYKQblOhijJAXGbbJjtQ==} + '@firebase/remote-config@0.9.1': + resolution: {integrity: sha512-nzQUSJnk1zAZEl2Q5O3I7Z61cYLK5JI4H6wyyOiHkVZ+bmgy1YXNNMptNbVjixMQ/eCzgA6nZRaC+1eBcJGUFA==} peerDependencies: '@firebase/app': 0.x - '@firebase/storage-compat@0.4.3': - resolution: {integrity: sha512-gruVqjtUGX8tEoeNbaWXZm0Zfcfcb7fvmDmBxV8yPAbWvExRnZYLO2+qw9idxNE7BvPXt5csyjSYHy//dAizxw==} + '@firebase/storage-compat@0.4.4': + resolution: {integrity: sha512-qSRgCB9f2R/nCp8t/8OC101cIFBFeUazlRInOMdzbnLzvrQBzEfx19SrR4pvdj/0+M+P/y8AK/a2s+3EB+B1Pw==} engines: {node: '>=20.0.0'} peerDependencies: + '@firebase/app': 0.x '@firebase/app-compat': 0.x '@firebase/storage-types@0.8.4': @@ -2054,14 +2062,14 @@ packages: '@firebase/app-types': 0.x '@firebase/util': 1.x - '@firebase/storage@0.14.3': - resolution: {integrity: sha512-YX4/YL6P6/fufSSeGnVhjWddcIXbFq2cWIhMKFTZo1E/Rtcl2mJj/BYUQTwJfcE1Tl8un1FOya4L05jcSLN/Eg==} + '@firebase/storage@0.14.4': + resolution: {integrity: sha512-jfzEWZb3Fpsq3FwAB2ifoc8mcSh935qXdDou3TpyjDWa45hhNcZUv8/w28/10njByhfK7snbakKN30nwnzQ3/w==} engines: {node: '>=20.0.0'} peerDependencies: '@firebase/app': 0.x - '@firebase/util@1.15.1': - resolution: {integrity: sha512-LUdM4Wg7YM9Pq/49nGYySJA0CSQEKnGffFzWV8+6gXN7mGxn+FL1IqvFbuZUtAQcfZgHYDwCE1wwlK7rB7gl2g==} + '@firebase/util@1.15.2': + resolution: {integrity: sha512-974pWIZVLDMc5GW5YAsj8y0XxULxIy/sPUy7tsxmWbF93KRIyh9xpuHlh0zDL+shUcf5nHDjFOg9YLiQ763eiA==} engines: {node: '>=20.0.0'} '@firebase/webchannel-wrapper@1.0.6': @@ -2098,8 +2106,8 @@ packages: resolution: {integrity: sha512-IJn+8A3QZJfe7FUtWqHVNo3xJs7KFpurCWGWCiCz3oEh+BkRymKZ1QxfAbU2yGMDzTytLGQ2IV6T2r3cuo75/w==} engines: {node: '>=18'} - '@google/genai@2.13.0': - resolution: {integrity: sha512-GM7C8Kaomvjz05x5JEO6+l3d/pciL9LxAG9dUjJLD7nTPZ9X0Cfsf2Z7eET6UjgWyUmxXCHtYnQoQ77F9+ZIOQ==} + '@google/genai@2.17.1': + resolution: {integrity: sha512-CdZ3M/titoH81hXXkvOikrOW26bC9IXh9iYT7u+r+5p7wi1LnMEnB0AbJfDeWAkjuneP4oJ299BtCt6twmORWg==} engines: {node: '>=20.0.0'} peerDependencies: '@modelcontextprotocol/sdk': ^1.25.2 @@ -2152,8 +2160,8 @@ packages: resolution: {integrity: sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q==} engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} - '@inquirer/checkbox@5.2.1': - resolution: {integrity: sha512-b6xmA/VlTe0ZgDQHDui+Nav470u7u49nRd8/iuhOcQPO9Ch7lGuogydhi2VOmNlZ+zXcM8IcPuNSwQcdJaF/kw==} + '@inquirer/checkbox@5.2.2': + resolution: {integrity: sha512-Y5/bAScMy5Y+9isCx0SKbyJebMCaXXX5em0kxkj115eZNscgV9srOHrgyfS0e5xAVymIfOh9piYBKDILktsMMg==} engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} peerDependencies: '@types/node': '>=18' @@ -2161,8 +2169,8 @@ packages: '@types/node': optional: true - '@inquirer/confirm@6.1.1': - resolution: {integrity: sha512-eb8DBZcz/2qHWQda4rk2JiQk5h9QV/cVHi1yjt0f69WFZMRFn0sJTye3EAP8icut8UDMjQPsaH5KbcOogefrFQ==} + '@inquirer/confirm@6.2.0': + resolution: {integrity: sha512-SKXarWrYhtpqOEctf9XGCGy29QjsvJAM0Aq9ZR9z4Ns94OmpqudOly+aSEfNqUf9SwsQaUgY9+Z8hyzG0xX8fw==} engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} peerDependencies: '@types/node': '>=18' @@ -2170,8 +2178,8 @@ packages: '@types/node': optional: true - '@inquirer/core@11.2.1': - resolution: {integrity: sha512-Qd6GJT1yVyrZZCfN8W2qKF5ApmqryXRhRKCuip8h01x2w/esJQ2XIYc6f9abMIHgKQdBfFTSOdbHRLAhuM09UA==} + '@inquirer/core@12.0.0': + resolution: {integrity: sha512-+nnvFEXIB08CZNVXpvW3B+zHW96QXvGUjNKJ8NJIPqAZi5Kd4WhYt2S3C234ReepG1qw2HOlEUbjYVHBowXObA==} engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} peerDependencies: '@types/node': '>=18' @@ -2179,8 +2187,8 @@ packages: '@types/node': optional: true - '@inquirer/editor@5.2.2': - resolution: {integrity: sha512-ZRVd/oD+sYsUd5zVm0NflqEzlqfYCyHNsqkHl2oWXEUHs12tCbcSFi+wVFEvD8+LGRaMUsVrE7qeo6lSG/S1Vg==} + '@inquirer/editor@5.3.0': + resolution: {integrity: sha512-nnsP/IdJ8s83q7ZuObmgn12QM+uLCkab9E0Oordojbn62WUg1c+v9Ou/F/057pgh0ppX0W+Hj5bO/Dp5hsxQtA==} engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} peerDependencies: '@types/node': '>=18' @@ -2188,8 +2196,8 @@ packages: '@types/node': optional: true - '@inquirer/expand@5.1.1': - resolution: {integrity: sha512-YmQpenjbFSHAK3sOd44puHh3V1KXXr+JiNpUztoSQ4drLh2rTVzTap/YtlAVu/5xavifIlBfNEzJ/neZJ1a/1g==} + '@inquirer/expand@5.1.2': + resolution: {integrity: sha512-OWIH1IyyWqEKIyqC9Xy+Bnga7NkGMovFdo4atYZMUOTRqf6rO2WCv9E/1MyzvOErDBCxs+9UFliRUDc50xs/jw==} engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} peerDependencies: '@types/node': '>=18' @@ -2197,8 +2205,8 @@ packages: '@types/node': optional: true - '@inquirer/external-editor@3.0.3': - resolution: {integrity: sha512-6thf5I8q7lZwzGLAxPaaGEREEkZ3nyePPDQ1oyobblxmEE8mqTLguScP7pDjUTAibiyb4hfXl+qjUEJ+di/aNA==} + '@inquirer/external-editor@3.0.4': + resolution: {integrity: sha512-tZbbaK2ovq6vlrRBNQvjrypmrED/p5x2ncIHQ79cD55tei3dD96v5glMMA+6tiq7K104i/25DVYKWVPJuV6ptA==} engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} peerDependencies: '@types/node': '>=18' @@ -2206,12 +2214,12 @@ packages: '@types/node': optional: true - '@inquirer/figures@2.0.7': - resolution: {integrity: sha512-aJ8TBPOGB6f/2qziPfElISTCEd5XOYTFckA2SGjhNmiKzfK/u4ot3v0DUzGVdUnKjN10EqnnEPck36BkyfLnJw==} + '@inquirer/figures@2.0.8': + resolution: {integrity: sha512-tApbon79GM9ry56ja/Ud3SY2CL4TQsao9fIwDQbgTeNY55025GdMzQ2+UdegV/lx51VNGUB59M0v0nMpybYY4Q==} engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} - '@inquirer/input@5.1.2': - resolution: {integrity: sha512-9K/DDBSQpOyZSkt6sOVP9Vo0TR7atX2kuILsUu0x3wVcVbe97lJwIJKMLdMw25tDYuXl/qp6erT0Xs1rfmcfZg==} + '@inquirer/input@5.1.3': + resolution: {integrity: sha512-F/BZHtyEzP+HO+IGVd4AjBRgvX/ywm42bx8S0+dENk2YclzE9tJ3X/15THwtT6ehApmKvdYDMsVTuyyDod0gOQ==} engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} peerDependencies: '@types/node': '>=18' @@ -2219,8 +2227,8 @@ packages: '@types/node': optional: true - '@inquirer/number@4.1.1': - resolution: {integrity: sha512-XF4IXAbPnGPgw0wsbC/i2tPcyfdZgDpUlhsqU0SfT4IRIGWha6Xm9VRgN5yYxJq+jnyXlfXI/nQ3ulfk0iEICA==} + '@inquirer/number@4.2.0': + resolution: {integrity: sha512-ew+fSDijsQ/WhD4TV3XLb+if400cDuzTzHfGR8sTNBXkK9CYDWoGE8fhaO8GbT312pNv1AJEOsDxy/z/HVettA==} engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} peerDependencies: '@types/node': '>=18' @@ -2228,8 +2236,8 @@ packages: '@types/node': optional: true - '@inquirer/password@5.1.1': - resolution: {integrity: sha512-3XBfF7DAsp5qeDsvN5Rd1HmbNokVvEQoUM0QLrRcybC9nX96w3Pbmu7qUsb3IT3J3jBvs2+mTXaKHOUsgHMLzg==} + '@inquirer/password@5.1.2': + resolution: {integrity: sha512-nSdufycW8xynEVssFkNQEYIzTySilog0UlfOVRwh3pXzPSk4frXUT2jZWjHnKae6RU9PaoF9wfy1pGwewQuqGw==} engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} peerDependencies: '@types/node': '>=18' @@ -2246,8 +2254,8 @@ packages: '@types/node': optional: true - '@inquirer/rawlist@5.3.1': - resolution: {integrity: sha512-QqdTqQddL3qPX/PPrjobpsO25NZ4dWXgTLenrR445L2ptLEYE6Z+PD5c5CNDJNx4ugRgELAIpSIJxZaO2jJ2Og==} + '@inquirer/prompts@8.6.0': + resolution: {integrity: sha512-WgBVDRy3IQ4v9XMCpQ1YDGpso2PcMUxYJzZdH4Nt4t0eoXhEPmOCh5iZbXbR4GTbdUB9VPWBbJB12rkjbaGDCw==} engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} peerDependencies: '@types/node': '>=18' @@ -2255,8 +2263,8 @@ packages: '@types/node': optional: true - '@inquirer/search@4.2.1': - resolution: {integrity: sha512-xJj8QWKRSrfKoBIITLZK61dD3zwo0Rz11fgDImku30/Oe81zMdIdGgrLY2h6RkJ+KZ/GhNYIRMKnH/62qBTA5g==} + '@inquirer/rawlist@5.3.2': + resolution: {integrity: sha512-oPSKrYK1X1bMkjXDzIKHUkJp195LFSfgbnVtXnjSKGFjrCbS6I+wyvfAZTwKE9BSt3HwWgfD7JfsXALBgCogzQ==} engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} peerDependencies: '@types/node': '>=18' @@ -2264,8 +2272,17 @@ packages: '@types/node': optional: true - '@inquirer/select@5.2.1': - resolution: {integrity: sha512-FlDndEUww8m7BfukO2nJa25vhD+H5jxxCv4oGioKqzyWz3nPHhhw4LKdYRSlXuAx7DsdWia7iyaBPKKS95Evfw==} + '@inquirer/search@4.3.0': + resolution: {integrity: sha512-HFxXE5w727ctSUcAwrDquftJGjMgu36OeV5SHEXMlr2j/ahzmRX9xSEeVolV8tzYnTf45cg6vGkdMMRdm3RPhQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/select@5.2.2': + resolution: {integrity: sha512-RkI8dRHWt+bh04oLixvF1kFzKC7e5rqJoHKkzcqSHATebBXFC6GmrT8ddbVkgSzLV0HnHs2cPFuBINr8otij8Q==} engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} peerDependencies: '@types/node': '>=18' @@ -2290,8 +2307,8 @@ packages: resolution: {integrity: sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==} engines: {node: '>=8'} - '@jasminejs/reporters@1.0.0': - resolution: {integrity: sha512-rM3GG4vx2H1Gp5kYCTr9aKlOEJFd43pzpiMAiy5b1+FUc2ub4e6bS6yCi/WQNDzAa5MVp9++dwcoEtcIfoEnhA==} + '@jasminejs/reporters@1.1.0': + resolution: {integrity: sha512-JIrlfaf9c6m0+GtBAgZdcFB6RIRbFBbXxZDZ0WOewq4cLIEcVKEe0RgI1FA77M39FNzeSMTAm+LK/kcQVIkfGQ==} '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -2351,50 +2368,50 @@ packages: peerDependencies: tslib: '2' - '@jsonjoy.com/fs-core@4.64.0': - resolution: {integrity: sha512-zs2TAq7Six5jgMuoMNjpspAvOP3mhtgq/k1UyQodEzCtQi/N83y2/y+zcvnZSGp/Rxq96DBN+bValOBQAyn/ew==} + '@jsonjoy.com/fs-core@4.68.1': + resolution: {integrity: sha512-V5oZ4Gt9WJKyQef0n9cAd0N9qjSkIBm3E4MYsgNIWBk5aINCDPKxMPo1i29rBxqiT4Ixf1epklqV9VJMKIxwlw==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' - '@jsonjoy.com/fs-fsa@4.64.0': - resolution: {integrity: sha512-nMWOVbkLFyEgmXZih3wyvxA9XpgyyqyfrINMHvEFqhi7uqfRl7c9ERJt6yX7vgMPrB9Uo+OJO+Spa0cFzPD01w==} + '@jsonjoy.com/fs-fsa@4.68.1': + resolution: {integrity: sha512-HCG72UioncuO7Gw09XNVG+S85e3cq2hrUC/mexBrsWsa3mI7eePkkqWie3uVYbtsb64OR9YGQs5SqaufDRYBcg==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' - '@jsonjoy.com/fs-node-builtins@4.64.0': - resolution: {integrity: sha512-/o7WRFhUWaM/fOrslwLZGnzn4RmRILykn+lAL+mNObqqRNw+CQSiij6hpCeZ+C7buhdoVo7go/OYqzaSUfDYmA==} + '@jsonjoy.com/fs-node-builtins@4.68.1': + resolution: {integrity: sha512-HK1BTksysokNZxNspqDH0yPaqN9YgR/AYIlYiIaU2Ys4BOk5CdybI7r6BgiZuiiPiV8n4sK/kZdice7Znpy2Kw==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' - '@jsonjoy.com/fs-node-to-fsa@4.64.0': - resolution: {integrity: sha512-WDD9WVs0hb7UAEKTgZW2f66WDrbj7gIIWwpP3spbLyXa0rghtUaFTB8L4gdR3ZCWwiKIsj38/CNijpVmpnuPUw==} + '@jsonjoy.com/fs-node-to-fsa@4.68.1': + resolution: {integrity: sha512-lpKmU4X9e/oh8GIuAI7EXaS5QiLNM3KD15CkdhfS6PYmrGvoJqKQcyEfnLgnnaGslh/PFUMYSIZBCf2ejJGw8g==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' - '@jsonjoy.com/fs-node-utils@4.64.0': - resolution: {integrity: sha512-k5Indsx9hWW9xSF7Y6oSKKwtCUNhzZxadub3owhIlitc+iMRVlPPdX2duTKQWBL3qNWpXya8jykgaaWpheeS4w==} + '@jsonjoy.com/fs-node-utils@4.68.1': + resolution: {integrity: sha512-/GxfW1DWm9SCdkfbvqevLO/P5duobQfmKkHXxdMIDbcZMQeAgooAstIfZhkXpATzq9QbCQsnoWFM/dGHdZfndw==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' - '@jsonjoy.com/fs-node@4.64.0': - resolution: {integrity: sha512-dO+NNkODbUli4uV42bcNrrLvq5rE7SNpdZ5TNd0dtbLsAaNK3MDiIC9lUi+brboGoIjW6vd2fB1qao60nrk5xA==} + '@jsonjoy.com/fs-node@4.68.1': + resolution: {integrity: sha512-R5D9mWtqdURzcOWj1vdXr3APCwX0xchtFT+kmW7fXLNDifWdDrnh26jSID8pdnUfFBxTyfHtFtTL/NWKzIH7kQ==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' - '@jsonjoy.com/fs-print@4.64.0': - resolution: {integrity: sha512-PHZFccchvkhWrwPWHjmVAhbC3vSHCtyZvlZfJJ3ho2bnzl450hXri6/8e6pbkWdH+SkmLXNml0sV8e5HDAfxKw==} + '@jsonjoy.com/fs-print@4.68.1': + resolution: {integrity: sha512-oGeZOGPYKK9v1CgeVeEDsLomH1lCnslSpqUN5GmPzrmAVGQlsmsdcXNA2O4lV8Y4xkuSuynx2ITBkUHJVaTbow==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' - '@jsonjoy.com/fs-snapshot@4.64.0': - resolution: {integrity: sha512-oM7UDeL83q6NBzzsfKAsYKXKVXlykKFqqOLh4xZZKAzzROTlInkPbc6LTDGThEOnPiFiUzA7tYziHG9xavd76Q==} + '@jsonjoy.com/fs-snapshot@4.68.1': + resolution: {integrity: sha512-XZfP0FDZN32bbc4t2bZN2qRrYHg5AktJnzk22HRoKGK4BprrbNRH2k5ceSNS/kupKYcofCs+O841+xaAbjnxwQ==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' @@ -2645,13 +2662,6 @@ packages: resolution: {integrity: sha512-xJIPs+bYuc9ASBl+cvGsKbGrJmS6fAKaSZCnT0lhahT5rhA2VVy9/EcIgd2JhtEuFOJNx7UHNn/qiTPTY4nrQw==} engines: {node: '>= 10'} - '@napi-rs/wasm-runtime@1.2.2': - resolution: {integrity: sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==} - engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0} - peerDependencies: - '@emnapi/core': ^1.7.1 || ^2.0.0-alpha.3 - '@emnapi/runtime': ^1.7.1 || ^2.0.0-alpha.3 - '@noble/hashes@1.4.0': resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==} engines: {node: '>= 16'} @@ -2668,8 +2678,8 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@octokit/auth-app@8.2.0': - resolution: {integrity: sha512-vVjdtQQwomrZ4V46B9LaCsxsySxGoHsyw6IYBov/TqJVROrlYdyNgw5q6tQbB7KZt53v1l1W53RiqTvpzL907g==} + '@octokit/auth-app@8.3.0': + resolution: {integrity: sha512-/UaKmJCsOc5XBZwhnFiGNdLH/FkDF8lYtBn1QlKxtX7IpgaRB/XOXjixFtERAknyUZHxp3oDuoiG0En4VptJSg==} engines: {node: '>= 20'} '@octokit/auth-oauth-app@9.0.4': @@ -2688,8 +2698,8 @@ packages: resolution: {integrity: sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==} engines: {node: '>= 20'} - '@octokit/core@7.0.6': - resolution: {integrity: sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==} + '@octokit/core@7.0.7': + resolution: {integrity: sha512-DcB0M3KFgr9ECI328lhBMVsyFT2DnmNucSBTqEN3exyNKUzkkpUSCHmTRcunF41Eou2TIQKW4seewri8ON9bSA==} engines: {node: '>= 20'} '@octokit/endpoint@11.0.4': @@ -2699,16 +2709,16 @@ packages: '@octokit/graphql-schema@15.26.1': resolution: {integrity: sha512-RFDC2MpRBd4AxSRvUeBIVeBU7ojN/SxDfALUd7iVYOSeEK3gZaqR2MGOysj4Zh2xj2RY5fQAUT+Oqq7hWTraMA==} - '@octokit/graphql@9.0.3': - resolution: {integrity: sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==} + '@octokit/graphql@9.0.4': + resolution: {integrity: sha512-5s15CCiY8XXQ+FG+b1YQcl6Z2FA++nwAz/tg2VUrTmnMncP+2nnGUEYANImdnxsA2Fnq+Mbl7hDjUTw7cFAwcg==} engines: {node: '>= 20'} '@octokit/oauth-authorization-url@8.0.0': resolution: {integrity: sha512-7QoLPRh/ssEA/HuHBHdVdSgF8xNLz/Bc5m9fZkArJE5bb6NmVkDm3anKxXPmN1zh6b5WKZPRr3697xKT/yM3qQ==} engines: {node: '>= 20'} - '@octokit/oauth-methods@6.0.3': - resolution: {integrity: sha512-t7MBs34Ja16BBZlOSAFslFh4wBMuofD83wVLG9zIuuEGMv/U12XAJ8ESOifgEcC1ptjhNA3sorLA9BCtVfgPuw==} + '@octokit/oauth-methods@6.0.4': + resolution: {integrity: sha512-96RsnxS7Hk/BQhUA1Qo2pmcYP6LWQRWMdo7bVbNE7ZCkFLhSUYBXVUj9tVnvhl4jzbwHYt/dcIG+ioY427b2sg==} engines: {node: '>= 20'} '@octokit/openapi-types@27.0.0': @@ -2747,16 +2757,12 @@ packages: peerDependencies: '@octokit/core': '>=6' - '@octokit/request-error@7.1.0': - resolution: {integrity: sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==} - engines: {node: '>= 20'} - '@octokit/request-error@7.1.1': resolution: {integrity: sha512-+eaY7G2VVpSf2pc5Gn1+mph837V/d/TYTJAgWL9Tb0ogGYcpN3IlAVFgjL+Vv93F/sevrxkvsYCedtpLdcFLzA==} engines: {node: '>= 20'} - '@octokit/request@10.0.13': - resolution: {integrity: sha512-v2269YxL9Yf+x3d+gRI63FP0vFQEiWgLyBzxe/Y+0yFDg2B/Tzf5dhh9VNfccVAQnfcfwQWyk/y6Bn7rUXXs7A==} + '@octokit/request@10.0.15': + resolution: {integrity: sha512-3CBg9aJ0hO9Pjyij8LbK/xYtEaPws9SW7xKz67daPNxQB1q5Y9OMA7DDOG0A6Hwf9ygGu3tvzusg0LXQ8/wAjA==} engines: {node: '>= 20'} '@octokit/rest@22.0.1': @@ -2798,135 +2804,130 @@ packages: resolution: {integrity: sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==} engines: {node: '>=14'} - '@oxc-parser/binding-android-arm-eabi@0.142.0': - resolution: {integrity: sha512-ZiRGDutGsv1G6bL/ozy/koC0Sv39T1DqyoC4KD1DOy9ZoACm1O5UWhEK2c02Qdk+4lfLVkvFa/mQ0fm/4h1BtQ==} + '@oxc-parser/binding-android-arm-eabi@0.146.0': + resolution: {integrity: sha512-jBCZIsff/9J/zdK1KSKZREScXOtRE4wYO+MUjrE+5NHGdsslrP4biJA9QJyKwErwS8lkwxH6Z537HaL4CQr0ow==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxc-parser/binding-android-arm64@0.142.0': - resolution: {integrity: sha512-WZkvGRLNQTz8lR9zP5nLjUdlroRCopBu3g9zF1p/laE6DzT1UbQo8Rdz5MWhaJUPYg/6gp+jo7HUgsyKaN1FtQ==} + '@oxc-parser/binding-android-arm64@0.146.0': + resolution: {integrity: sha512-BVwj6sVmgD9j5F+R5B+13pYQKOCmew+3myS19wcG5e+AeZu5ybBLEfcaKueC8MhvjtwL2Suh2tQE/Ywop2Z8rA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxc-parser/binding-darwin-arm64@0.142.0': - resolution: {integrity: sha512-l4khS8LQOOVYsGRVARo1gSaCT/aBSceUVXgtovWc2+drnxVuDr082WA3OCHVdVzIz5JIrP/y9CWsSKxBDNmYGg==} + '@oxc-parser/binding-darwin-arm64@0.146.0': + resolution: {integrity: sha512-jQGIM4zEEvvuypbBcoeoWuigRfP6R8Ow/9gfDzVoYkv5mFyVPbIePNQnV752jUynfty0nDxarIpML+7Q/MyMPg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxc-parser/binding-darwin-x64@0.142.0': - resolution: {integrity: sha512-QBsNF3nqlXmcH2B1YOPqQYmCJoy4HuIjUxGbBO/k5JAJUl68ghU2psRY2zPk+RyBaWqKP/qfL4oaFgEMCdwskA==} + '@oxc-parser/binding-darwin-x64@0.146.0': + resolution: {integrity: sha512-quDy3cvW1712l6W+qe1/xWRym6STOuGJmFzRIg5StXVJglsbqrCpHfw9PyqPs0hQeOAjAEO1USrjPaIvHiRKQw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxc-parser/binding-freebsd-x64@0.142.0': - resolution: {integrity: sha512-b7Q7m4Cqc6XqNhri3R+QhU+GVy646Pn+bkdhrDdWym/Fdi0ZUa+d73H9dm5H91JtbtAQ/z1d8XKMW3oOV8a4tQ==} + '@oxc-parser/binding-freebsd-x64@0.146.0': + resolution: {integrity: sha512-wx/p/9phlX4VbYk671G4WewST3pysXNQfZRx4gx69QIz0/uW2O3MCnnEQh4il6l2lYr0EDAq3kFYovIw68CXHw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxc-parser/binding-linux-arm-gnueabihf@0.142.0': - resolution: {integrity: sha512-3riVS5IhdH3uCZj1Y9ftDQlR0dvLsIlw/edrRqk8JhgNd5K0XSs+UBtgh50N13CAlW9/TXj6sVGXaKNBocd0Yg==} + '@oxc-parser/binding-linux-arm-gnueabihf@0.146.0': + resolution: {integrity: sha512-W9nzfbWH8Z6cIaBj6Rh/eVgymFkZ2MaOV0rQruKTLYKLAZADo9JlOmBml6Dq2QbChtmuc3DKDL42KDHaQFX8yQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxc-parser/binding-linux-arm-musleabihf@0.142.0': - resolution: {integrity: sha512-NmXUOpgpTSkhl795TiXmWppTwmSJ92RC1qvD6e4XOF+slgmo3e6Ah+kEu+6AN8s7NAOEwqGmir58MgSQSWmBSA==} + '@oxc-parser/binding-linux-arm-musleabihf@0.146.0': + resolution: {integrity: sha512-S5X1mEvTLBTl+xt7bRlr1n6qI+OTy1EaRkYyd4EQgEZqV7i53fQa1I72OqR0IwSQKVwh6TZPkzDcsN7fS9MynQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxc-parser/binding-linux-arm64-gnu@0.142.0': - resolution: {integrity: sha512-gc0EXsKtXgerujmU2Bql3u1L1HsSQ2774R83idq/FoNMPVV/RY/1ErFsvnit7KoiP/sLvzQixeUo4Ut0ic0wmw==} + '@oxc-parser/binding-linux-arm64-gnu@0.146.0': + resolution: {integrity: sha512-Br9UB4nnecVfj/Vp+JiJnJo2wIJtwaOm+uN7MwWKF5cx10P816urucmksG/iG2OeL7hfZ4h5fCmePrLU6TocfQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxc-parser/binding-linux-arm64-musl@0.142.0': - resolution: {integrity: sha512-F2XvmWSE0uWpie+jHKKIFgdVOe9ypGhkEZxKx5DuW215K6cbAC274yYaPkcM7EqY4Df3Weyhpcz3lsURyH2LVg==} + '@oxc-parser/binding-linux-arm64-musl@0.146.0': + resolution: {integrity: sha512-5AP+od9VGhRyCwswTfuD0jSDfIcWptEBC+K0LWmXsUJ9BPVF3KbhsjeoV+9tijX6g5is0w4H8DUV7NkOJoOVVA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxc-parser/binding-linux-ppc64-gnu@0.142.0': - resolution: {integrity: sha512-wLMbT21U/QxknQsk+VvNF0b9D2/aGWhcaQQQ+VYlE8FwD5+GoWZIPPXNzyHmkYyhm0KB3itL+TBavjMatqNnYA==} + '@oxc-parser/binding-linux-ppc64-gnu@0.146.0': + resolution: {integrity: sha512-Qz3jc1qgvU9B9vQu3jAJcxKlGX7zYRtKs8LPYtQi8cmirhtYcX6XvXseESaDFbi1L+SorXhjRQ/2npEX85kKBw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxc-parser/binding-linux-riscv64-gnu@0.142.0': - resolution: {integrity: sha512-+G8F/4ckwT7FCJV4H2bt09xEzJbjNCfuL4Sp1AYNaFtFMVtgIGMuJlteT82U+K0UIZ/DzAR/LDlMFnEuajG7Kw==} + '@oxc-parser/binding-linux-riscv64-gnu@0.146.0': + resolution: {integrity: sha512-Mfmg18lOZ1IWJJJuHi1ZsMLunqNVeLEh3Byuw+Aa8dYo7qOrZOMZQz1gFgAeNga5/Z5jjRuIKuIdo9aO7HYMdg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxc-parser/binding-linux-riscv64-musl@0.142.0': - resolution: {integrity: sha512-hTsHtTLxMAfCo+rpF5K3qZJKW2NpPN/CHd4mYB3y7XlSdspHkd2gehDIofP64AacA9nWQw2tY3O7wR6UY8IVOA==} + '@oxc-parser/binding-linux-riscv64-musl@0.146.0': + resolution: {integrity: sha512-6OdXwYnzbwYcnUH8f0LYauLNitjIcgDR/CDA/wcZ2whwJwVflGtCLF3+3HOXgs/SESnpQCWXr7Q/z9muLJ5bmQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxc-parser/binding-linux-s390x-gnu@0.142.0': - resolution: {integrity: sha512-6y7qYY3TCUDYjqswImdTGl92y+KA/80twALegQPN27kfY+bG7Ib1+L3jbmrCZQx6wrVnai9IPsEZp07I0hx7JQ==} + '@oxc-parser/binding-linux-s390x-gnu@0.146.0': + resolution: {integrity: sha512-BilV4Ei84A0m6r1v+LmgVrfUMbAz33IXRooVw/BQNDExUvGGNLrk6NWeot+ve99bRidTZdwWuzBHgxWCnSSa9Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxc-parser/binding-linux-x64-gnu@0.142.0': - resolution: {integrity: sha512-i69kAWU+2LgoH5bR+zWiiu+UzAw7Oxkwv7COeJTeY19pn4e70nKQcr9Pm6cL2Z0Z54d+gl9qADlK/0yyuCPiBA==} + '@oxc-parser/binding-linux-x64-gnu@0.146.0': + resolution: {integrity: sha512-RaMfGIpWOa87u3aD87drbPgisy7Nv6gIdjRFw6msIDKO+3FmcOTw+vpp4hVANhUIzpQta2EAsmYQRrqeprZTmQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxc-parser/binding-linux-x64-musl@0.142.0': - resolution: {integrity: sha512-4SQs678MmjYVrmhAgCWD4o0vpaFszXw9xLX5p2Z9MMFcltxiLkA88wQjh80YHjPrXtpyZ2CWI5m+1yNKM0m2Pw==} + '@oxc-parser/binding-linux-x64-musl@0.146.0': + resolution: {integrity: sha512-0bzF60jfQRsJ9TJOIHK+wSUirenJAyWCdP9z3P6LMS0znD4Gf7WYOiv7niONVOpRnlGjzMPd4kqgOJfCDLQAQg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxc-parser/binding-openharmony-arm64@0.142.0': - resolution: {integrity: sha512-YHpx9N7Ln3a++Tc8rv+H7mrK1zyJQOAwCFg8LZ3lTs1T5afGWeZrLPhPT9HLnIwSjCyJqPWVMIrMxbjcmBr2oQ==} + '@oxc-parser/binding-openharmony-arm64@0.146.0': + resolution: {integrity: sha512-O1gxUFt5FUgyFVUQK2BhZNwv7jLsqLR8uBANOhpYE5D5q+eCvcry7RvQT3DgKpWP1XRYB5UsbQGHHcwchEKb+w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxc-parser/binding-wasm32-wasi@0.142.0': - resolution: {integrity: sha512-3pLDyY3+oogW73RM5uehNgAiR/Xfb7fvO2Q1Z1gIqZ2+50XDVQmBVlRkHXZTU4gKnQHpwETNsYQVsJ3joVB2iA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [wasm32] - - '@oxc-parser/binding-win32-arm64-msvc@0.142.0': - resolution: {integrity: sha512-Had/VeVY28Oyb0K+Q4FV8KCzoBycIh93oDK6pCbya9lkzdq+ikMHMgBubsdqqlybjJmQRawCQRrnBRHyQwYvcQ==} + '@oxc-parser/binding-win32-arm64-msvc@0.146.0': + resolution: {integrity: sha512-i46zYS0TTCt+oaXaDx4mKxuXo+h8OBWI12XFatRm5I9/l2WKhp4TQ6VAjw0r1NMRiQQZ9dwQlRyQNhyTkOaINg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxc-parser/binding-win32-ia32-msvc@0.142.0': - resolution: {integrity: sha512-GGi3+YphVHavvgs6gum2UXoNCqzHAmPt/nXkn8ZQZstV2Q1qZD1Mn8fz/nWrDkefHQtrG/+1/XrbMxsBTo6Svw==} + '@oxc-parser/binding-win32-ia32-msvc@0.146.0': + resolution: {integrity: sha512-v1bb+ma2oKXwh4NHAg2NQwe9ORXFBDSCEkuO1qC4Rzm8EXcQdNhiJHpTNfulZ2P+1QpYUB6QYupW4mgMVom/tA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxc-parser/binding-win32-x64-msvc@0.142.0': - resolution: {integrity: sha512-Ny/Wv4Us1LGC/ljwNTp+Hx3r/pH15EFfeDF0p+n898gt+TtRd6C9SccHcuUhDiNTb8s5tt7jdeAMDRQZ4Vq6hg==} + '@oxc-parser/binding-win32-x64-msvc@0.146.0': + resolution: {integrity: sha512-uT8N1/qdNSGonvW1O2kZYNczqb/VYfnj5dpUy1FmedOPodj3MNFNWhAJkMZlI5u8xuTRtrpTccnr0U5HAMVRjQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@oxc-project/types@0.142.0': - resolution: {integrity: sha512-7W+2q5AKQVU36fkaryontrHn3YDt1RyUYXatw9i5H8ocYe2sPKSFB6eS8WNPeRKiN1qAWWZUPm7gwFzJGrccqQ==} + '@oxc-project/types@0.146.0': + resolution: {integrity: sha512-XC0QsnnhVe7sLIWmYmdPw7x5P0h4W8vUU3Nv1ySgWXtvCz8NizoAEpGXA0sOYoJQV2Rl13LgURAHQ5cI5ILCSA==} '@parcel/watcher-android-arm64@2.6.0': resolution: {integrity: sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA==} @@ -3010,35 +3011,45 @@ packages: resolution: {integrity: sha512-7FNeNl8NCE7aINx7WXiKQrPYZWC/hvrTsmk6zmxbI7LTXE7hVek/n8AfVgpe2y82zl3w0HvCHN0bVKMBoJcC0w==} engines: {node: '>= 10.0.0'} - '@peculiar/asn1-cms@2.8.0': - resolution: {integrity: sha512-NgekZOrSJFSBFLFoLfwePguAWAx7z1+f2TEsWFUMyiqqfntZ4+S/S5hzqME3q4pCA0iOsFKdwiQ35dwY24eVqA==} + '@peculiar/asn1-cms@2.9.4': + resolution: {integrity: sha512-cben7oxmQsUGZqotus7yt0srYdncOT6RNWcTQ77T2RFOXejYVYkXadrfePdRcrVpO9K95IRLKKglG2k38jKXuw==} + engines: {node: '>=14'} - '@peculiar/asn1-csr@2.8.0': - resolution: {integrity: sha512-akbF8+uvleHs8sejNPQxwmVFuInAg6FMNHOwMILXfP518YfFJwdR3jr6oNUPOaEJfuEhn/vkNOCIT6ASUd4mbg==} + '@peculiar/asn1-csr@2.9.4': + resolution: {integrity: sha512-xd4YN4vpRjkDAQWVfZZkeu12IEND7DOpkqaHSIHxZl1uggUNa9Ju0QxY2jHvDAS9pP0zhRBytg8ifsnGo3V0jw==} + engines: {node: '>=14'} - '@peculiar/asn1-ecc@2.8.0': - resolution: {integrity: sha512-ohwlk+u9Rv2NOAY1c6MfHj45ATVF8R1DUN/WCgABiRtLi2ZftlZWZX7KvpAbU8v9xPcmoILfELeEABj/rn18AQ==} + '@peculiar/asn1-ecc@2.9.4': + resolution: {integrity: sha512-JJXefFshRAuVAjWQo/39bkg1ywc1VaiO44S8RRC+Ykvf/u2KDmYffoDb0ZBPCR5uJy4AGKQhl8mX+Q8ShcWaXQ==} + engines: {node: '>=14'} - '@peculiar/asn1-pfx@2.8.0': - resolution: {integrity: sha512-5yof1ytoB++RQtaFbqSUJ8pxDJtZT6vbVqZ8XoJ61ph7UjNVvfFwAilnCodqkNsAodpy13gDhoxZXw00pghnyg==} + '@peculiar/asn1-pfx@2.9.4': + resolution: {integrity: sha512-khuGzHTzNzk4GDlIBEILyIs6Lce0yn0ZBdoI9v93kmNncfZRhD+AQ5ODFqdhvoE8cMJF/JMTQ8yA+t1D14kqCw==} + engines: {node: '>=14'} - '@peculiar/asn1-pkcs8@2.8.0': - resolution: {integrity: sha512-qAKXtLpBEw9LqhKpjw3ajZSXlBur+ipW+y2ivVBQAG6F6qRx94yO+1ZR4mvw+YaCfKSaOzLeYEzsPaBp4SJELA==} + '@peculiar/asn1-pkcs8@2.9.4': + resolution: {integrity: sha512-duRdotlUx9eDZe6QrQpQKl61RbWykCHBCkKayP8V8XdEFwlKHZ8qGGDMyS6Pye7OX7nLFttTTpRkJeet78ckwQ==} + engines: {node: '>=14'} - '@peculiar/asn1-pkcs9@2.8.0': - resolution: {integrity: sha512-b5nDWCnkV60+cQ141D6sVVwK9nz64R5n3zSVnklGd+ECdkW2Ol3U1a6yYFlalpSOaD557yuJB64A+q42jG7lUQ==} + '@peculiar/asn1-pkcs9@2.9.4': + resolution: {integrity: sha512-kaL4cNxBpdQE2dKlyZBqz4ygCrwffO+8wfoxTEqM1Z8RadvCeELBRzcv0dzM8aY9azHMwODO5nxU65zXmhToOQ==} + engines: {node: '>=14'} - '@peculiar/asn1-rsa@2.8.0': - resolution: {integrity: sha512-zHEUlCqB2mk7x2lxDwHHJy7hWZOPdGHVlsmITWKB5/PbQo61atbu9PJ/0r9dQNMwFzbKPXZ8uK8/91eUhRznSg==} + '@peculiar/asn1-rsa@2.9.4': + resolution: {integrity: sha512-pZ96eD1PptovcWQ/GSmuNFXd/7EQJNlKfDaNCyE2rx3W0v6QFelkzquVqRSRyyDXXCYD69ZXJDzZ8GhIiQzKoA==} + engines: {node: '>=14'} - '@peculiar/asn1-schema@2.8.0': - resolution: {integrity: sha512-7YT0U/ze0tF2QOBbE15gKZwy5tvgGyLRiRHLzhlbOpf7BT032oBSd0haZqXn5W6l26WLlu3dyxzjM+2638/z2Q==} + '@peculiar/asn1-schema@2.9.4': + resolution: {integrity: sha512-GjzePcT9Iw8NzeOPf73iNS9xM+TBhd/FilAfP+RQGkTMQJTVWtytN3JHJACCjf/ABNau5S7mS3g+DcuxmRgYEg==} + engines: {node: '>=14'} - '@peculiar/asn1-x509-attr@2.8.0': - resolution: {integrity: sha512-tHjkfS/qhMnmrlB2J9NhflQlQ7In3khO3CfmVrriOlpTeErY9ZIKOso1hQ5JQiyrJ7ShvqVPk7E5fQmbclkSKA==} + '@peculiar/asn1-x509-attr@2.9.4': + resolution: {integrity: sha512-ehQXbpQaQYycgu8OrvigwSPTFfVRcu0ECNYCWw+yzBp02Lw5paRqzzhUpfOgO2K38+WfFZuEz/0RPtam5g0OMg==} + engines: {node: '>=14'} - '@peculiar/asn1-x509@2.8.0': - resolution: {integrity: sha512-N0CMuhWUzsWEVq6F1q9X6+VKUnWzSW+cSVg+aPaGGwDdbFoFWTYgin5MHwXgpWd6y9COMBxnfy/Qc+Xc7F0Zwg==} + '@peculiar/asn1-x509@2.9.4': + resolution: {integrity: sha512-CxhBo/RdEbMMob7T31ZdQjGuoyRFLVwrDzTn25bihzBasRg9kRm/0IxIPvhgQtcK/9dNcO1XQL2fuPugwELL0Q==} + engines: {node: '>=14'} '@peculiar/utils@2.0.3': resolution: {integrity: sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==} @@ -3101,8 +3112,8 @@ packages: '@protobufjs/utf8@1.1.2': resolution: {integrity: sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==} - '@puppeteer/browsers@3.0.6': - resolution: {integrity: sha512-B/gKoqlFkzhvzsI6jo9K1cZz9o5ypviVv/xu8CwA4grZzyVwN+XfkT+tu8T1zrauuEXv6VhS2oGX+6NL95WcKA==} + '@puppeteer/browsers@3.2.1': + resolution: {integrity: sha512-KDz+3qDRdBAlRlMjmKyj6dEs33YHTk/xRHEENSXq6TNnhgoU15ruSHtEBeVF6OZ9tBDY55Se4P0nFMNsipzU9A==} engines: {node: '>=22.12.0'} hasBin: true peerDependencies: @@ -3114,96 +3125,98 @@ packages: yauzl: optional: true - '@rolldown/binding-android-arm64@1.2.1': - resolution: {integrity: sha512-02hOeOSryYxVrOIphmLAsqnCJWxwlzFk+pEt/N/i6OgT3lShHO7xGCU5cpgchRDHboAEbSjzgGh+O/u1GswQmA==} + '@rolldown/binding-android-arm-eabi@1.2.5': + resolution: {integrity: sha512-DLe/i+l8ynIBY7XEQ191TeZvCoowIGa18R+dIV30GW7DiOtp74i/xX8hs8GUjW5ARV7VZuie3d6AumSmCwbeRA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@rolldown/binding-android-arm64@1.2.5': + resolution: {integrity: sha512-zXcwKlQApYAOELHd8PwKDFkagYF9Wy4e0RJ+0qnzl9Pjnpj75TEG8ufv40p2J7kCEfwZAsNiuzRIyNNMWT38ig==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.2.1': - resolution: {integrity: sha512-fMsTOnN0OjFm3CyppWPitKnc8UlliVARUULW6cfU6AIqjdtgmSFWSk9vecHzZduv/yMWIHDlRhM1e8Iff9uAfA==} + '@rolldown/binding-darwin-arm64@1.2.5': + resolution: {integrity: sha512-dK4QakI42nzWgJT5sm4y4y/O//D4OxM75/cH28RLV+nzIN9AY+YsbuUVrUTjlLjXR6vpyxFbSsbmNuJ6BP9sww==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.2.1': - resolution: {integrity: sha512-1wjKdz/XLGKHaTNHjQveQ/B23TKx4ItAqm1JbyVuvNPc4Ze0Fb48s49TAd/2zcplPl8okE/UbTgmlVfwT7eFeQ==} + '@rolldown/binding-darwin-x64@1.2.5': + resolution: {integrity: sha512-fqSALaUu1Wjd1nK2uW2kJDWdLCc8lx1IcY+MTY26Aurfdx19anlzhqXOgCFbBFQnlFDTn4TC1/7Nz4Bl2mLP3A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.2.1': - resolution: {integrity: sha512-Fa0jHR07E7YBN4vOEsbVf2briYNsuOowfLJaXULZM0ldMlaCaj2LJgLMbMe4iacRyZmvR8efFhgR9wKuGclQUg==} + '@rolldown/binding-freebsd-x64@1.2.5': + resolution: {integrity: sha512-/vCnNxlkxs9tKxNDcyWUePpJ/PgTzxIaVhoM5SmG8UV+GR/IcPam4VYxi7GIMo7PSDuNqlJqvprqii9NqqVCMw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.2.1': - resolution: {integrity: sha512-pzkgu1SSHGgRRyRZ4fbmSgmajbVt+epaLP99NDjFft69v/ypfTi6swBMiVdh2EkQ0OSnHE1lZDM7DRGkyAzUpA==} + '@rolldown/binding-linux-arm-gnueabihf@1.2.5': + resolution: {integrity: sha512-abk0NLA519LxRCszmbE0jYKuQ9YPocOXTiOXOo6Yr+YAT95VH+PtqYAjOJvGKt3viEd/x4qzabAlwd5bHOOARg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.2.1': - resolution: {integrity: sha512-QI5SEDY8cbiYWHx0VO4vIc3UlS6a32vXHjU8Qy/17adEmZIPuByJg13UEvo9c/UCiUkdcVWY83C+b+JrwnNyUg==} + '@rolldown/binding-linux-arm64-gnu@1.2.5': + resolution: {integrity: sha512-Y7eALiJ8lr0M2HH103Js+g7V34wf6snlpZLAsHI90uLhr3PVlNsbFVAXJC9d/V6BnPyKtpSwI+NcB/RLxsQxuA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.2.1': - resolution: {integrity: sha512-Sm41FyCeXqmYcERoYOCbGIL5hNfd8w9LQ7Y61Bev48HkcjaJqV/iiVOaiDxjVTRMS+QKrZmD8cfPt4uMVnvM+A==} + '@rolldown/binding-linux-arm64-musl@1.2.5': + resolution: {integrity: sha512-xMvZgnbZg4YVnR/AX2b3oOPDTFYJvUVaJg5FedA/LuvexAtXibZQej4cnTkw3rjsJ/ggUROB64TdtETiim+FYA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-ppc64-gnu@1.2.1': - resolution: {integrity: sha512-2x+WhXTGl9yJYPbltW/BSEPTVz9OIWQyER4N+gJEDWkkn904eRcBzELqh/Hf7K0w/ubGbKNMv0ZC+94QK/IFEg==} + '@rolldown/binding-linux-ppc64-gnu@1.2.5': + resolution: {integrity: sha512-GRjeqTUDHTo5GwntsLaAMcBahG3nlpjftXWZLN73HiYQlhwEowvarFgQnRnQZtIp4keXX7quXFbG38uPZBa2EA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.2.1': - resolution: {integrity: sha512-eEjmQpuRQayHPWWnywaWHkFT3ToPbP3RYy42VVd/B9aBGDA+Ol25EIWHxKQST3IiWJjikCWUF7KtbfqwZrzVwQ==} + '@rolldown/binding-linux-s390x-gnu@1.2.5': + resolution: {integrity: sha512-vLNTR45F2Uwc8AufkNXPmB4VliaXs+FvcheEogIzOXzO4l+LzieXF5A/TWxLy5HtqpsRCHUfd0lPVrrdgXdLHQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.2.1': - resolution: {integrity: sha512-/Orga1fZYkLc/56jBICcHrKchl8Z2UKdDSr3LG9ToWO1lQ6a4Livk9Xz+9WN91zsz5QR3XQz2NNoSDEvP6qadw==} + '@rolldown/binding-linux-x64-gnu@1.2.5': + resolution: {integrity: sha512-Mgj59/HTuYeK9Gz2MA+mBWKnHsAgkBSec15ZMb1st3oIfFbX7gCjOae7GydHhzcyQi9Z/7M1QuN9bR3oFqF0jQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.2.1': - resolution: {integrity: sha512-xxBJRL+0q0Kce7orznGWLuylHDY65vuARXZRpX+hPdv+DqK2c3NlCsVA98tlWzWNEE7yPqA/1NQ5nnCrj49Y5A==} + '@rolldown/binding-linux-x64-musl@1.2.5': + resolution: {integrity: sha512-mY8AP0/ichsbhAxGnLa3d3+MwV0EfgrPND2bplI3Ym8T6R2pJ0N87bvrKVwNXmdy3jnr6eQBecdqx/HMknBmpA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.2.1': - resolution: {integrity: sha512-M6AdXIXw3s+/8XpKMzdGDEXGS1S7kwUsy+rcTIUIOx5Ge4nXKCtAFHFV9YKkXvGcC5WMoTjAteLzlsQROVI0Yw==} + '@rolldown/binding-openharmony-arm64@1.2.5': + resolution: {integrity: sha512-8SLssA2oweAxyRgDp789ACfRb/3P+zNRJpzZxSizxF9m8NUDQ4+3xjo8ttjhVGGw6Qxb70oZiEtIjaKikCO7Yw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.2.1': - resolution: {integrity: sha512-/TX0SoRGojHzSAHpfVBbavRVSazg5U3h3Y3VXfcc0cdugq6kxdqw8LPGFiPr+/7gE/60zRcsOY2Vi9b9eT0jww==} - engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0} - - '@rolldown/binding-win32-arm64-msvc@1.2.1': - resolution: {integrity: sha512-EvRrivJieyHG+AO9lleZWgq+g0+S7oV2C51yuqlcyU/R9net+sI4Pj0F+lUoP2bEr6TWX3SqFaaS0SzfLxSzkw==} + '@rolldown/binding-win32-arm64-msvc@1.2.5': + resolution: {integrity: sha512-vGbruD5zquhoc8D9SViXgN2FBJtNdTyQ4DtG+SWiEGlJiAzoKcZ2xp+xuXCffhubVdt0NJlTZqkeRuERy7g8Cw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.2.1': - resolution: {integrity: sha512-Z4eCmn5QJ/5+azF9knpLWKfVd9aidn0mAe9TpJgvBLId9Ax3t0+JVxBmT25Bv7NBbVW1TZyKjQjQReouMeH5UQ==} + '@rolldown/binding-win32-x64-msvc@1.2.5': + resolution: {integrity: sha512-e/SXpgISz+IoqVcSSI0rx/d/he8zqLex+/rCWpnHpmVfmPIUjag9H6P7zotf0gJHwPUhQxZ/mF8tr6acebT9yw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -3256,146 +3269,146 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.62.4': - resolution: {integrity: sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg==} + '@rollup/rollup-android-arm-eabi@4.62.5': + resolution: {integrity: sha512-jfkGfTwhQpsiSckPF8r9bU3pn3vyd72NlWaO+TgEO6WPSDnUhXzrNYCHBMOYj0ACaUgjm6eERLF+XV9a6RstoA==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.62.4': - resolution: {integrity: sha512-JKuJc+pnpks2pjy7L/N3v/cAkZxYlnmuZoD840ldbMI5KDbC4iO9NKwPKYdjYFCMAIIlBzYSFHxIJVYzRo2/8A==} + '@rollup/rollup-android-arm64@4.62.5': + resolution: {integrity: sha512-oGVqyQlxnrz9/ty89oHpU857VUHEl5/Xu4R2lS+aivCTrNnSsbiENzTnNaBsjxH0CNWGPhzHArOLFwo+oKXveA==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.62.4': - resolution: {integrity: sha512-krw5uS2STmvJ02x0uTXHbqQNuz+9eZ1iw+qXk9dmW2gvV4jV7O2hEoOnuhFrpOPiel1mBFtqbxYZZtC46hXLOw==} + '@rollup/rollup-darwin-arm64@4.62.5': + resolution: {integrity: sha512-bW7B8xMEq8n99Q3ieEcPRGuphurdZAaFzQc9Efyyw3FL6DZO6pMy9xhdN+kBoD7Sy05xNXSr4OyPPnpkYriS/A==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.62.4': - resolution: {integrity: sha512-wsTxtgApb4PrOsNJIm0FZ1h3WvCC+k9uxLJ4ad75hgoS4NiRes2SoJFlDAyMwiUY8IssDqGcHbXuN0sx1tfF1A==} + '@rollup/rollup-darwin-x64@4.62.5': + resolution: {integrity: sha512-YSwBS86QeHOGlrxJ1PSOIZSkzRL/JmKeunhc+lV6M1a6En8QuVCD/T/qIA0J4Gd2Y86RIOBYrLcOUtqGh9+/1w==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.62.4': - resolution: {integrity: sha512-GUOnQlyZe3yAXhWOtOMsn5Qkrv5E5mZXa0thbARWi5Ei2szlVXJFQhddZ4HbAzh8q92w5twp+CQvs/eFanz9YQ==} + '@rollup/rollup-freebsd-arm64@4.62.5': + resolution: {integrity: sha512-2fST8lILgl7cKbme/1KDdPCmbXbG+gqoV3bHp19L0ypX/3akYMBVdOunPleRCwonoLnXOZ/0F+Mt/v8POFmfcQ==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.62.4': - resolution: {integrity: sha512-/Y7f3QuxjzPKsjA/rfEDa3+0vXqyjmJ50Ln8dPpCmWkKTrUoWHG1cWhTqaAMLob2m2nESWuC7yGrREz019Ztqg==} + '@rollup/rollup-freebsd-x64@4.62.5': + resolution: {integrity: sha512-cpIxQCP9J+EVad0a6LO1kY3ZGODlk80VlI+2I96B8xMcdHZ4pLVhfQ49JFpYqjPF91FFkQWftf57YlDcTiw9yQ==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.62.4': - resolution: {integrity: sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA==} + '@rollup/rollup-linux-arm-gnueabihf@4.62.5': + resolution: {integrity: sha512-r9fGh3eFs3e/udWh5ZjXQtxiYK/xoFxQaYR/cELxac/Udkl5Th+IsFm0CX3Kl9hmUH/we7EoMpjJgeQNnE0+IA==} cpu: [arm] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm-musleabihf@4.62.4': - resolution: {integrity: sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ==} + '@rollup/rollup-linux-arm-musleabihf@4.62.5': + resolution: {integrity: sha512-xdvFdp7OM6KLJviJT2g/YuRSUjnZgGHk4RNgwIbN7X6cPugOucV60DdHXWzsBVCUdrGb6qSXnJQrrAKMmQuj3Q==} cpu: [arm] os: [linux] libc: [musl] - '@rollup/rollup-linux-arm64-gnu@4.62.4': - resolution: {integrity: sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ==} + '@rollup/rollup-linux-arm64-gnu@4.62.5': + resolution: {integrity: sha512-rRqILAndyzHzP7T9NFQrq+4HFWNhqkqkKur7eiBpfLmz01PO0JKx5Vchu3YllE4YXI/Ftgq/szrDWg5GJ0mI8g==} cpu: [arm64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm64-musl@4.62.4': - resolution: {integrity: sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g==} + '@rollup/rollup-linux-arm64-musl@4.62.5': + resolution: {integrity: sha512-Gf4X3qVMucayUvux6aXXPgXovocSFUC0rrffDuPI/S2nHhNMhjcZxsrAFYCOF350PRreW1XwzFj3CT/3bKsWCw==} cpu: [arm64] os: [linux] libc: [musl] - '@rollup/rollup-linux-loong64-gnu@4.62.4': - resolution: {integrity: sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA==} + '@rollup/rollup-linux-loong64-gnu@4.62.5': + resolution: {integrity: sha512-+s5qA0TNM0qm8PK/a5gt/1Hpx+NV08uSuCncvhziIlQzT6AEV2fnUQo7eBtFTFO0nA9scauvoR2HusfXmQnO4w==} cpu: [loong64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-loong64-musl@4.62.4': - resolution: {integrity: sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ==} + '@rollup/rollup-linux-loong64-musl@4.62.5': + resolution: {integrity: sha512-ybb6QvWwWJCbBWqERpc8K3pYVGIrXlG8MEQ8IIuJY6Y9KdHQxoFoNyfkAOtKn1VHu3KuLidXvwrvGR1mEjeWCw==} cpu: [loong64] os: [linux] libc: [musl] - '@rollup/rollup-linux-ppc64-gnu@4.62.4': - resolution: {integrity: sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w==} + '@rollup/rollup-linux-ppc64-gnu@4.62.5': + resolution: {integrity: sha512-nZb1DtnOyhCmYvsC8A2CwOkopVg+IS1+fPUa7rMOAXtNw5+lLCLLPqd6XAiNrGtoQKsbvIBOwsHnBH/3wnb4HQ==} cpu: [ppc64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-ppc64-musl@4.62.4': - resolution: {integrity: sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w==} + '@rollup/rollup-linux-ppc64-musl@4.62.5': + resolution: {integrity: sha512-yMbj63Sp89ryrXLWyz+sy+fYD2HpOnMCLGbe4Oa1smclFSUukdtD/BgdiHaAetJNb74URD8U4hM+qG5KVzMEkg==} cpu: [ppc64] os: [linux] libc: [musl] - '@rollup/rollup-linux-riscv64-gnu@4.62.4': - resolution: {integrity: sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ==} + '@rollup/rollup-linux-riscv64-gnu@4.62.5': + resolution: {integrity: sha512-mhoan3OJw2kYV/e1jtIdmvUZgyBFeA6zGWsOswmR0Tg19TQbowZuR+JMLID6spbbBN7Zee2ejrgmy3+FxGrIdA==} cpu: [riscv64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-riscv64-musl@4.62.4': - resolution: {integrity: sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA==} + '@rollup/rollup-linux-riscv64-musl@4.62.5': + resolution: {integrity: sha512-5ZTLmjWbb1VZdjuyhe83K/8QO0/h11midQCBP+X5OYn32ra7eOBoM0ZqtaY4nkgNsYgmdVhMYPoyVPTjUpHf3w==} cpu: [riscv64] os: [linux] libc: [musl] - '@rollup/rollup-linux-s390x-gnu@4.62.4': - resolution: {integrity: sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA==} + '@rollup/rollup-linux-s390x-gnu@4.62.5': + resolution: {integrity: sha512-m53kG+br6PGxOTmgBEM2DHSDs9RVjsyEbUwjJPJGTFm1grWOG8EKJggDCTb60unD4Tjby8fi7/m9XfkEWasVWg==} cpu: [s390x] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.62.4': - resolution: {integrity: sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw==} + '@rollup/rollup-linux-x64-gnu@4.62.5': + resolution: {integrity: sha512-6RHPJR1g/uvdYU8uXBnfq3nlqyZCP82Fr6NHgfGoaIeSh0YEqnX/x6uA9MmJJbnSH7swqX4F+CkGdUF+6doiQA==} cpu: [x64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-musl@4.62.4': - resolution: {integrity: sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA==} + '@rollup/rollup-linux-x64-musl@4.62.5': + resolution: {integrity: sha512-xs+OXQtEXgpXT0DmA5+U3qnRZHdCST/5HRQxS8wSPZTUZN/EMWeHuSIod32LQklTBZBV9DyfncKBQ8n5V3eFdw==} cpu: [x64] os: [linux] libc: [musl] - '@rollup/rollup-openbsd-x64@4.62.4': - resolution: {integrity: sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ==} + '@rollup/rollup-openbsd-x64@4.62.5': + resolution: {integrity: sha512-e7hD+sl3s+mcLQDZ8pbudBVsdG6r5yN4w3LqG2TJ8sQHDpblWj5lrJs/3m01Cvlxbt4x13zu5thLjgypgtkYzw==} cpu: [x64] os: [openbsd] - '@rollup/rollup-openharmony-arm64@4.62.4': - resolution: {integrity: sha512-s62SQ/vgsRSvMwDkOEfTqfgASF0f26ZNaQuTA6Aok5lrikf89yI2W0gFHvZb2Jpgc6N8JnOKZgCK2iciO3CsxQ==} + '@rollup/rollup-openharmony-arm64@4.62.5': + resolution: {integrity: sha512-GiyJaCf+WpMub/17aPcKk27QMl5W6f+KhdPTjlFOn5akH5Wa/DCM9Stdx5cDfmasyKB08MqpVQ1uJE2RkkpbXg==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.62.4': - resolution: {integrity: sha512-J6wGf8TVGbXJq+HH+ttTvrcfNKPbuZecV6KT1B8I18BC5IURUh5kl4Yl5OEP5eFIUoI5BWxCsyYMhFsDx8kekw==} + '@rollup/rollup-win32-arm64-msvc@4.62.5': + resolution: {integrity: sha512-+OQ8U2DdoEfXl8T4Fb18AjmEwbXMerKDKCL8yCPAYhKCEEKoul7rkbeGCBFCbAlaGaa7pmtRTpkAJM2LE/i5FA==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.62.4': - resolution: {integrity: sha512-zmfrQd/0wu6oJs8Vq8KwY/YtsKSsLtKe/HwAP4Wqy8LhWjeT55fHRAkOhYQ12wI3ayS4Tt12d5CDRD7N96SAYQ==} + '@rollup/rollup-win32-ia32-msvc@4.62.5': + resolution: {integrity: sha512-KanvAZrPKbDBFwrgiU9yEVpQoox9QPV1WZOXX7HudJQY+eSlu82CtWxDU8WtuRRvtN5EGkLczkd6Y6DTcvm9wA==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.62.4': - resolution: {integrity: sha512-qPzHqdj9rfUD+w79dtE07zi/kFwKyCJqplp5K5ygeLTp7jLpAoc16OAH39HSmRC9UpozaecsleI8uAdEj6v2yw==} + '@rollup/rollup-win32-x64-gnu@4.62.5': + resolution: {integrity: sha512-1aC3UEWTtRl3RK3VpDJ/Tqk1XI4SLTmXIthAq6wRWo8XiSXJNd+VprJM4/1P4+i6HIaFEFlVi9sTTziniD2tOQ==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.62.4': - resolution: {integrity: sha512-zD6NdeWEByGE9QF9vCrlJ5YQB4oq9q91kPZS37Jwj5hOkvR1lTBSpsKhKDw4IJtbQ35LsTS1HD9DZYGKIshU1Q==} + '@rollup/rollup-win32-x64-msvc@4.62.5': + resolution: {integrity: sha512-/gDJaRs4gl0NPIwqCz+6PkpmhhjRAD2j6P4rSNHBzUkO3naEx2mIU0pRle1vUNRQ7mE/+8OOeXLTv/J56FKiQg==} cpu: [x64] os: [win32] - '@rollup/wasm-node@4.62.4': - resolution: {integrity: sha512-GJFM5d4k5dSoY/oyILsKArplGvkss1DOURyXsl6IEM/X9RV3bQwqu9GY6tYfA97ZwIejfAguHa9fo1pGOQcyDg==} + '@rollup/wasm-node@4.62.5': + resolution: {integrity: sha512-NSyG08F0m74qhvWDyFeYaZapAmieh3bEiQzCzql3N9ErlYoyt+V3xpDX8dBdKiZu1UXUpGNbBEblKaW5zcpqaw==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -3434,9 +3447,6 @@ packages: peerDependencies: eslint: '>=7.7.0' - '@tybys/wasm-util@0.10.3': - resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} - '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -3506,9 +3516,6 @@ packages: '@types/gensync@1.0.5': resolution: {integrity: sha512-MbsRCT7mTikHwKZ0X+LVUTLRrZZRLipTuXEO9qOYO+zmjMVk81axyClMROf6uoPD9MRVu46bx8zoR0Ad9q3NAg==} - '@types/graceful-fs@4.1.9': - resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} - '@types/http-cache-semantics@4.2.0': resolution: {integrity: sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==} @@ -3584,9 +3591,6 @@ packages: '@types/retry@0.12.0': resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} - '@types/semver@7.7.1': - resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} - '@types/semver@7.8.0': resolution: {integrity: sha512-1mAINjtQCXXeLkJ9ehXkwOcBpqtLxiVtKhpUf83DdRNdQKV0iXZpaHYqRr7nj+wvxuJzoAmAwXI+sCNMv1CzLQ==} @@ -3608,9 +3612,6 @@ packages: '@types/urijs@1.19.26': resolution: {integrity: sha512-wkXrVzX5yoqLnndOwFsieJA7oKM8cNkOKJtf/3vVGSUFkWDKZvFHpIl9Pvqb/T9UsawBBFMTTD8xu7sK5MWuvg==} - '@types/watchpack@2.4.5': - resolution: {integrity: sha512-8CarnGOIYYRL342jwQyHrGwz4vCD3y5uwwYmzQVzT2Z24DqSd6wwBva6m0eNJX4S5pVmrx9xUEbOsOoqBVhWsg==} - '@types/which@3.0.4': resolution: {integrity: sha512-liyfuo/106JdlgSchJzXEQCVArk0CvevqPote8F8HgWgJ3dRCcTHgJIsLDuee0kxk/mhbInzIZk3QWSZJ8R+2w==} @@ -3626,138 +3627,138 @@ packages: '@types/yarnpkg__lockfile@1.1.9': resolution: {integrity: sha512-GD4Fk15UoP5NLCNor51YdfL9MSdldKCqOC9EssrRw3HVfar9wUZ5y8Lfnp+qVD6hIinLr8ygklDYnmlnlQo12Q==} - '@typescript-eslint/eslint-plugin@8.65.0': - resolution: {integrity: sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==} + '@typescript-eslint/eslint-plugin@8.67.0': + resolution: {integrity: sha512-Un7Heoyj65NREbKAyIrFxeM143NZpExWmy1Nep4DLeQOeLlTeumPjoNKnBrU5D5moWXbPJgRa5Uwcdu0faVNGQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.65.0 + '@typescript-eslint/parser': ^8.67.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.65.0': - resolution: {integrity: sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==} + '@typescript-eslint/parser@8.67.0': + resolution: {integrity: sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.65.0': - resolution: {integrity: sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==} + '@typescript-eslint/project-service@8.67.0': + resolution: {integrity: sha512-cvE8c7ulYeXN9fYuszhCeCsbzyVEXuhrRCybnBre7TUmqb5nRmBfQAwCj0O3WJFDeyAZt4VYv51vMCC9LHSdYw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.65.0': - resolution: {integrity: sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==} + '@typescript-eslint/scope-manager@8.67.0': + resolution: {integrity: sha512-EgvsleTwS4E+WzzSvem8fAUubLwatMNF1B5hHSLQxcvs7q2dtRhGyujHwLJSYlG41niJ7GP24Aha2+0mb1b2kg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.65.0': - resolution: {integrity: sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==} + '@typescript-eslint/tsconfig-utils@8.67.0': + resolution: {integrity: sha512-vV+LUSv5njUWsknE71fqKTlXUva+R76SaeORd6Zojcunk/6DvKFXONU3BrAs2H49mbygUXt6gbYunzwqNwlhdg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.65.0': - resolution: {integrity: sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==} + '@typescript-eslint/type-utils@8.67.0': + resolution: {integrity: sha512-aVWDXbRmdXO9siTfX4ditQI1T9+zVcNazT48EJCD0v40/9RIFoUgZ05CmGEq9H2gixRpjUn/iplwvlcvutJW/Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.65.0': - resolution: {integrity: sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==} + '@typescript-eslint/types@8.67.0': + resolution: {integrity: sha512-sBtgslww8nsMYUjhdPBiSyUqSzT8uR6g93A2QXnQC8+cGdjz0CyaOdqHDRJb1AtORbZCNUJBBeFA/tNR2uQmww==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.65.0': - resolution: {integrity: sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==} + '@typescript-eslint/typescript-estree@8.67.0': + resolution: {integrity: sha512-EKQBCE9yNlRJYm7jdTW5AhDacDUmSwQb0FAJAmK2EKYrNXIsa2vxcSZx6PvJ/dEdI6lS+Y9W+EXckLj0iPFGcw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.65.0': - resolution: {integrity: sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==} + '@typescript-eslint/utils@8.67.0': + resolution: {integrity: sha512-U9D1FdwEWBwok3hxxSdhclMb0twvt9QnjIQ0VfQ1AiX2epnpSgv2ubVDsayOFyY8K6FX+AQ7E0FKWVG3iKsj1A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.65.0': - resolution: {integrity: sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==} + '@typescript-eslint/visitor-keys@8.67.0': + resolution: {integrity: sha512-fkv8dHRDqfGtTHuJeebdrQ7cX6Ad4WAS00rgHh9UGvMycF1mjBfsxry1XsLIFhWZ6Judlh6UdzK+TYlbpCXgnA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@verdaccio/auth@8.1.1': - resolution: {integrity: sha512-mIhE2uFWrUhknk1/bdBcZJGHcRrV13lrIBk0n8Hosdh5A+Cbqh3ctdmSyTske93eahBZFHzEnwSo0Zcg3mnK+A==} + '@verdaccio/auth@8.1.2': + resolution: {integrity: sha512-ts5k0Z7kkxOKTVg9U8gVIFxmvytqIEl/eU2zTD/w0CrBT3vkEzvwQ9K0kHDV4VR1dCWhX1nqJtIlqtWewZQr8A==} engines: {node: '>=22'} - '@verdaccio/config@8.2.1': - resolution: {integrity: sha512-6mT3OY0uS0FQ6QuxBZagm+iomj2M7/cN2GveN/V4AvigmSwDLcoVl7xjYzaW35E6jmYY+7SRUaJTjWO9XQmK9A==} + '@verdaccio/config@8.2.2': + resolution: {integrity: sha512-1rLGFg/qTRgHVq1llx5fW+FtYijV4MeXBRc82aSy2TUQ4fy+4yEckI1+GCAm7fIvl5+YOzW9PcABtZOeEkeu7Q==} engines: {node: '>=22'} - '@verdaccio/core@8.2.1': - resolution: {integrity: sha512-fcK9lGTXSxrNncbShtt5sCfFPiP2GdsQmhXdeu4HDIH9GU8cLF5Jj/QBpLjMuz+lB+qHg6QBFlUKS0pnio20jA==} + '@verdaccio/core@8.2.2': + resolution: {integrity: sha512-BSCCR5cFkepjL3HnosKUi6aRezwgHu1FihFtes63yxIiDXkMclVi5IekTH6SCeW/7MO1Uyp58kh1updbB6VW4w==} engines: {node: '>=22'} '@verdaccio/file-locking@13.1.0': resolution: {integrity: sha512-rGFfdyCZdgpbkROJJfjOA01R5BFtzrnDAhNIkxxc/yWZ6Cir+MRHpEbN2weOEPAwYDke9Wms4JKhpsStu+iV8Q==} engines: {node: '>=22'} - '@verdaccio/hooks@8.1.2': - resolution: {integrity: sha512-i8Ppd1ZycpT72aZ/FwrHXHvDFVv/rZ4jQeru7p0MNRZoaEqZX9yt6nuwMiYAOOyS0N32RIH8R0zlpqet7oMXFA==} + '@verdaccio/hooks@8.1.3': + resolution: {integrity: sha512-aCdLrzZAZB2zLmqImZQsQOK+7gIJsPv0cy4O34SenOJJ2kWESbacQVwcDGjv+EWIjbQy0EPCjgjqwryIkvdy8g==} engines: {node: '>=22'} - '@verdaccio/loaders@8.1.1': - resolution: {integrity: sha512-lj1RUBzmrQL0vOIADPXw6dnrHohJljI6w6hJCzWOGrc8vP8j1InQS9QhTEewtfOoruP44fjVNEkWe97nK/IGnQ==} + '@verdaccio/loaders@8.1.2': + resolution: {integrity: sha512-3NrHUaspgJUmTbKF4jQeHOo22zfa7XsYL5MD+PbviLg42OxERHXiuLBIBf2FE9KU3dhcuB+5WZqOLfS2U7WRSA==} engines: {node: '>=22'} - '@verdaccio/local-storage-legacy@11.4.1': - resolution: {integrity: sha512-DZzUIpFbYa10WMKm05eQb4KKReoyMsQknKjaP7O9T9UfmhWMQ9qOBEwkHdUpNe0z3Bq9je4MoLL5yrTs1420TA==} + '@verdaccio/local-storage-legacy@11.4.2': + resolution: {integrity: sha512-ay/Hs6itET+8YKHDUds7UWlyTpLMDmsmtKCKF67obF2fwPd9RRoiLFWiVyY0H5gsqSiEGOpOMw5JlFRYnf+row==} engines: {node: '>=22'} - '@verdaccio/logger-commons@8.1.1': - resolution: {integrity: sha512-TCf+R3WOVxFKqvCmaxcZNchmz8GyFrIoIeO3nWL0gpTbmrKhWvCWXI+iYZkAJQhqBE6CMzisQZ5/UDzl+HQHnQ==} + '@verdaccio/logger-commons@8.1.2': + resolution: {integrity: sha512-vw6QuRS2c6NmsJvjfZjHik8bTyqj9i2kAlJHSoDwPAlQ/njyfjmI9Sa+yoz5fKJX4aGA/LoIqHSYEW2Sf+5rjw==} engines: {node: '>=22'} '@verdaccio/logger-prettify@8.1.0': resolution: {integrity: sha512-Mriivx1LPx8/8ux0MlNoj/FtxQiQmf5BG2casfWEf9R7jzBDAUCqQJUR4s0PaV2Mlc9cdHcETOucx3RGPuWHeQ==} engines: {node: '>=22'} - '@verdaccio/logger@8.1.1': - resolution: {integrity: sha512-YOcM1niOSlnlCN7XuO2kLN4WE7hlONsuCUWGvQsld/niDFar24JaZ/+hR+WntERu5g39iPLBAzYMeXmIOIP0sg==} + '@verdaccio/logger@8.1.2': + resolution: {integrity: sha512-9FDPtTNH4b1ZBW2jXGk+5ud+omkKZiBRAUGumDX99R3+vW6HGqVjo0DjwmBP0sGxseLdh/q2S7A2Z91EvUpgYw==} engines: {node: '>=22'} - '@verdaccio/middleware@8.1.1': - resolution: {integrity: sha512-S2gK6F2h4KWJUpGWd7pyxkrbTuTWSSTqtXI55wPl5JIPzvlcS2Yls8ye3upmGb9eJuMzWkkwAG+61Vo6fdwLzA==} + '@verdaccio/middleware@8.1.2': + resolution: {integrity: sha512-JIMDFIkEVP7py9QxW7vBy1N2/PUMESE0SJ3tZGHtEVzUCCsUmpby7vuEDJ3STk6yzkqtqk2lTWYAcUbqwI/7sQ==} engines: {node: '>=22'} - '@verdaccio/package-filter@13.1.1': - resolution: {integrity: sha512-bFOlWOnTbi7/6p0mpqLLrceNoi+4XCNpgWtnhYDMbh9i5aenxRxE4jKl+bGIbrNzOzQSNJy3zVnOGdoxYMvpjg==} + '@verdaccio/package-filter@13.2.0': + resolution: {integrity: sha512-RrauvQMklcJMiV9iuklhBj7+K+R8w99nzlQbLoY/GqAvGdLZ3VRDWR3e/81Fcdy0fyL7E5NI7i8aA9iNZsm/LA==} engines: {node: '>=22'} '@verdaccio/search-indexer@8.1.0': resolution: {integrity: sha512-N0vHWnSCZVEU3ffvbH/g4cRvGkXO+FJQcNggdY1wxT8LP7K6NJ0F6aq7jSF3ebW5IokdQMJuqWTNx18h8dgrsg==} engines: {node: '>=22'} - '@verdaccio/signature@8.1.1': - resolution: {integrity: sha512-N7WpRriTzXjlWKNX9FpcHMEi+vysRXV8r02ymKfR95yVLaFiWyl8mu7X78qCulKjmAba2C51MZLrlRosED7rEw==} + '@verdaccio/signature@8.1.2': + resolution: {integrity: sha512-aa4FUz7f4Bj241PFZnI69ofZIcgJg1AHfkB4D8GG49rgwahpuEO9E81I0m46bqpe2gWAGib57KYkb9NOYCg/ig==} engines: {node: '>=22'} '@verdaccio/streams@10.3.0': resolution: {integrity: sha512-MARQAzAgS42GIawkrBVMTV81vRf2yqZmwonnQb9FWeGj+tsMcgpT/f2fWMQm/UubLpWxBBH6oCkmhFd29R2xGA==} engines: {node: '>=22', npm: '>=5'} - '@verdaccio/tarball@13.1.1': - resolution: {integrity: sha512-p8S7HcS7z8jq5+7eKW5y0mD3M1dcyhi86f+23mA0Re91ursyggrU7l5/5dFdKQfN3LNdQGaj/FC5UKmRtU6dlw==} + '@verdaccio/tarball@13.1.2': + resolution: {integrity: sha512-0xk52gcW776safIt+FLUTuv2wDb0zd4f4DxOYMMW4+5CRwa6QiiolLz4ImBWrC4O4WnZpbQ0mzLDyFaLd48LkQ==} engines: {node: '>=22'} - '@verdaccio/ui-theme@9.0.0-next-9.23': - resolution: {integrity: sha512-l4tUIT+uZ+t/YTG5Tqm7h/WF+J0KeE7duL9VHc7kjrax4TV4PdoBwlTEKWgo8MbGLLijtpwV52MIMmzZQhGhGg==} + '@verdaccio/ui-theme@9.0.0-next-9.26': + resolution: {integrity: sha512-qF5q16SOhmQhYWZoq+q5QW3yxzEQ7knSXW6N7hJKjFf/5FD+RcTHMALXH08iGxk6fHuuNKUq6KR9uRkHUNF9Yg==} - '@verdaccio/url@13.1.1': - resolution: {integrity: sha512-Vlq6ilfrraBzRIcfrgYHveKIi5j6Rpi33D6eTYVHMMXaEBqo3cId+hfOa+S1WH2eLSAIBt4tKiPcwi5Z4HEM/Q==} + '@verdaccio/url@13.1.2': + resolution: {integrity: sha512-AvaMN3BJnHk9zDDL5oT+VOAIoUoXqoOEWVe3hsfa8T/IDLcl6nyYJd75Gt4U0huOXLdCpOwjVUl4zpxOvvThZg==} engines: {node: '>=22'} - '@verdaccio/utils@8.2.1': - resolution: {integrity: sha512-Yen0yN7iraA3AGyAEPonC4k7pMWQuFYAhjMW+DXa57Q1zoYXq5Lk1cXwdNl6irF7EgKP+5Sz6bMUxQIWQELngQ==} + '@verdaccio/utils@8.2.2': + resolution: {integrity: sha512-RWS2h1yXeaF7txXD7jK/v1aecMrTCGFGRMDH3qjcvM0FMXYs5rleGo/MooiXHuOGFC4D7xNqt3hERXQweNlf2Q==} engines: {node: '>=22'} '@vitejs/plugin-basic-ssl@2.3.0': @@ -3766,20 +3767,20 @@ packages: peerDependencies: vite: ^6.0.0 || ^7.0.0 || ^8.0.0 - '@vitest/coverage-v8@4.1.10': - resolution: {integrity: sha512-IM49HmthevbgAO4anp1hwtoT9wYe59w0LR00gr+eagHE+ZJ5lK4sLPeO0ubgoJcwLk6dehU3R24N+FbEEKDc8g==} + '@vitest/coverage-v8@4.1.11': + resolution: {integrity: sha512-8MVGEFnJIcdGjcbfKmeq8z0pZHH0JlVtoVZH9Q/qwUp6wyFnEJUBMrw9DCaj+ra3vShGmhavjalMIhPNxZAUcw==} peerDependencies: - '@vitest/browser': 4.1.10 - vitest: 4.1.10 + '@vitest/browser': 4.1.11 + vitest: 4.1.11 peerDependenciesMeta: '@vitest/browser': optional: true - '@vitest/expect@4.1.10': - resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==} + '@vitest/expect@4.1.11': + resolution: {integrity: sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==} - '@vitest/mocker@4.1.10': - resolution: {integrity: sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==} + '@vitest/mocker@4.1.11': + resolution: {integrity: sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -3789,20 +3790,20 @@ packages: vite: optional: true - '@vitest/pretty-format@4.1.10': - resolution: {integrity: sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==} + '@vitest/pretty-format@4.1.11': + resolution: {integrity: sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==} - '@vitest/runner@4.1.10': - resolution: {integrity: sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==} + '@vitest/runner@4.1.11': + resolution: {integrity: sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==} - '@vitest/snapshot@4.1.10': - resolution: {integrity: sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==} + '@vitest/snapshot@4.1.11': + resolution: {integrity: sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==} - '@vitest/spy@4.1.10': - resolution: {integrity: sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==} + '@vitest/spy@4.1.11': + resolution: {integrity: sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==} - '@vitest/utils@4.1.10': - resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==} + '@vitest/utils@4.1.11': + resolution: {integrity: sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==} '@webassemblyjs/ast@1.14.1': resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} @@ -3849,8 +3850,8 @@ packages: '@webassemblyjs/wast-printer@1.14.1': resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} - '@xmldom/xmldom@0.8.13': - resolution: {integrity: sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==} + '@xmldom/xmldom@0.8.15': + resolution: {integrity: sha512-/5NV/vDALVFDXgLmfsy9TRCBlKwO2LNBFzpzvb9iIj+jR+eSc6DLYYvVOdivT/jm7MtU6TebYuRmzEOI7w40UA==} engines: {node: '>=10.0.0'} '@xtuc/ieee754@1.2.0': @@ -3862,140 +3863,140 @@ packages: '@yarnpkg/lockfile@1.1.0': resolution: {integrity: sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==} - '@yuku-codegen/binding-android-arm64@0.8.3': - resolution: {integrity: sha512-/EKnnqwvN7xYoVDhQEIEJTdPDwGW1wkFz/2Eku3ES/IJd4lcQh/OaIDFBmoJKvpe12enrb1TIoYh1fxasGXolA==} + '@yuku-codegen/binding-android-arm64@0.8.7': + resolution: {integrity: sha512-C/0zV5IhgVdYhGJTwrY0v8dknxlhiKwtVJkMUaexu9/QvRmzlV4vfU3hZlUSgqc2BxQHntL1mCVbDq8j0FRFDw==} cpu: [arm64] os: [android] - '@yuku-codegen/binding-darwin-arm64@0.8.3': - resolution: {integrity: sha512-DFAOliF5YIPv3ayNHGOJhIun6Af4kMaL/YXxf8ZtD1qrOIMFnX/AQBhwfvLalhwmmxuGA8AUteaKRHBvdKZFVA==} + '@yuku-codegen/binding-darwin-arm64@0.8.7': + resolution: {integrity: sha512-/u+REDMI4a0/lsJXTM4c53/w31OGZLOReZIyg62uhgLs0kc8NHsj/nOcxTdlQjq5gi0zhdkccD9LaLTXcdzPvw==} cpu: [arm64] os: [darwin] - '@yuku-codegen/binding-darwin-x64@0.8.3': - resolution: {integrity: sha512-WlMh4/oEibaTzE9j5Zq8qnsrH4Ii4kWdcDv/Pj2Rb/MYSrKghtg+bxbWpPe/6zJD21p9zZBApQUxl8ECpZOJuQ==} + '@yuku-codegen/binding-darwin-x64@0.8.7': + resolution: {integrity: sha512-sMMzFOwCo4WXR+/6zIBThOocSC50iIIZZdfiIDbaLvj0Ax/rWt/iavyfEAqajyvzydLyCqR/ZItdLWSRlu1umw==} cpu: [x64] os: [darwin] - '@yuku-codegen/binding-freebsd-x64@0.8.3': - resolution: {integrity: sha512-hoDOpPP0FTxPSD+6w0Gs4p8iL1yXe6jjIXcdzNxyT1KE6B3JI6O0gTIWQISJ+8QyNpNjIwBb7nHCdRavktJM6A==} + '@yuku-codegen/binding-freebsd-x64@0.8.7': + resolution: {integrity: sha512-MpdpKXix9P+Y1rKgjvcNeNtGjXeL1CmttNhYINrWls8kRpm4xM/oBGTmn6w7to8lAlwj5jm8q03dQPl5mRv4Qw==} cpu: [x64] os: [freebsd] - '@yuku-codegen/binding-linux-arm-gnu@0.8.3': - resolution: {integrity: sha512-nNW0GGMJyF04pK4A7Kq7WAYtUWU9uI5ugDAoXl9yHpd3IIZ8UI+zFlM01e+ZGWnQcdxYYLumeRe/EjzZT9bVfQ==} + '@yuku-codegen/binding-linux-arm-gnu@0.8.7': + resolution: {integrity: sha512-rr1srFLlPAmC1vtxfc9C1YLDe3iH09YjfSeeIidBqKhzx1MATjOAq4mjlRUOnhr/L27MotWIYOFKwVsd5JZFOg==} cpu: [arm] os: [linux] libc: [glibc] - '@yuku-codegen/binding-linux-arm-musl@0.8.3': - resolution: {integrity: sha512-/jpxKhO8AV5TmXgT3R2Gv3YctKRUhyDzd5bQw8TiJ3O4z7qerHzoW2kE40fPAO3L434/IZtZbdhr8HuOqiwECA==} + '@yuku-codegen/binding-linux-arm-musl@0.8.7': + resolution: {integrity: sha512-eAufXh8qBRpiSO6ueaMDL+yyoXIGLhpUce72YbcACtZU2qhExwBIJyEtQf5kH2Ki0X2aqkSjSfog4OPtKXan3Q==} cpu: [arm] os: [linux] libc: [musl] - '@yuku-codegen/binding-linux-arm64-gnu@0.8.3': - resolution: {integrity: sha512-CYhLJfnCknabfLvUjsanxC5s3BBtZHUwfzdDL7GcqShIRQh2qqgG7pPfFrFJ6Jp56kkjKXkfluFGn9nnIv0nZg==} + '@yuku-codegen/binding-linux-arm64-gnu@0.8.7': + resolution: {integrity: sha512-gw4w6wPoHObBrdIC4duVWLmJOvpdE25j5D7yrM5mACNlK4klRz/lv8hK+ssQk9EJHBgZjSaqZIJVFgqNYbfv7A==} cpu: [arm64] os: [linux] libc: [glibc] - '@yuku-codegen/binding-linux-arm64-musl@0.8.3': - resolution: {integrity: sha512-c6gEdnI0MgA7/rVw6CACMciSbAcxVwLyD/jSBbMLWUeqqbysCNGrGPAHdpSaadpz3W1bd+OdXt9XWjfm66708w==} + '@yuku-codegen/binding-linux-arm64-musl@0.8.7': + resolution: {integrity: sha512-L68N6Y4XkqcIaKo3Ra88JEvBEH4AHff44A4INcrxeVWZ8CZtu2tCpfVxe3hR8qQQMcvBSQlDn24KpkCKEhVvfA==} cpu: [arm64] os: [linux] libc: [musl] - '@yuku-codegen/binding-linux-x64-gnu@0.8.3': - resolution: {integrity: sha512-CRVZ9Rw5lIah/PpWeShWv7XiUCMY15N6rZRA2sEZrQvc5Az7Dv9/wsDMa6oBMkfQLXuDkFo4G1QOYyWbebjejg==} + '@yuku-codegen/binding-linux-x64-gnu@0.8.7': + resolution: {integrity: sha512-dzyAbltJmf3Cqlb8HcFuYIf5Yn0fl1vTr3XJ9HiVNNvOlhqPSArOqtw9vI1p6/VTXTjrMLXlU+s+/kNHiIy/Cw==} cpu: [x64] os: [linux] libc: [glibc] - '@yuku-codegen/binding-linux-x64-musl@0.8.3': - resolution: {integrity: sha512-G12Nhecjmv7OlbCX6Y4HU4wYYePd111kTE+yTjbitnt+P3m8bNegtYG4ZGo4scGTq8cKsLF4xcda1XNzCUA6nQ==} + '@yuku-codegen/binding-linux-x64-musl@0.8.7': + resolution: {integrity: sha512-Otw4MH3404q0Bbvl+YTdW9aoUV5vXmUw8260bWvt1XlaoIX/ceSgI4ygheRDMfBPoHt6FDayQaO9OLVUZAgkFA==} cpu: [x64] os: [linux] libc: [musl] - '@yuku-codegen/binding-win32-arm64@0.8.3': - resolution: {integrity: sha512-i8bpXWaMlik9DvFl+89emEx3RZFtSd21Vlt0UrnPvUC7h8NGElP2SwQcdcG+pPmihFIYJAoIuJLw7YdQcFcDkA==} + '@yuku-codegen/binding-win32-arm64@0.8.7': + resolution: {integrity: sha512-qo/jyrzryiBuKEsFiuWaBCBe3tRMynQ0qFWFgOEjcCMQeZfBm+wKiVEUEFXXLc7bh8YezguAWp0Mtnhq4ARNyA==} cpu: [arm64] os: [win32] - '@yuku-codegen/binding-win32-x64@0.8.3': - resolution: {integrity: sha512-vlYymeTSsx+qxZoNvdl6KehgYDaQC4Sk/9KUnM3V2mriyCwSdhW7lqdpQGl+RLGsDTxyuRGjzGIjgRWk3lohmA==} + '@yuku-codegen/binding-win32-x64@0.8.7': + resolution: {integrity: sha512-D5lDsVDx6m00E6bWySlWdH72Ca4TPSaphDqB6QjU6MpuNLIJqoGoatYyq2rOmBE8Zv/kunot/o58KGL03P3eiA==} cpu: [x64] os: [win32] - '@yuku-parser/binding-android-arm64@0.8.3': - resolution: {integrity: sha512-vySYRsMeul9ssvxeHdxgS9ZUIcq7gqljWNqgokjJE0uQWvVvOprihJ6hOsiifVqWsla0BMc3vAFBvNS9QqCw7g==} + '@yuku-parser/binding-android-arm64@0.8.7': + resolution: {integrity: sha512-eGKYiUDX7Y0V7tDTmg+JTVnXnjMqfXXsorZ+EDf5kxwchQ3Or1HS14MzI2fw+jFhHR85fCWt+mtX33Yao73hIQ==} cpu: [arm64] os: [android] - '@yuku-parser/binding-darwin-arm64@0.8.3': - resolution: {integrity: sha512-+wpB/wqhiZ685Y77I+lj6v9pHSAJ3Y+QMHJmvch0Q0ahIMbNwtKk3s54MhtjCMKO1qpjPbyN/PjuHDg2hbKaVQ==} + '@yuku-parser/binding-darwin-arm64@0.8.7': + resolution: {integrity: sha512-Re0RHelKLnjEURulY2/KxW+Ngb8zuNA4BRZuMwgGQNzVumT6u4U2N2hc01oeYVNVof0i7GrXE4UCNBgbpRRnjQ==} cpu: [arm64] os: [darwin] - '@yuku-parser/binding-darwin-x64@0.8.3': - resolution: {integrity: sha512-jKqiWejj4zVy7pPtEGu4/Ty+pG1h7ooQOXIkm7shKZTSwTU9X8X+eoH11uIeKHZi2SQWV0GhNz0J56eerseysQ==} + '@yuku-parser/binding-darwin-x64@0.8.7': + resolution: {integrity: sha512-Hn8DROtQkjlA1ACbPgj4a7eP9IuVOI504oiTwpkWPbpaDWD9KdmnVYCqW+1LfenNK/g7O9NhWGpXEdaCNX7lIA==} cpu: [x64] os: [darwin] - '@yuku-parser/binding-freebsd-x64@0.8.3': - resolution: {integrity: sha512-FC7zSwzFzd4z9bsId07CiHLR+Iw6yW/LzIQhL5AUtPUuVXLgEyx0rilgbRUYkl1CT3GJcLpkh63WuPZUSgCDzw==} + '@yuku-parser/binding-freebsd-x64@0.8.7': + resolution: {integrity: sha512-bAP2OV8wRuzplX/jYxv9+vvqQT8JxyNphI8fLfXGL054Xs+4/J5u33cIm3y4rxY8rdoLmmdiJs2Tq7r7lrDRfA==} cpu: [x64] os: [freebsd] - '@yuku-parser/binding-linux-arm-gnu@0.8.3': - resolution: {integrity: sha512-So61j88b9/ygDnUPlWCm1EUPw4HSxAyDjrNHKgud5N3aRDQ3kw94nW7TriXbo7GBXID9oBHCMNm1r1Fof/Df5Q==} + '@yuku-parser/binding-linux-arm-gnu@0.8.7': + resolution: {integrity: sha512-kTYwJQQgmZeAWdDIWabiReIZMpmfLueIj1tCmjStUtFGhR1Z0qwxonKVfUC4N7h/VhGGzLZ//7O1kgt1QKqgCg==} cpu: [arm] os: [linux] libc: [glibc] - '@yuku-parser/binding-linux-arm-musl@0.8.3': - resolution: {integrity: sha512-Nmnn20yJvSSKL8ZdtqReBRSGCDkSMqR5jEk/Sk/cdIdZmqVD49Z6M7w2GbMjdrxMI1MBPbsWFMMWxa93cd5t5g==} + '@yuku-parser/binding-linux-arm-musl@0.8.7': + resolution: {integrity: sha512-uL4jE8HPT2BLlxAXyD10LqgPuXa9eDa0BKpCdSANmzIJghq/2eZo3/gQNtaxPZMupWoxjYzSad9IXrwu7aYPXQ==} cpu: [arm] os: [linux] libc: [musl] - '@yuku-parser/binding-linux-arm64-gnu@0.8.3': - resolution: {integrity: sha512-Lfgw7AXJ0rxu6BMPGgfc8HLJWEIr8BHhCzcQp/75k+NM90uCLkHlBNqIg/K42KlSvBgAvu9euOvjdswib+4qJA==} + '@yuku-parser/binding-linux-arm64-gnu@0.8.7': + resolution: {integrity: sha512-3gVN4pWSKZmXiNX7cU164dR9MPvesCHnlH6nPfpK+yQsCuYphjKKplcb4SnZBrhiqmXbgb2HR0c2TS0IZUPhgA==} cpu: [arm64] os: [linux] libc: [glibc] - '@yuku-parser/binding-linux-arm64-musl@0.8.3': - resolution: {integrity: sha512-cfRyu87xsJ0tFkHNsnMC4Rq6+xsFJ6i2dc4VAH52d2qLvykEJU/Mdi3ul1O2PyOApX/LoLT3uQZ0fWs3D5XE4w==} + '@yuku-parser/binding-linux-arm64-musl@0.8.7': + resolution: {integrity: sha512-S0mwfEjoLpxzXeZw802Wa4RaELsQiPtWqG6INcy8j4GtvNFtl4LCX3eGO1XLn9pyLAISLzTRyU3zUCBUPin8lg==} cpu: [arm64] os: [linux] libc: [musl] - '@yuku-parser/binding-linux-x64-gnu@0.8.3': - resolution: {integrity: sha512-GcQQCUuYxbm6P1n+io/A50rvWKDeWHutIp6rW0ycDOZuEQjOb8hDVgS88+NDyOnd9FfS0/Z6GXopcRFDyKpzOg==} + '@yuku-parser/binding-linux-x64-gnu@0.8.7': + resolution: {integrity: sha512-lnbWdPmerE5D1uH1G4IEZKnPzCrWCStRGrtgpSIe1RibAo5bZIjDbbbPYXmMHCEh4F+x/JaJpElh26a3r+BPbg==} cpu: [x64] os: [linux] libc: [glibc] - '@yuku-parser/binding-linux-x64-musl@0.8.3': - resolution: {integrity: sha512-rMkImBGZzg7GZlj8krYtdiezyjYI4igjKWMut5T65jHyNWFigMQrEpn9mDIBflloW9FKhGE3mN6yTZ/N+4HRwg==} + '@yuku-parser/binding-linux-x64-musl@0.8.7': + resolution: {integrity: sha512-769uwndMvMzUvATWbAcEvyLHKA+DzhHSCl/obBUrRdYfRo26yxui6S8y3z7uJ+Naup7UKrDxrpK7OnQkxkl9KQ==} cpu: [x64] os: [linux] libc: [musl] - '@yuku-parser/binding-win32-arm64@0.8.3': - resolution: {integrity: sha512-/2Pl2cAzCXWxah8FqJapEj/ikpt9cEutEZFCa0hnbfrshkn5+C+aBM3ZDq62d1jsgQjBMmqr5HVhJUA4OAG/Tg==} + '@yuku-parser/binding-win32-arm64@0.8.7': + resolution: {integrity: sha512-mEB/9PlaAkisJ6KWGz0zvywXoU6+80dTlR2LwS7s/jcXXoU6fm2+sitBZXtqu3+Q4DcDgPxM45uWMCzPs0TSRw==} cpu: [arm64] os: [win32] - '@yuku-parser/binding-win32-x64@0.8.3': - resolution: {integrity: sha512-Ntnvjoan9jnfLhn7Kn3h8j/bhsbVdQSVmKUqFULKtmwImLCJVHOJbLL4qbEJyrOQ7r/FBL1/c/dRvx/AQWzzXg==} + '@yuku-parser/binding-win32-x64@0.8.7': + resolution: {integrity: sha512-8vNB2DP0ou61nGb8tc/qfi41gfyDXz1MHr2zqL3nR+cJ6CEbiuWV/l/a/vv151gCgiZLLAyGkQGENpozdg716w==} cpu: [x64] os: [win32] - '@yuku-toolchain/types@0.8.3': - resolution: {integrity: sha512-9LN3HYs3A9qSPVFunsxlbfwBcUgexti3TmhOzIxB/UH8zFuaHQJXTRDcN17DW6cp1GsyZtiZA7f18uIra36Jag==} + '@yuku-toolchain/types@0.8.7': + resolution: {integrity: sha512-2Z53dNxAJL6UvFoIrDZvYf3zlO8s4VJK4O2hhaB4mXVwwpX/7ajtss3cmfqKvamlNLWyt9FSWs4eoYdlbxpnHA==} JSONStream@1.3.5: resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} @@ -4078,8 +4079,8 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - ansi-regex@6.2.2: - resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + ansi-regex@6.3.0: + resolution: {integrity: sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==} engines: {node: '>=12'} ansi-styles@4.3.0: @@ -4246,9 +4247,9 @@ packages: bare-abort-controller: optional: true - bare-fs@4.7.4: - resolution: {integrity: sha512-y1kC+ffIx/tPLdTE693uNjHfzTfr+ravR5tvWlMXe25nELbkqV400S71qHDwbkAQ1FVEZobB1NFRzFbCCcyBCQ==} - engines: {bare: '>=1.16.0'} + bare-fs@4.8.0: + resolution: {integrity: sha512-fM+MhCvdQhZ7NV6S95a07gPSqjIYKn6mFaXfx266wN3ajZGl/+1AzH+ubkXQ0fFZvOe2nk9VHkzdYkQE5zMV3Q==} + engines: {bare: '>=1.28.0'} peerDependencies: bare-buffer: '*' peerDependenciesMeta: @@ -4272,8 +4273,8 @@ packages: bare-events: optional: true - bare-url@2.4.7: - resolution: {integrity: sha512-o8CRCiJtib+ycO3mE4A5UChtGX4dDP2XxsWVu9P+Zc3H8tcmKwNVEDoDTXmwN+uuMhfKeT7/i7Y26xS8W7ohoA==} + bare-url@2.5.2: + resolution: {integrity: sha512-L13PCJzKG8RGvx8V1/DdMi12ERhC3tprr7/8a94BxpmnRsFqxh5XZNdhtMxu5HPkRshYOOWRGY8lDP7ZhpG9Cg==} base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -4282,8 +4283,8 @@ packages: resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} engines: {node: ^4.5.0 || >= 5.9} - baseline-browser-mapping@2.11.11: - resolution: {integrity: sha512-/yImnXwyTvgMkhgekLHok/Rx5vO6E0BmStWlSqKWMVm2a2ITuZ1Tn+9bgLS+gZRdZmWtd8nxuhHpdmCUOWsTQQ==} + baseline-browser-mapping@2.11.18: + resolution: {integrity: sha512-1iEmLEYSiE1SeBoAfPo/Mnx3PzfzHUkDK61ASkCpuk3YXugYLH5DYK1SzqV55F8FMI6s0F+/tCP7Polz1QRjxw==} engines: {node: '>=6.0.0'} hasBin: true @@ -4365,8 +4366,8 @@ packages: browserify-zlib@0.1.4: resolution: {integrity: sha512-19OEpq7vWgsH6WkvkBJQDFvJS1uPcbFOQ4v9CU839dO+ZZXUZO6XpE6hNCqvlIIj+4fZvRiJ6DsAQ382GwiyTQ==} - browserslist@4.28.7: - resolution: {integrity: sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==} + browserslist@4.28.8: + resolution: {integrity: sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -4426,8 +4427,8 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - caniuse-lite@1.0.30001806: - resolution: {integrity: sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==} + caniuse-lite@1.0.30001809: + resolution: {integrity: sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==} caseless@0.12.0: resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} @@ -4523,6 +4524,9 @@ packages: colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + colorjs.io@0.7.1: + resolution: {integrity: sha512-LY7OHnJZxHwT5UlzNa9bbhHHDbzB6yE5+3MIPwJEQKRvSCt/T4G7epsj+9j2BExUIIfXFIJGsIXUKdfrK9Q5tA==} + colors@1.4.0: resolution: {integrity: sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==} engines: {node: '>=0.1.90'} @@ -4587,16 +4591,20 @@ packages: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} - content-type@2.0.0: - resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==} + content-type@2.1.0: + resolution: {integrity: sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==} engines: {node: '>=18'} + content-type@3.0.0: + resolution: {integrity: sha512-AIi5H6p0xk5uknXcN3/rmhP8jgp69OfSe/JuKiQAFprJ7UGw7mwj7m4XcmDzlrnJDG+cGpphAINGdU3g3g7kDw==} + engines: {node: '>=22'} + conventional-commits-filter@6.0.1: resolution: {integrity: sha512-cs+LadpH7Kpw0M3k8wurk+sOVVDAENA0iK4OBOrkL94j5lEVYRJ4j3zd2bhY9qgzyrPqthdcYT3axzRN7AliMg==} engines: {node: '>=22'} - conventional-commits-parser@7.1.1: - resolution: {integrity: sha512-B0f42jI++V5Vb7qK+DDw68r0dNxz5hk+RdKUkx2NOi39emc9hsHa3u2M3doF7QQhRFzCrAj7uM90teG+RBTaYQ==} + conventional-commits-parser@7.1.2: + resolution: {integrity: sha512-O+x4N2yH+ijvqWlIyTHsXTAP+algNWgGbjY2duCe8w2vUMvUB95cLRslCPfTMQyLAKlet3bhZTdu6ozn4M+QJQ==} engines: {node: '>=22'} hasBin: true @@ -4627,8 +4635,9 @@ packages: peerDependencies: webpack: ^5.1.0 - core-js-compat@3.49.0: - resolution: {integrity: sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==} + core-js-compat@3.50.0: + resolution: {integrity: sha512-XGpFGbMLHwSt74YLTKho7Ib242qi6O8MSX+sRokV4oz7iKXvQWGYZthjIhjRGMxjzVkAubBO512dKGYcefmX3Q==} + engines: {node: '>=6.4.0'} core-util-is@1.0.2: resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} @@ -4767,8 +4776,8 @@ packages: resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} engines: {node: '>=18'} - default-browser@5.5.0: - resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==} + default-browser@5.5.1: + resolution: {integrity: sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==} engines: {node: '>=18'} define-data-property@1.1.4: @@ -4815,8 +4824,8 @@ packages: engines: {node: '>= 0.8.0'} hasBin: true - devtools-protocol@0.0.1653615: - resolution: {integrity: sha512-pGVkY3T/qXxAp2nFPodwYqOevk6ncNMSmvL8QfRCx5ZWGd6Vor7AFNmyaA8Zs6uJyP1QAfjuLandCgvSix1BNA==} + devtools-protocol@0.0.1666840: + resolution: {integrity: sha512-gCcO42XCHKEs7Ag0S7aGYsnJ7hlgrO3qderYqeiY0Eqk+0GFfuvT13IA0hHreJTa2KCdDVyGMeOhdMNmrrTjVg==} di@0.0.1: resolution: {integrity: sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==} @@ -4893,8 +4902,8 @@ packages: engines: {node: '>=0.12.18'} hasBin: true - electron-to-chromium@1.5.399: - resolution: {integrity: sha512-lEcqhErbHjXRvd41rnWLpzbyU/IXfIYo7QwaFWmxGeLiLyY2TBCdHnWY88vB+p3ubnihRypDm66panXl7TylLA==} + electron-to-chromium@1.5.412: + resolution: {integrity: sha512-z4rMe3esBzlzovKHj4gxJnsCGZRK5l4baUvm+gCGJBPE+gsyUMKsuU9tnEUtI1dOebXz1ytAPGjvXhmQ7rIPwA==} emoji-regex@10.6.0: resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} @@ -4994,8 +5003,8 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} - es-module-lexer@2.3.1: - resolution: {integrity: sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==} + es-module-lexer@2.3.2: + resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==} es-object-atoms@1.1.2: resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} @@ -5013,13 +5022,13 @@ packages: resolution: {integrity: sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==} engines: {node: '>= 0.4'} - esbuild-wasm@0.28.1: - resolution: {integrity: sha512-p/GD4E8oYRjg3kjdKrnMb0s4PzXgJF42e0MF4H0+ACyK/kIlFRp3e0fzOleIG+wBBm6MM3XQrbpe7soEA+vJIA==} + esbuild-wasm@0.28.2: + resolution: {integrity: sha512-GccVwhv3mmOUVQHCQm2Ox/rby8n/EqUwvZxE6Pjfikrq/lWw9g9WX/u9EykWnpot3Ko6j426DgQdea2xWKIAQA==} engines: {node: '>=18'} hasBin: true - esbuild@0.28.1: - resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} + esbuild@0.28.2: + resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==} engines: {node: '>=18'} hasBin: true @@ -5094,8 +5103,8 @@ packages: resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@10.8.0: - resolution: {integrity: sha512-nuKKvN+oIBO0koN7Tm7dlkmnkc21mtt0QJLwAKzjLq14y6lRTdVG36MZHJ8eQHwdJMwZbQNMlPOYedMq/oVJvQ==} + eslint@10.9.0: + resolution: {integrity: sha512-5KeEOJZBfEVA47boFiBsf+6MmmJpffM7qEBg4pLla2e4nlKgdKlqCW0oSLOGsT8Wl5uCGJptLV1bkaiShj90Gw==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} hasBin: true peerDependencies: @@ -5203,8 +5212,8 @@ packages: fast-string-width@3.0.2: resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} - fast-uri@3.1.5: - resolution: {integrity: sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==} + fast-uri@3.1.6: + resolution: {integrity: sha512-7Ical1vFEMr0onbVzEDIreM22I4khW+fzyQPwvAFWBp1iwdshSZRsL4jjRvPG9JP1uiqMHRto+YU6R2/CzDz5Q==} fast-wrap-ansi@0.2.2: resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} @@ -5265,8 +5274,8 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} - firebase@12.16.0: - resolution: {integrity: sha512-CNw6hFBdONkzF8UGLDx/RDRY9gVa5VmJNHd7qi4gdmA3ZuLkuOrhmWefB2l+FN+OxFpN77Itq7aO6zlTi780ag==} + firebase@12.17.1: + resolution: {integrity: sha512-dhp41ye9jMQvhx5FwjMkf/hjDHJApl7gXmvzOZGvP0M7c/GZGUnQ4qvsvlOBkF0Pa7wAwHMdcpL0ON2pXCQ4Sw==} flat-cache@4.0.1: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} @@ -5360,8 +5369,8 @@ packages: resolution: {integrity: sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==} engines: {node: '>=18'} - gaxios@7.3.0: - resolution: {integrity: sha512-RB5vLV+vvQeoFPCX4QMK6/hjVkbIamPp1QSUD0CiZcnj12qbpiL+pLbYtgD+oZkWl0tl9z+o2Utp+MpM3QRhBA==} + gaxios@7.3.1: + resolution: {integrity: sha512-kB3rzJV7d9juLZh8/56QTXCwQfxyhdOMdyYk1HdQKFtF8TJTDTZQJtixWIwXdE9Jji91mC41DUNpjleo4L4eAQ==} engines: {node: '>=18'} gcp-metadata@8.1.2: @@ -5446,8 +5455,8 @@ packages: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} - globals@17.9.0: - resolution: {integrity: sha512-m/MvAW61QVU5VDNF1Vj8axt016h8w7L5TU1e9zlab7XIttAT2YAlCwl75K1fOqvMM9apmD7lbCIRhpfkhmxhCg==} + globals@17.11.0: + resolution: {integrity: sha512-Z2I8hM+PbJDXQDq3Icgpzv+mPdwr68iZUU9d5WW4FuXfDUQfkZaZuvjMv42/5crNyw154+9+VWXbYrUgDXbxNw==} engines: {node: '>=18'} globalthis@1.0.4: @@ -5649,8 +5658,8 @@ packages: resolution: {integrity: sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==} engines: {node: '>= 4'} - immutable@3.8.3: - resolution: {integrity: sha512-AUY/VyX0E5XlibOmWt10uabJzam1zlYjwiEgQSDc5+UIkFNaF9WM0JxXKaNMGf+F/ffUF+7kRKXM9A7C0xXqMg==} + immutable@3.8.4: + resolution: {integrity: sha512-ZQv17KolYrYmsDoDB8N67zhIKsNi9mGYlk96y/yuxyAqJB2hLzSGSM7k/sB0/ZvO4kx27U9+fBeD/XlLGh/uXQ==} engines: {node: '>=0.10.0'} immutable@5.1.9: @@ -5685,8 +5694,8 @@ packages: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} - ipaddr.js@2.4.0: - resolution: {integrity: sha512-9VGk3HGanVE6JoZXHiCpnGy5X0jYDnN4EA4lntFPj+1vIWlFhIylq2CrrCOJH9EAhc5CYhq18F2Av2tgoAPsYQ==} + ipaddr.js@2.5.0: + resolution: {integrity: sha512-aq+t5NAc+cS6rZQQVWC2x98CPqGtKKTMDd4Gaodv0wShnItdKg/51djkGJ1hqH+Oy0ivDftCbSLCQob8zso01w==} engines: {node: '>= 10'} is-array-buffer@3.0.5: @@ -6029,8 +6038,8 @@ packages: json-stringify-safe@5.0.1: resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} - json-with-bigint@3.5.10: - resolution: {integrity: sha512-Vcx+JVNEBts/xfcoCS69sKrOhOk/3TVlvlT+XzUOefVKnnrbYSCKpDCm10pohsJFtsJVYnwa/cXRZ4eElzaM6w==} + json-with-bigint@3.5.12: + resolution: {integrity: sha512-uwbF/wSSuOgC7qqlq27Xp5B6a2MHVug3t0idZdTqu0JnlFvgJuH7ju+KAk/J06C7GfhoYy2gnb9wz2INqcne7w==} json5@1.0.2: resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} @@ -6075,10 +6084,10 @@ packages: resolution: {integrity: sha512-yj7hbequkQP2qOSb20GuNSIyE//PgJWHwC2IydLE6XRtsnaflv+/OSGNssPjobYUlhVVagy99TQpqUt3vAUG7A==} engines: {node: '>=10.0.0'} - karma-jasmine-html-reporter@2.2.0: - resolution: {integrity: sha512-J0laEC43Oy2RdR5V5R3bqmdo7yRIYySq6XHKbA+e5iSAgLjhR1oICLGeSREPlJXpeyNcdJf3J17YcdhD0mRssQ==} + karma-jasmine-html-reporter@2.3.0: + resolution: {integrity: sha512-iFDjVpcWHnupRoJfnjuAeNF68sAGk+7RfceCzm33HHYNV6OgBa6EC1nPgJUFM+tVDHLlQ5NhgcTKz7AbdrV3AQ==} peerDependencies: - jasmine-core: ^4.0.0 || ^5.0.0 || ^6.0.0 + jasmine-core: ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 karma: ^6.0.0 karma-jasmine: ^5.0.0 @@ -6122,8 +6131,8 @@ packages: webpack: optional: true - less@4.8.1: - resolution: {integrity: sha512-jQ3lRIo1aUtiWVYXZ7mk4+V4BjCGswF3IxTLJ+4RUta8ZiHh8lhkig2G8dya2eCcyR1dYUvzuV46EkJN8PSwww==} + less@4.9.0: + resolution: {integrity: sha512-umRhrCH7fCi8Uj2RcwKjJdvUORTjeWqkdKx0LbcZvjIwsAVsnIAGcxHaqowPeBFBjQuWOeC/bve0AlpFzF/+SQ==} engines: {node: '>=18'} hasBin: true @@ -6329,8 +6338,8 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - magic-string@1.1.0: - resolution: {integrity: sha512-kS3VHe0nEPST2saQV4Rbkchcd3UBRkVTQHo1D3h/ZTwFDhai/mfKkmtPAtD129EOI7K3HlHIsFOt0WrI2/oU9g==} + magic-string@1.2.2: + resolution: {integrity: sha512-veT/+7iXrXzT39XnEN4lOxtNl72dMgJ8Lp+5Bd6YcMSWpb0n0MjBM8Uuooi6jgJr8dhUW2swQgBmoZVMni5SVg==} magicast@0.5.4: resolution: {integrity: sha512-llBEhWm1SacoRwgHUoQJYtwp4PBLF4faQi5TCpIGyGs9n4y5+juI0tDgyKIfpqxckRHaHzouUEph3THklWh03w==} @@ -6358,10 +6367,8 @@ packages: resolution: {integrity: sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==} engines: {node: '>= 0.8'} - memfs@4.64.0: - resolution: {integrity: sha512-Kw72fgY7Wn+sD8KmtNWSafl1dz0UvAsE/PHs3YVfLiaZuA3HxNm9sRLqAu0ATiBGJvME1PxZXbBZPv5GycDeAw==} - peerDependencies: - tslib: '2' + memfs@4.68.1: + resolution: {integrity: sha512-OD+IDRUvIxu3QHL+nFm9gdyugInD27FDJ+sl4B5QgomPHXMlbw+GP918P8VNKu2FkNlVeqBkpzkwROpamVifRw==} merge-descriptors@1.0.3: resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} @@ -6430,10 +6437,6 @@ packages: peerDependencies: webpack: ^5.0.0 - minimatch@10.2.5: - resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} - engines: {node: 18 || 20 || >=22} - minimatch@10.2.6: resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} engines: {node: 18 || 20 || >=22} @@ -6514,8 +6517,8 @@ packages: engines: {node: '>=10'} hasBin: true - modern-tar@0.7.7: - resolution: {integrity: sha512-t9VmxaqrmANnEOBhpSDI6HD192Ge48k8vmWqQQL7hSFEqHEYwZbbsu49+aKLWZeRvFs3j1pMhXOqqF4kPlvjkQ==} + modern-tar@0.8.4: + resolution: {integrity: sha512-gN54ddmyzEg10orwZ2u4OOv+bjpMWdIl5jIkodK97bMq8QBSL5c0D7YX0lT1Ooz+99S7+PvFbnxzdjgHo1r41g==} engines: {node: '>=18.0.0'} mrmime@2.0.1: @@ -6547,8 +6550,8 @@ packages: resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} engines: {node: ^20.17.0 || >=22.9.0} - nanoid@3.3.16: - resolution: {integrity: sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==} + nanoid@3.3.18: + resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -6573,19 +6576,19 @@ packages: resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} engines: {node: '>= 0.6'} - negotiator@1.0.0: - resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} - engines: {node: '>= 0.6'} + negotiator@1.1.0: + resolution: {integrity: sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==} + engines: {node: '>=18'} neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - ng-packagr@22.2.0-next.2: - resolution: {integrity: sha512-769/f4DvfQEm+SMNdKM9R/D/bulIHTwDJBPj7e3CE0fI/EjVNEigv1l3j00Ipm+AAEAZhxX5Q0A4JY0fmwKpJg==} + ng-packagr@22.2.0-next.3: + resolution: {integrity: sha512-bWjugQxxaZt1yWN/TxDCPbHwfuqHaAjD7blIZd7YDLAd32424JHWLFKqQwzi93NYYCUadSg8CU+LGiczZkuFsA==} engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0} hasBin: true peerDependencies: - '@angular/compiler-cli': ^22.0.0 || ^22.1.0-next || ^22.2.0-next + '@angular/compiler-cli': ^22.2.0-next tailwindcss: ^2.0.0 || ^3.0.0 || ^4.0.0 tslib: ^2.3.0 typescript: '>=6.0 <6.1' @@ -6593,8 +6596,8 @@ packages: tailwindcss: optional: true - nock@14.0.16: - resolution: {integrity: sha512-8r4KEc6nT1D/fdLD/R1BO1CPaVEL8o40u/guFRJlXabN7vr3RmMqyjsY5Krt0nMwhsOAwXQ/mtN5vy5Jh3aErg==} + nock@14.0.17: + resolution: {integrity: sha512-EjRr1weMa4ALQX35AgZTEnP+weJJjlW1KGDiNM2IQC2069YDHas4f4B4UUYR+TTLyKWxJvOz2wObDKQs/LNreA==} engines: {node: '>=18.20.0 <20 || >=20.12.1'} node-addon-api@6.1.0: @@ -6636,8 +6639,8 @@ packages: resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} hasBin: true - node-releases@2.0.51: - resolution: {integrity: sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==} + node-releases@2.0.53: + resolution: {integrity: sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==} engines: {node: '>=18'} normalize-path@3.0.0: @@ -6718,8 +6721,8 @@ packages: resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} engines: {node: '>=18'} - open@11.0.0: - resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==} + open@11.0.1: + resolution: {integrity: sha512-NzwMUB6C1D0+Kd+9iMS/H4k+Ck3cTX6Ckyfr/gAGlmvSE1LUQZnEZvWBi4PYmMwH/S5SMeTXnE+9uAz8uF+pWw==} engines: {node: '>=20'} opn@5.3.0: @@ -6744,8 +6747,8 @@ packages: resolution: {integrity: sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==} engines: {node: '>= 0.4'} - oxc-parser@0.142.0: - resolution: {integrity: sha512-kKR+jPiRJYJDexVoziIg/FVGvr1fT1FZSSJOk6tVoMKKSlsf1Cso+cgGCJkOEDWOP174vRntCPFKg+AS7InWvw==} + oxc-parser@0.146.0: + resolution: {integrity: sha512-hrt0Ou2mCL8dLuvfvKWFjRlWaJKt0Xs7CfacAgb9mha0gKW1lSisUzNRyALgItMPIfThxkl8jH2ONP6ZbQXpaA==} engines: {node: ^20.19.0 || >=22.12.0} p-finally@1.0.0: @@ -6880,8 +6883,8 @@ packages: resolution: {integrity: sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==} hasBin: true - piscina@5.3.0: - resolution: {integrity: sha512-9D3tPBayfoal6l9l4Y8NrMn1jkV718qJoYrla6P51+hh9h0Q+9/1c8KgEnoBQqhguyfgjay4biADI8HJG3pkoA==} + piscina@5.3.1: + resolution: {integrity: sha512-LYbVUyHb5CzavqNgs64Uv+gHkrCdEt73kfPazdsJnndDSJiaEDIzMUIAHbtV3plzaoBUphZfXZCQKF01LHMgXw==} engines: {node: '>=20.x'} pkg-dir@8.0.0: @@ -6950,21 +6953,25 @@ packages: peerDependencies: postcss: ^8.4.31 - postcss-selector-parser@7.1.4: - resolution: {integrity: sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==} + postcss-selector-parser@7.1.5: + resolution: {integrity: sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==} engines: {node: '>=4'} postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - postcss@8.5.25: - resolution: {integrity: sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==} + postcss@8.5.26: + resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==} engines: {node: ^10 || ^12 || >=14} powershell-utils@0.1.0: resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==} engines: {node: '>=20'} + powershell-utils@0.2.0: + resolution: {integrity: sha512-ZlsFlG7MtSFCoc5xreOvBAozCJ6Pf06opgJjh9ONEv418xpZSAzNjstD36C6+JwOnfSqOW/9uDkqKjezTdxZhw==} + engines: {node: '>=20'} + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -6974,8 +6981,8 @@ packages: engines: {node: '>=14'} hasBin: true - probe-image-size@7.3.0: - resolution: {integrity: sha512-7CaDeBwiAbh6ohXsvLbAZhO7wzsZAmaevfxe39qvCwRh8LyaZfDlBGGLU1CCTgrTLtCOdwBBhjOrIHaIIimHfQ==} + probe-image-size@7.4.0: + resolution: {integrity: sha512-cdEprVtZxV+awMde9X+4jILBFYh4CARxVrQaMl4wY4YcPWbul9jntXrIW95NInBDyJwcVUP3U0T6yukN8rMBaQ==} proc-log@7.0.0: resolution: {integrity: sha512-FYgfaA69XZ93zaXLoMNQ+ViDXGGBgR8aLh03txzcFhV+9xOXx7+8DLCULrKKpR9+GsH9ZfHm82aSUPpozX0Ztg==} @@ -7035,20 +7042,20 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - puppeteer-core@25.4.0: - resolution: {integrity: sha512-K1plkLOdeoUnGeT1OvdqF3qxl33v+Ra/uH5VyPEhXdMcpvGiEskHzxxEU3fgpccJpJLIipB/rPUsvkZRWeKqOA==} + puppeteer-core@25.8.0: + resolution: {integrity: sha512-LDOrawV8vfCVk+yLj2ozvajNP4Sv3OV9y3Tpiyy2g2Z+aQlbcozP6KJfI4iSBq7YQER+86ihEtPa5ioiZyWxMQ==} engines: {node: '>=22.12.0'} - puppeteer@25.4.0: - resolution: {integrity: sha512-xfQp8dFBcGaLc1hEMaVr7s+oW4ZkAurr8Y9H81ilKhu6QoLfSTkZjU7IavnyJ/VWpB9ni3KNJUQHUatslLWyGw==} + puppeteer@25.8.0: + resolution: {integrity: sha512-3gcUJ+Jfodb5zNa/lWLZukBUwYiRIwAc8WRICoqfi+ZYmNWqpsPFyanTU3Gw/lhgII9aotVbAmhT6/NKHWgyUA==} engines: {node: '>=22.12.0'} hasBin: true pvtsutils@1.3.6: resolution: {integrity: sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==} - pvutils@1.1.5: - resolution: {integrity: sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==} + pvutils@1.2.0: + resolution: {integrity: sha512-BbubeCEyTuQjVMakvJQ/Sxbc93F2pwmbsxONT/ZRrwU7Ua38d8unYTwXpTVLAKJ4BDuH9IGztCjQcd/N/39Dvg==} engines: {node: '>=16.0.0'} qjobs@1.2.0: @@ -7089,8 +7096,9 @@ packages: resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} engines: {node: '>= 0.10'} - re2js@0.4.3: - resolution: {integrity: sha512-EuNmh7jurhHEE8Ge/lBo9JuMLb3qf866Xjjfyovw3wPc7+hlqDkZq4LwhrCQMEI+ARWfrKrHozEndzlpNT0WDg==} + re2js@2.8.6: + resolution: {integrity: sha512-xLgQil4kIUCrAzVk9fRSkxkFNwmygLFjVxXrLc65aE1F0+Zsb8rxumFBy4XKyvgMCTL6kilDq3EZ0piE2dP/Dg==} + engines: {node: '>=18.0.0'} readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} @@ -7111,8 +7119,8 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} - readdirp@5.0.0: - resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} + readdirp@5.1.1: + resolution: {integrity: sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==} engines: {node: '>= 20.19.0'} real-require@0.2.0: @@ -7222,13 +7230,13 @@ packages: resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} hasBin: true - rolldown-plugin-dts@0.27.14: - resolution: {integrity: sha512-ZvuDDwoIpRK9RPxDXratCpklFO9QZZWndf/sd0VBFb4LEj0jj07UcHK9OCh7V4XiFz2Z89ziyBC2K6tJiDjrbw==} - engines: {node: ^22.18.0 || >=24.11.0} + rolldown-plugin-dts@0.28.2: + resolution: {integrity: sha512-U0Ng45ZaESZ7rJtQtgCWkJYCpMgH42yIj3xv9HGAsh/dJ8XBhjI4lcqh4NOWx8+CAxoY2HWg8VYINML2yE9A5A==} + engines: {node: ^22.18.0 || ^24.11.0 || >=26.0.0} peerDependencies: '@typescript/native-preview': '*' '@volar/typescript': ~2.4.0 - rolldown: ^1.0.0 + rolldown: ^1.2.0 typescript: ^5.0.0 || ^6.0.0 || ~7.0.0 vue-tsc: ~3.2.0 || ~3.3.0 peerDependenciesMeta: @@ -7241,8 +7249,8 @@ packages: vue-tsc: optional: true - rolldown@1.2.1: - resolution: {integrity: sha512-4FKJhg8d3OiyQOA6Q1Q0hoFFpW9/OoX+VsHzpECsdsIZoOArrAK90gl59YK/Z+gnDel45bgJZK03ozH/9bCqEw==} + rolldown@1.2.5: + resolution: {integrity: sha512-VD2IE5PUG4Oj8zz2VGykiYd5wbnjdIiSsNQb8Qu5B+noEp+A78mu2iVvpp27g8es14Tk9rofNs5Tku9iQCS4fA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -7250,12 +7258,16 @@ packages: resolution: {integrity: sha512-66iiym49fU6YDJW4DPEYbmUwm4emHXP048lJM9YkecADYGO4cKf0gQZ13U/IqRN00DXbKz0g7O3yOt8m4BNFew==} engines: {node: '>=18.0.0'} - rollup-plugin-dts@6.4.1: - resolution: {integrity: sha512-l//F3Zf7ID5GoOfLfD8kroBjQKEKpy1qfhtAdnpibFZMffPaylrg1CoDC2vGkPeTeyxUe4bVFCln2EFuL7IGGg==} + rollup-plugin-dts@6.5.1: + resolution: {integrity: sha512-jODTXp3H7MK/Ur/ErtsrQ0G1GvaCmc3du+y5pNrdBMf6d7HlL2Nd/N6TkEr+f75CkUj01zEoEd7y2elH0eHi1Q==} engines: {node: '>=20'} peerDependencies: - rollup: ^3.29.4 || ^4 - typescript: ^4.5 || ^5.0 || ^6.0 + '@typescript/typescript6': ^6 + rollup: ^3 || ^4 + typescript: ^4.5 || ^5 || ^6 || ^7 + peerDependenciesMeta: + '@typescript/typescript6': + optional: true rollup-plugin-sourcemaps2@0.5.8: resolution: {integrity: sha512-c5BfiVbKAmaz+RoEhFF44Tg/X2QoLxEB8F7wS/eNWDJF/NKxi0SF7srN3Dg0RUnIcYLyCOwscket5Mr3BImMbg==} @@ -7267,8 +7279,8 @@ packages: '@types/node': optional: true - rollup@4.62.4: - resolution: {integrity: sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==} + rollup@4.62.5: + resolution: {integrity: sha512-/tqMfgP7GPA3PHhCmuiS4vIjrSVhHLgY++i+dhbG462euyAj7FpM4D9uq1X3BgjlqRdpcOrYhcQtfiQLNc8tqw==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -7317,6 +7329,123 @@ packages: sanitize-filename@1.6.4: resolution: {integrity: sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg==} + sass-embedded-all-unknown@1.103.1: + resolution: {integrity: sha512-MwX2ap06TbImAqlbnH6EndgBBejk6Eq2QB6Ae2hukCn9wmXf9cRu27um3mz3tyB4oJOOku0xfHqFH/+ckYj6YA==} + cpu: ['!arm', '!arm64', '!riscv64', '!x64'] + + sass-embedded-android-arm64@1.103.1: + resolution: {integrity: sha512-jBXWMksyz55XeLOWvQs64BqiG5DcqflmMxcvMoA2oHtQT/7XZ02hBn502qpcF/q8Qd+qqG/hU5ccM+hGXrbQZw==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [android] + + sass-embedded-android-arm@1.103.1: + resolution: {integrity: sha512-p36WHpsu5HEo2+NVNbI2Nmb8VgVH0xwOQyzghvZhF7ikGCzEVYI5BP28vHUZlCcgj2TUvMEJKI2lV336a+F/sg==} + engines: {node: '>=14.0.0'} + cpu: [arm] + os: [android] + + sass-embedded-android-riscv64@1.103.1: + resolution: {integrity: sha512-rzrH7RNntk0rx+zCE9xOiAIUt37D1cQKvFfcoX0xiePdGbNHBN8DWKaWN9vaCRfbgKCmxPjevzaqFNI/G/8E1A==} + engines: {node: '>=14.0.0'} + cpu: [riscv64] + os: [android] + + sass-embedded-android-x64@1.103.1: + resolution: {integrity: sha512-EQglAJXJutuIcqgZf7YOwzao2ND6ow/OgqN+cM48/Qaeb6AimYmZiUJmM9FFcmS3Q2n1k6/2OU3l3Hlin7bFpw==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [android] + + sass-embedded-darwin-arm64@1.103.1: + resolution: {integrity: sha512-rlaBeCul8pLbDRKdBAxdDxSBwFU5fbX42ci6CxW2Vbs34jWTMQ72+nE2v2KkGGce+JnYbb7UV6xDP/4uDWWi+w==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [darwin] + + sass-embedded-darwin-x64@1.103.1: + resolution: {integrity: sha512-VSKFfhI//AOct6ppFmAaSH7tlW2LOFECMGs77jIoWf0ZjvGch/3sc9lUTTdIaYIOnTS4qpZck+8OcI2D0G92QA==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [darwin] + + sass-embedded-linux-arm64@1.103.1: + resolution: {integrity: sha512-rC+80/Xr9svo0ka2Zr/sjD+N1zEHTw2Urm/nsEjaVp5HSH2i22jwhTKTIqie2OLl6ti+qdYnbI0sSfxOYRtgrg==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [linux] + libc: glibc + + sass-embedded-linux-arm@1.103.1: + resolution: {integrity: sha512-tJRLPUtBwXHTnBnG7I39gQvCLreuOHLLCMyKHPR1hQ7aFNwiZjADYzKMRQPNMUqti/Os9z+6I7V13FqgQHfuzg==} + engines: {node: '>=14.0.0'} + cpu: [arm] + os: [linux] + libc: glibc + + sass-embedded-linux-musl-arm64@1.103.1: + resolution: {integrity: sha512-jMgG/C/VMo7+ShMFdk0xjJmQmYkj1PbJu/yrQiaQHpuo5pE2G+vnkEgsuM5EM3lTQ4MbMfnf715qy1fkbNPiUQ==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [linux] + libc: musl + + sass-embedded-linux-musl-arm@1.103.1: + resolution: {integrity: sha512-N8L/kgVzXpnH+8d6ErIzqvb0l8kHODR3OfPaf0Azw6DOLGtEjwDRUbHno+G7iogGzyPOjKsM8OdyqHJj/bpTdw==} + engines: {node: '>=14.0.0'} + cpu: [arm] + os: [linux] + libc: musl + + sass-embedded-linux-musl-riscv64@1.103.1: + resolution: {integrity: sha512-LZm8rEvI6sKU87qPOlODsYeAs7CmWy0B4ShYkjG4OMcGCcV4Ffxn25XW4TFqmruThITQvJ8WT+WGblzdoxYgbA==} + engines: {node: '>=14.0.0'} + cpu: [riscv64] + os: [linux] + libc: musl + + sass-embedded-linux-musl-x64@1.103.1: + resolution: {integrity: sha512-rmKzgk4t6RpaDhSefbbnk5yrA4pk1nHlVhJECGay8yMfzK8eglATeY9fzPgt89xCuQk6rkOiy11e+m8tLYOW5Q==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [linux] + libc: musl + + sass-embedded-linux-riscv64@1.103.1: + resolution: {integrity: sha512-xydQmtxla31uMrmN6z+mAogVGdEMDmkUElVq2+udomX/TPZDYJBkApEyJl237M2VLWVv29v1N4U4i2Z4xa7SDA==} + engines: {node: '>=14.0.0'} + cpu: [riscv64] + os: [linux] + libc: glibc + + sass-embedded-linux-x64@1.103.1: + resolution: {integrity: sha512-Rln1oWm0MWKzzemOgQWrRFzfVBARwS9qxAidZPpCQHSohzPnWizFkmY1CUJad/YJNsA7sscueMdX63OEvxoM8w==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [linux] + libc: glibc + + sass-embedded-unknown-all@1.103.1: + resolution: {integrity: sha512-p7kI4US8+n+YguKczXh+4KcodJnk+8cwv+QFdvMaz3OfPrdHV0EnhPsaVDWD9qXb1XXCEdUhpp6vQwGk3eKc3A==} + os: ['!android', '!darwin', '!linux', '!win32'] + + sass-embedded-win32-arm64@1.103.1: + resolution: {integrity: sha512-/0renOj3SpZGu4TA8CZJ6qZbTxGZk1khkPfiBTmV1uFB40ESYM5VsdXNj76P07bsAjIj9X2fCjTafLQFLSd9Nw==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [win32] + + sass-embedded-win32-x64@1.103.1: + resolution: {integrity: sha512-fIg60j9u5YlLUU3W8FvySMR4HHIo9R12HHFQTW7njyQ8nXqgR+t+XfCzGJloQka8g583BlAfu31Q76Ljr5o+9g==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [win32] + + sass-embedded@1.103.1: + resolution: {integrity: sha512-UVIuFZPzz+4DXYbHzQ5gKaKkyP2IH3Sc/WoF63N2V7QIfnC2E9UpQAtiKlCVJTfBe43HRgHDBLnXpZkYAEBsNQ==} + engines: {node: '>=20.19.0'} + hasBin: true + sass-loader@17.0.0: resolution: {integrity: sha512-0Ybm8ohBQ9LcrycVrFQp/KQBNX5a3Wda9/smS0mE/xLffzEnwvV8nykOzrbiSWNzTE3IB/jiXx8O4QmDPG2+Gw==} engines: {node: '>= 22.11.0'} @@ -7335,8 +7464,8 @@ packages: webpack: optional: true - sass@1.102.0: - resolution: {integrity: sha512-NSOyTnaQF7rTAEOtI2fwb386vL+akyiQLBZu8Na7hXCb+umJy0GAqlcMIaqACZ6Z1VgTBS4K9PG6B3IdjHGJsw==} + sass@1.103.1: + resolution: {integrity: sha512-9icZURbP51S6S0QGoyaeqk9uB06GNWxsFYWfH5RgpFgqK5FA8tJcM3AdVxrZEVJ7dz+L87nG95gBKf4VuaMHGw==} engines: {node: '>=20.19.0'} hasBin: true @@ -7373,8 +7502,8 @@ packages: resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} engines: {node: '>= 18'} - serialize-javascript@7.0.7: - resolution: {integrity: sha512-YAy8Od6KV+uuwUuU50np8fGB/Aues6Y0nAhA9y/hId74PlKUcme4pXcBD46NWKr1Q4osN/iseZ17YqO1XfmI8g==} + serialize-javascript@7.1.0: + resolution: {integrity: sha512-RNEqWOyhhUQYN9V1GfHwu9AR/g+NTciH6Z5u3/no6X3/w+04J2lVDL+svFQVXgXrEGBMG2puMVN3gq2SNGuTGw==} engines: {node: '>=20.0.0'} serve-index@1.9.2: @@ -7658,6 +7787,14 @@ packages: symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + sync-child-process@1.0.2: + resolution: {integrity: sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==} + engines: {node: '>=16.0.0'} + + sync-message-port@1.2.0: + resolution: {integrity: sha512-gAQ9qrUN/UCypHtGFbbe7Rc/f9bzO88IwrG8TDo/aMKAApKyD6E3W4Cm0EfhfBb6Z6SKt59tTCTfD+n1xmAvMg==} + engines: {node: '>=16.0.0'} + tagged-tag@1.0.0: resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} engines: {node: '>=20'} @@ -7676,8 +7813,8 @@ packages: teex@1.0.1: resolution: {integrity: sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==} - terser@5.49.0: - resolution: {integrity: sha512-SNiDnXyHSrxVcIOtVbULzcTmniUiwcV7Nwdyj1twVubeTmbjoa8p69KKDpfkdoOavuM4/GRm1+ykI8qqnavHoA==} + terser@5.50.0: + resolution: {integrity: sha512-CN9BVxWhgS/hRxtUMjtC2uRWSTcSfQFHMDWma6sKKfIivCD91sM+FOPfvwoaRMqCSrUpe1nv3jDamd9eEQ4y+w==} engines: {node: '>=10'} hasBin: true @@ -7792,8 +7929,8 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tsx@4.23.1: - resolution: {integrity: sha512-GQHnkIfxyx1wYCOS/wonik5MVRZU9hi1TEZmzGZSCJB1y9YgoZ8H6itNE/u4suE+yLmOzuE4E5S4TZ/ZX2wcWQ==} + tsx@4.23.12: + resolution: {integrity: sha512-FDf4L4sYzKtzWYhU/Xm0AQFdTjdIxNo9ElTf2mxXM6k8YMHXzYUe4yODVaXP4V9uMFbVg8c0qyBccK2OOxb45Q==} engines: {node: '>=18.0.0'} hasBin: true @@ -7891,8 +8028,8 @@ packages: resolution: {integrity: sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA==} engines: {node: '>=18.17'} - undici@8.9.0: - resolution: {integrity: sha512-aWZpUj7XoGonMClx4gdDRfgBjqeA+F473aDmROQQbM9n6PRfK/u1q/a0X4wMTgcHfT8H6fpbt98PFuDUwFg2YA==} + undici@8.10.0: + resolution: {integrity: sha512-HvltHd7avK13QIw/oLe4qoOLyoVSoafqJ2jYOrtMRBkbYT31eiBQ8O0ehRKZiEZCMEyLFQNIADpgCWC5fALvYQ==} engines: {node: '>=22.19.0'} unenv@1.10.0: @@ -7937,8 +8074,8 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} - update-browserslist-db@1.2.3: - resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + update-browserslist-db@1.3.1: + resolution: {integrity: sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -7971,24 +8108,27 @@ packages: resolution: {integrity: sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==} engines: {node: '>= 0.10'} + varint@6.0.0: + resolution: {integrity: sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==} + vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} - verdaccio-audit@13.1.1: - resolution: {integrity: sha512-6RNvJ9sIvG4Zkqp4EAn/h2Q/XhaUJ2Hi4MSl8lKiZBdIzU4r/KBhuoYZ1xF/PBuSiih5z3OP47r0JwOu0qDwOw==} + verdaccio-audit@13.1.2: + resolution: {integrity: sha512-2nHSt8qRQcCCUQX6QO72eC1D1RoqCBZVPTotVdS/CGpMtE+jUHIDnOSWWMQxtN53PHa6wjjheySJCcuEpmtJsQ==} engines: {node: '>=22'} - verdaccio-auth-memory@13.1.1: - resolution: {integrity: sha512-Oo3CsyIGaM2S0qCXMN0Coi1uVyvIplT5DAEqwYK22z9XTHAZQf3oUgiVPxEwpFGcqxYsIAq1kxH5COST3xxR6g==} + verdaccio-auth-memory@13.1.2: + resolution: {integrity: sha512-Z5CH/iUa37QmaNt+nbgvyjojyQ6WZA0r06ymsGjor8xCqFBzGtIlLsM8Es0Njh/fU06r928RcsfEjha7zFqv1w==} engines: {node: '>=22'} - verdaccio-htpasswd@13.1.1: - resolution: {integrity: sha512-cLzVGQyj2iYkUCZyqV+ACU82Nod9ThKzmDMCV+8fp4c7QmrHAsl6oUaSEW1ZBAtB2hIP/Gv2vAfiTzrXrAX7NA==} + verdaccio-htpasswd@13.1.2: + resolution: {integrity: sha512-37/V2zidmJWpJp78PFMAHEpDPMAdFiIRevV/CshJydyiglIJd0CJi0W4LGsbgXy2pSqYbu22dVzq6JV/MR5xYw==} engines: {node: '>=22'} - verdaccio@6.9.2: - resolution: {integrity: sha512-HwFvPB/LMfDZlfZdVo6zZX05rOWlVReRDMWqE7rihE0crlw4brNFNO4HTXxEVrjz/bV9iurJyRSANWOTaAOtAQ==} + verdaccio@6.10.0: + resolution: {integrity: sha512-kY/wz65nE5P3aJkVvIsM/5XqyuQgLjQ9vBKlnGSsgetT4d3nkPI/NAKkI02aSm00D0Ene1SOPNJ/oITELPrM/g==} engines: {node: '>=22'} hasBin: true @@ -7996,13 +8136,13 @@ packages: resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==} engines: {'0': node >=0.6.0} - vite@8.2.0: - resolution: {integrity: sha512-pn+CFpM0lwDeKwmOq1ZaBK/9sjorZcgqxki6MbY/jPEVd9vichIlmlD4HmQ5wdP5EgqQCFRaACBxMC7uEGc6lQ==} + vite@8.2.2: + resolution: {integrity: sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.4.0 + '@vitejs/devtools': ^0.4.0 || ^0.5.0 esbuild: ^0.27.0 || ^0.28.0 jiti: '>=1.21.0' less: ^4.0.0 @@ -8039,20 +8179,20 @@ packages: yaml: optional: true - vitest@4.1.10: - resolution: {integrity: sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==} + vitest@4.1.11: + resolution: {integrity: sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.10 - '@vitest/browser-preview': 4.1.10 - '@vitest/browser-webdriverio': 4.1.10 - '@vitest/coverage-istanbul': 4.1.10 - '@vitest/coverage-v8': 4.1.10 - '@vitest/ui': 4.1.10 + '@vitest/browser-playwright': 4.1.11 + '@vitest/browser-preview': 4.1.11 + '@vitest/browser-webdriverio': 4.1.11 + '@vitest/coverage-istanbul': 4.1.11 + '@vitest/coverage-v8': 4.1.11 + '@vitest/ui': 4.1.11 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -8229,8 +8369,8 @@ packages: wordwrap@1.0.0: resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} - wrap-ansi@10.0.0: - resolution: {integrity: sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ==} + wrap-ansi@10.0.1: + resolution: {integrity: sha512-M0N4xzyzosiIok3svYlEo1sdLZts/8FPgYH/GPC3wvlmPoRvnoManGMrE54waYj3tISA8w6lsdesfVv67qSr8Q==} engines: {node: '>=20'} wrap-ansi@7.0.0: @@ -8248,8 +8388,8 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - ws@8.21.1: - resolution: {integrity: sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==} + ws@8.21.3: + resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -8260,8 +8400,8 @@ packages: utf-8-validate: optional: true - wsl-utils@0.3.1: - resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==} + wsl-utils@1.0.0: + resolution: {integrity: sha512-Hl0ZOAs672vg+06kfujwRhoS6/jehvULrlFkuF2dRu6pHgA8U06h3xqNIqNNU1LTXPcedxByAR4GS6pwQK0mgA==} engines: {node: '>=20'} xhr2@0.2.1: @@ -8283,6 +8423,9 @@ packages: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} + xxhash-wasm@1.1.0: + resolution: {integrity: sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==} + y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -8327,14 +8470,14 @@ packages: resolution: {integrity: sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==} engines: {node: '>=18'} - yuku-ast@0.8.3: - resolution: {integrity: sha512-8x34yU5uhHUnJXzy2Qvjvec/vE9BzS0/2khVT1MsLmSLO/P8Q1Wp8IxHv+IhD+HMYETk6kherOSvP4JPWw2joQ==} + yuku-ast@0.8.7: + resolution: {integrity: sha512-h6+4bDfyootiMB9vckk5uKo5r5j0GHrkr17FQTDNfEsFT3DWlN9uu1HJwQwc64pgmLCI945fWM3lbTIqxjT3GQ==} - yuku-codegen@0.8.3: - resolution: {integrity: sha512-okdo5bb+TfebQa4JOjz9QxeT34D6CcBxu8dxaPUdFEKRdLkp+D2Fah2OanepK+XTyPXdmAJzAo9iXvYvZ/5rmg==} + yuku-codegen@0.8.7: + resolution: {integrity: sha512-adwDZSh8oVDzhE6Du9PwVWxcOxeV0e2EVhUuMKWfhSY4wkrDq9eqixlxFF3l/XGUV1E7UFzhpz9393MUumkyNw==} - yuku-parser@0.8.3: - resolution: {integrity: sha512-KPQcpF9aj77ywlJBIkQWCQ9DObdxnCA8AJdUOmA5CZZx042Xt4+dvbQmPJfWxF3E+KG5dVAZ2fBKuDJ8VsKWgA==} + yuku-parser@0.8.7: + resolution: {integrity: sha512-vRD9nwt4L3aYpxNqeSC4WqLv58xrXef0Ong1Mc45CTXTIpvLafx7JO05sczmQZwdLEZvywrLOGdNC5+Rp5N1BQ==} zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} @@ -8368,29 +8511,29 @@ snapshots: '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 - '@angular/animations@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))': + '@angular/animations@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))': dependencies: - '@angular/core': 22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2) + '@angular/core': 22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2) tslib: 2.8.1 - '@angular/cdk@22.1.0-rc.0(@angular/common@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@22.2.0-next.0(@angular/animations@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2)': + '@angular/cdk@22.2.0-next.2(@angular/common@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@22.2.0-next.3(@angular/animations@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2)': dependencies: - '@angular/common': 22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2) - '@angular/core': 22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2) - '@angular/platform-browser': 22.2.0-next.0(@angular/animations@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)) + '@angular/common': 22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2) + '@angular/core': 22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2) + '@angular/platform-browser': 22.2.0-next.3(@angular/animations@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)) parse5: 8.0.1 rxjs: 7.8.2 tslib: 2.8.1 - '@angular/common@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2)': + '@angular/common@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2)': dependencies: - '@angular/core': 22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2) + '@angular/core': 22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2) rxjs: 7.8.2 tslib: 2.8.1 - '@angular/compiler-cli@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(typescript@6.0.3)': + '@angular/compiler-cli@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(typescript@6.0.3)': dependencies: - '@angular/compiler': 22.2.0-next.0 + '@angular/compiler': 22.2.0-next.3 '@babel/core': 8.0.1 '@jridgewell/sourcemap-codec': 1.5.5 chokidar: 5.0.0 @@ -8402,62 +8545,62 @@ snapshots: optionalDependencies: typescript: 6.0.3 - '@angular/compiler@22.2.0-next.0': + '@angular/compiler@22.2.0-next.3': dependencies: tslib: 2.8.1 - '@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)': + '@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)': dependencies: rxjs: 7.8.2 tslib: 2.8.1 optionalDependencies: - '@angular/compiler': 22.2.0-next.0 + '@angular/compiler': 22.2.0-next.3 zone.js: 0.16.2 - '@angular/forms@22.2.0-next.0(@angular/common@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@22.2.0-next.0(@angular/animations@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2)': + '@angular/forms@22.2.0-next.3(@angular/common@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@22.2.0-next.3(@angular/animations@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2)': dependencies: - '@angular/common': 22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2) - '@angular/core': 22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2) - '@angular/platform-browser': 22.2.0-next.0(@angular/animations@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)) + '@angular/common': 22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2) + '@angular/core': 22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2) + '@angular/platform-browser': 22.2.0-next.3(@angular/animations@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)) '@standard-schema/spec': 1.1.0 rxjs: 7.8.2 tslib: 2.8.1 zod: 4.4.3 - '@angular/localize@22.2.0-next.0(@angular/compiler-cli@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(typescript@6.0.3))(@angular/compiler@22.2.0-next.0)': + '@angular/localize@22.2.0-next.3(@angular/compiler-cli@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(typescript@6.0.3))(@angular/compiler@22.2.0-next.3)': dependencies: - '@angular/compiler': 22.2.0-next.0 - '@angular/compiler-cli': 22.2.0-next.0(@angular/compiler@22.2.0-next.0)(typescript@6.0.3) + '@angular/compiler': 22.2.0-next.3 + '@angular/compiler-cli': 22.2.0-next.3(@angular/compiler@22.2.0-next.3)(typescript@6.0.3) '@babel/core': 8.0.1 tinyglobby: 0.2.17 yargs: 18.1.0 - '@angular/material@22.1.0-rc.0(1d924d51246212fc2acfd8877e0b8722)': + '@angular/material@22.2.0-next.2(d4251d47b7bf7d31284dc64d049d8a95)': dependencies: - '@angular/cdk': 22.1.0-rc.0(@angular/common@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@22.2.0-next.0(@angular/animations@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2) - '@angular/common': 22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2) - '@angular/core': 22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2) - '@angular/forms': 22.2.0-next.0(@angular/common@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@22.2.0-next.0(@angular/animations@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2) - '@angular/platform-browser': 22.2.0-next.0(@angular/animations@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)) + '@angular/cdk': 22.2.0-next.2(@angular/common@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@22.2.0-next.3(@angular/animations@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2) + '@angular/common': 22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2) + '@angular/core': 22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2) + '@angular/forms': 22.2.0-next.3(@angular/common@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@22.2.0-next.3(@angular/animations@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2) + '@angular/platform-browser': 22.2.0-next.3(@angular/animations@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)) rxjs: 7.8.2 tslib: 2.8.1 - '@angular/ng-dev@https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/dcf9b6776377d5d301e054111add3ef80f433b70': + '@angular/ng-dev@https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/bcad06e14cb29b52b0856020af5e2ab868075602': dependencies: '@actions/core': 3.0.1 - '@conventional-changelog/git-client': 3.1.0(conventional-commits-filter@6.0.1)(conventional-commits-parser@7.1.1) + '@conventional-changelog/git-client': 3.1.2(conventional-commits-filter@6.0.1)(conventional-commits-parser@7.1.2) '@google-cloud/spanner': 8.0.0(supports-color@11.0.0) - '@google/genai': 2.13.0(bufferutil@4.1.0)(supports-color@11.0.0)(utf-8-validate@6.0.6) + '@google/genai': 2.17.1(bufferutil@4.1.0)(supports-color@11.0.0)(utf-8-validate@6.0.6) '@inquirer/prompts': 8.5.2(@types/node@24.13.3) '@inquirer/type': 4.0.7(@types/node@24.13.3) - '@octokit/auth-app': 8.2.0 - '@octokit/core': 7.0.6 - '@octokit/graphql': 9.0.3 + '@octokit/auth-app': 8.3.0 + '@octokit/core': 7.0.7 + '@octokit/graphql': 9.0.4 '@octokit/graphql-schema': 15.26.1 '@octokit/openapi-types': 28.0.0 - '@octokit/plugin-paginate-rest': 15.0.0(@octokit/core@7.0.6) - '@octokit/plugin-rest-endpoint-methods': 18.0.0(@octokit/core@7.0.6) - '@octokit/request-error': 7.1.0 + '@octokit/plugin-paginate-rest': 15.0.0(@octokit/core@7.0.7) + '@octokit/plugin-rest-endpoint-methods': 18.0.0(@octokit/core@7.0.7) + '@octokit/request-error': 7.1.1 '@octokit/rest': 22.0.1 '@octokit/types': 17.0.0 '@pnpm/dependency-path': 1001.1.10 @@ -8467,7 +8610,7 @@ snapshots: '@types/folder-hash': 4.0.4 '@types/jasmine': 6.0.0 '@types/node': 24.13.3 - '@types/semver': 7.7.1 + '@types/semver': 7.8.0 '@types/which': 3.0.4 '@types/yargs': 17.0.35 '@types/yarnpkg__lockfile': 1.1.9 @@ -8475,11 +8618,11 @@ snapshots: bufferutil: 4.1.0 cli-progress: 3.12.0 conventional-commits-filter: 6.0.1 - conventional-commits-parser: 7.1.1 + conventional-commits-parser: 7.1.2 ejs: 6.0.1 encoding: 0.1.13 fast-glob: 3.3.3 - firebase: 12.16.0 + firebase: 12.17.1 folder-hash: 4.1.3(supports-color@11.0.0) jasmine: 6.3.0 jasmine-core: 6.3.0 @@ -8487,10 +8630,10 @@ snapshots: jsonc-parser: 3.3.1 minimatch: 10.2.6 multimatch: 8.0.0 - nock: 14.0.16 + nock: 14.0.17 semver: 7.8.5 supports-color: 11.0.0 - tsx: 4.23.1 + tsx: 4.23.12 typed-graphqlify: 3.1.6 typescript: 6.0.3 utf-8-validate: 6.0.6 @@ -8502,42 +8645,42 @@ snapshots: - '@modelcontextprotocol/sdk' - '@react-native-async-storage/async-storage' - '@angular/platform-browser@22.2.0-next.0(@angular/animations@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))': + '@angular/platform-browser@22.2.0-next.3(@angular/animations@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))': dependencies: - '@angular/common': 22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2) - '@angular/core': 22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2) + '@angular/common': 22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2) + '@angular/core': 22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2) tslib: 2.8.1 optionalDependencies: - '@angular/animations': 22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)) + '@angular/animations': 22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)) - '@angular/platform-server@22.2.0-next.0(@angular/common@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/compiler@22.2.0-next.0)(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@22.2.0-next.0(@angular/animations@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2)': + '@angular/platform-server@22.2.0-next.3(@angular/common@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/compiler@22.2.0-next.3)(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@22.2.0-next.3(@angular/animations@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2)': dependencies: - '@angular/common': 22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2) - '@angular/compiler': 22.2.0-next.0 - '@angular/core': 22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2) - '@angular/platform-browser': 22.2.0-next.0(@angular/animations@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)) + '@angular/common': 22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2) + '@angular/compiler': 22.2.0-next.3 + '@angular/core': 22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2) + '@angular/platform-browser': 22.2.0-next.3(@angular/animations@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)) rxjs: 7.8.2 tslib: 2.8.1 xhr2: 0.2.1 - '@angular/router@22.2.0-next.0(@angular/common@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@22.2.0-next.0(@angular/animations@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2)': + '@angular/router@22.2.0-next.3(@angular/common@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@22.2.0-next.3(@angular/animations@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2)': dependencies: - '@angular/common': 22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2) - '@angular/core': 22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2) - '@angular/platform-browser': 22.2.0-next.0(@angular/animations@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2)) + '@angular/common': 22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2) + '@angular/core': 22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2) + '@angular/platform-browser': 22.2.0-next.3(@angular/animations@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2)) rxjs: 7.8.2 tslib: 2.8.1 - '@angular/service-worker@22.2.0-next.0(@angular/core@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2)': + '@angular/service-worker@22.2.0-next.3(@angular/core@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2)': dependencies: - '@angular/core': 22.2.0-next.0(@angular/compiler@22.2.0-next.0)(rxjs@7.8.2)(zone.js@0.16.2) + '@angular/core': 22.2.0-next.3(@angular/compiler@22.2.0-next.3)(rxjs@7.8.2)(zone.js@0.16.2) rxjs: 7.8.2 tslib: 2.8.1 - '@asamuzakjp/css-color@6.0.5': + '@asamuzakjp/css-color@6.0.7': dependencies: '@csstools/css-calc': 3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) - '@csstools/css-color-parser': 4.1.10(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 lru-cache: 11.5.2 @@ -8628,7 +8771,7 @@ snapshots: dependencies: '@babel/compat-data': 7.29.7 '@babel/helper-validator-option': 7.29.7 - browserslist: 4.28.7 + browserslist: 4.28.8 lru-cache: 5.1.1 semver: 6.3.1 @@ -8636,7 +8779,7 @@ snapshots: dependencies: '@babel/compat-data': 8.0.0 '@babel/helper-validator-option': 8.0.0 - browserslist: 4.28.7 + browserslist: 4.28.8 lru-cache: 11.5.2 semver: 7.8.5 @@ -9164,7 +9307,7 @@ snapshots: '@babel/plugin-transform-unicode-sets-regex': 8.0.1(@babel/core@8.0.1) '@babel/preset-modules': 0.2.0(@babel/core@8.0.1) babel-plugin-polyfill-corejs3: 1.0.0(@babel/core@8.0.1) - core-js-compat: 3.49.0 + core-js-compat: 3.50.0 semver: 7.8.5 '@babel/preset-modules@0.2.0(@babel/core@8.0.1)': @@ -9234,27 +9377,29 @@ snapshots: dependencies: css-tree: 3.2.1 + '@bufbuild/protobuf@2.14.0': {} + '@colors/colors@1.5.0': {} - '@conventional-changelog/git-client@3.1.0(conventional-commits-filter@6.0.1)(conventional-commits-parser@7.1.1)': + '@conventional-changelog/git-client@3.1.2(conventional-commits-filter@6.0.1)(conventional-commits-parser@7.1.2)': dependencies: '@simple-libs/child-process-utils': 2.0.0 '@simple-libs/stream-utils': 2.0.0 semver: 7.8.5 optionalDependencies: conventional-commits-filter: 6.0.1 - conventional-commits-parser: 7.1.1 + conventional-commits-parser: 7.1.2 - '@csstools/color-helpers@6.1.0': {} + '@csstools/color-helpers@6.1.1': {} '@csstools/css-calc@3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': dependencies: '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 - '@csstools/css-color-parser@4.1.10(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + '@csstools/css-color-parser@4.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': dependencies: - '@csstools/color-helpers': 6.1.0 + '@csstools/color-helpers': 6.1.1 '@csstools/css-calc': 3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 @@ -9263,7 +9408,7 @@ snapshots: dependencies: '@csstools/css-tokenizer': 4.0.0 - '@csstools/css-syntax-patches-for-csstree@1.1.7(css-tree@3.2.1)': + '@csstools/css-syntax-patches-for-csstree@1.1.8(css-tree@3.2.1)': optionalDependencies: css-tree: 3.2.1 @@ -9291,128 +9436,96 @@ snapshots: '@discoveryjs/json-ext@1.1.0': {} - '@emnapi/core@1.11.2': - dependencies: - '@emnapi/wasi-threads': 1.2.2 - tslib: 2.8.1 - optional: true - - '@emnapi/core@2.0.0-alpha.3': - dependencies: - '@emnapi/wasi-threads': 2.0.1 - tslib: 2.8.1 - optional: true - - '@emnapi/runtime@1.11.2': - dependencies: - tslib: 2.8.1 + '@esbuild/aix-ppc64@0.28.2': optional: true - '@emnapi/runtime@2.0.0-alpha.3': - dependencies: - tslib: 2.8.1 - optional: true - - '@emnapi/wasi-threads@1.2.2': - dependencies: - tslib: 2.8.1 - optional: true - - '@emnapi/wasi-threads@2.0.1': - dependencies: - tslib: 2.8.1 + '@esbuild/android-arm64@0.28.2': optional: true - '@esbuild/aix-ppc64@0.28.1': + '@esbuild/android-arm@0.28.2': optional: true - '@esbuild/android-arm64@0.28.1': + '@esbuild/android-x64@0.28.2': optional: true - '@esbuild/android-arm@0.28.1': + '@esbuild/darwin-arm64@0.28.2': optional: true - '@esbuild/android-x64@0.28.1': + '@esbuild/darwin-x64@0.28.2': optional: true - '@esbuild/darwin-arm64@0.28.1': + '@esbuild/freebsd-arm64@0.28.2': optional: true - '@esbuild/darwin-x64@0.28.1': + '@esbuild/freebsd-x64@0.28.2': optional: true - '@esbuild/freebsd-arm64@0.28.1': + '@esbuild/linux-arm64@0.28.2': optional: true - '@esbuild/freebsd-x64@0.28.1': + '@esbuild/linux-arm@0.28.2': optional: true - '@esbuild/linux-arm64@0.28.1': + '@esbuild/linux-ia32@0.28.2': optional: true - '@esbuild/linux-arm@0.28.1': + '@esbuild/linux-loong64@0.28.2': optional: true - '@esbuild/linux-ia32@0.28.1': + '@esbuild/linux-mips64el@0.28.2': optional: true - '@esbuild/linux-loong64@0.28.1': + '@esbuild/linux-ppc64@0.28.2': optional: true - '@esbuild/linux-mips64el@0.28.1': + '@esbuild/linux-riscv64@0.28.2': optional: true - '@esbuild/linux-ppc64@0.28.1': + '@esbuild/linux-s390x@0.28.2': optional: true - '@esbuild/linux-riscv64@0.28.1': + '@esbuild/linux-x64@0.28.2': optional: true - '@esbuild/linux-s390x@0.28.1': + '@esbuild/netbsd-arm64@0.28.2': optional: true - '@esbuild/linux-x64@0.28.1': + '@esbuild/netbsd-x64@0.28.2': optional: true - '@esbuild/netbsd-arm64@0.28.1': + '@esbuild/openbsd-arm64@0.28.2': optional: true - '@esbuild/netbsd-x64@0.28.1': + '@esbuild/openbsd-x64@0.28.2': optional: true - '@esbuild/openbsd-arm64@0.28.1': + '@esbuild/openharmony-arm64@0.28.2': optional: true - '@esbuild/openbsd-x64@0.28.1': + '@esbuild/sunos-x64@0.28.2': optional: true - '@esbuild/openharmony-arm64@0.28.1': + '@esbuild/win32-arm64@0.28.2': optional: true - '@esbuild/sunos-x64@0.28.1': + '@esbuild/win32-ia32@0.28.2': optional: true - '@esbuild/win32-arm64@0.28.1': + '@esbuild/win32-x64@0.28.2': optional: true - '@esbuild/win32-ia32@0.28.1': - optional: true - - '@esbuild/win32-x64@0.28.1': - optional: true - - '@eslint-community/eslint-utils@4.10.1(eslint@10.8.0(jiti@2.7.0)(supports-color@11.0.0))': + '@eslint-community/eslint-utils@4.10.1(eslint@10.9.0(jiti@2.7.0)(supports-color@11.0.0))': dependencies: - eslint: 10.8.0(jiti@2.7.0)(supports-color@11.0.0) + eslint: 10.9.0(jiti@2.7.0)(supports-color@11.0.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} - '@eslint/compat@2.1.0(eslint@10.8.0(jiti@2.7.0)(supports-color@11.0.0))': + '@eslint/compat@2.1.0(eslint@10.9.0(jiti@2.7.0)(supports-color@11.0.0))': dependencies: '@eslint/core': 1.2.1 optionalDependencies: - eslint: 10.8.0(jiti@2.7.0)(supports-color@11.0.0) + eslint: 10.9.0(jiti@2.7.0)(supports-color@11.0.0) '@eslint/config-array@0.23.5(supports-color@11.0.0)': dependencies: @@ -9444,9 +9557,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@10.0.1(eslint@10.8.0(jiti@2.7.0)(supports-color@11.0.0))': + '@eslint/js@10.0.1(eslint@10.9.0(jiti@2.7.0)(supports-color@11.0.0))': optionalDependencies: - eslint: 10.8.0(jiti@2.7.0)(supports-color@11.0.0) + eslint: 10.9.0(jiti@2.7.0)(supports-color@11.0.0) '@eslint/object-schema@3.0.5': {} @@ -9457,221 +9570,221 @@ snapshots: '@exodus/bytes@1.15.1': {} - '@firebase/ai@2.13.1(@firebase/app-types@0.9.5)(@firebase/app@0.15.1)': + '@firebase/ai@2.14.0(@firebase/app-types@0.9.5)(@firebase/app@0.16.0)': dependencies: - '@firebase/app': 0.15.1 + '@firebase/app': 0.16.0 '@firebase/app-check-interop-types': 0.3.4 '@firebase/app-types': 0.9.5 - '@firebase/component': 0.7.3 + '@firebase/component': 0.7.4 '@firebase/logger': 0.5.1 - '@firebase/util': 1.15.1 + '@firebase/util': 1.15.2 tslib: 2.8.1 - '@firebase/analytics-compat@0.2.28(@firebase/app-compat@0.5.15)(@firebase/app@0.15.1)': + '@firebase/analytics-compat@0.2.29(@firebase/app-compat@0.5.16)(@firebase/app@0.16.0)': dependencies: - '@firebase/analytics': 0.10.22(@firebase/app@0.15.1) + '@firebase/analytics': 0.10.23(@firebase/app@0.16.0) '@firebase/analytics-types': 0.8.4 - '@firebase/app-compat': 0.5.15 - '@firebase/component': 0.7.3 - '@firebase/util': 1.15.1 + '@firebase/app': 0.16.0 + '@firebase/app-compat': 0.5.16 + '@firebase/component': 0.7.4 + '@firebase/util': 1.15.2 tslib: 2.8.1 - transitivePeerDependencies: - - '@firebase/app' '@firebase/analytics-types@0.8.4': {} - '@firebase/analytics@0.10.22(@firebase/app@0.15.1)': + '@firebase/analytics@0.10.23(@firebase/app@0.16.0)': dependencies: - '@firebase/app': 0.15.1 - '@firebase/component': 0.7.3 - '@firebase/installations': 0.6.22(@firebase/app@0.15.1) + '@firebase/app': 0.16.0 + '@firebase/component': 0.7.4 + '@firebase/installations': 0.6.23(@firebase/app@0.16.0) '@firebase/logger': 0.5.1 - '@firebase/util': 1.15.1 + '@firebase/util': 1.15.2 tslib: 2.8.1 - '@firebase/app-check-compat@0.4.5(@firebase/app-compat@0.5.15)(@firebase/app@0.15.1)': + '@firebase/app-check-compat@0.4.6(@firebase/app-compat@0.5.16)(@firebase/app@0.16.0)': dependencies: - '@firebase/app-check': 0.12.0(@firebase/app@0.15.1) + '@firebase/app': 0.16.0 + '@firebase/app-check': 0.13.0(@firebase/app@0.16.0) '@firebase/app-check-types': 0.5.4 - '@firebase/app-compat': 0.5.15 - '@firebase/component': 0.7.3 + '@firebase/app-compat': 0.5.16 + '@firebase/component': 0.7.4 '@firebase/logger': 0.5.1 - '@firebase/util': 1.15.1 + '@firebase/util': 1.15.2 tslib: 2.8.1 - transitivePeerDependencies: - - '@firebase/app' '@firebase/app-check-interop-types@0.3.4': {} '@firebase/app-check-types@0.5.4': {} - '@firebase/app-check@0.12.0(@firebase/app@0.15.1)': + '@firebase/app-check@0.13.0(@firebase/app@0.16.0)': dependencies: - '@firebase/app': 0.15.1 - '@firebase/component': 0.7.3 + '@firebase/app': 0.16.0 + '@firebase/component': 0.7.4 '@firebase/logger': 0.5.1 - '@firebase/util': 1.15.1 + '@firebase/util': 1.15.2 tslib: 2.8.1 - '@firebase/app-compat@0.5.15': + '@firebase/app-compat@0.5.16': dependencies: - '@firebase/app': 0.15.1 - '@firebase/component': 0.7.3 + '@firebase/app': 0.16.0 + '@firebase/component': 0.7.4 '@firebase/logger': 0.5.1 - '@firebase/util': 1.15.1 + '@firebase/util': 1.15.2 tslib: 2.8.1 '@firebase/app-types@0.9.5': dependencies: '@firebase/logger': 0.5.1 - '@firebase/app@0.15.1': + '@firebase/app@0.16.0': dependencies: - '@firebase/component': 0.7.3 + '@firebase/component': 0.7.4 '@firebase/logger': 0.5.1 - '@firebase/util': 1.15.1 + '@firebase/util': 1.15.2 idb: 7.1.1 tslib: 2.8.1 - '@firebase/auth-compat@0.6.8(@firebase/app-compat@0.5.15)(@firebase/app-types@0.9.5)(@firebase/app@0.15.1)': + '@firebase/auth-compat@0.6.9(@firebase/app-compat@0.5.16)(@firebase/app-types@0.9.5)(@firebase/app@0.16.0)': dependencies: - '@firebase/app-compat': 0.5.15 - '@firebase/auth': 1.13.3(@firebase/app@0.15.1) - '@firebase/auth-types': 0.13.1(@firebase/app-types@0.9.5)(@firebase/util@1.15.1) - '@firebase/component': 0.7.3 - '@firebase/util': 1.15.1 + '@firebase/app': 0.16.0 + '@firebase/app-compat': 0.5.16 + '@firebase/auth': 1.13.4(@firebase/app@0.16.0) + '@firebase/auth-types': 0.13.1(@firebase/app-types@0.9.5)(@firebase/util@1.15.2) + '@firebase/component': 0.7.4 + '@firebase/util': 1.15.2 tslib: 2.8.1 transitivePeerDependencies: - - '@firebase/app' - '@firebase/app-types' - '@react-native-async-storage/async-storage' '@firebase/auth-interop-types@0.2.5': {} - '@firebase/auth-types@0.13.1(@firebase/app-types@0.9.5)(@firebase/util@1.15.1)': + '@firebase/auth-types@0.13.1(@firebase/app-types@0.9.5)(@firebase/util@1.15.2)': dependencies: '@firebase/app-types': 0.9.5 - '@firebase/util': 1.15.1 + '@firebase/util': 1.15.2 - '@firebase/auth@1.13.3(@firebase/app@0.15.1)': + '@firebase/auth@1.13.4(@firebase/app@0.16.0)': dependencies: - '@firebase/app': 0.15.1 - '@firebase/component': 0.7.3 + '@firebase/app': 0.16.0 + '@firebase/component': 0.7.4 '@firebase/logger': 0.5.1 - '@firebase/util': 1.15.1 + '@firebase/util': 1.15.2 tslib: 2.8.1 - '@firebase/component@0.7.3': + '@firebase/component@0.7.4': dependencies: - '@firebase/util': 1.15.1 + '@firebase/util': 1.15.2 tslib: 2.8.1 - '@firebase/data-connect@0.7.1(@firebase/app@0.15.1)': + '@firebase/data-connect@0.7.3(@firebase/app@0.16.0)': dependencies: - '@firebase/app': 0.15.1 + '@firebase/app': 0.16.0 '@firebase/auth-interop-types': 0.2.5 - '@firebase/component': 0.7.3 + '@firebase/component': 0.7.4 '@firebase/logger': 0.5.1 - '@firebase/util': 1.15.1 + '@firebase/util': 1.15.2 tslib: 2.8.1 - '@firebase/database-compat@2.1.4': + '@firebase/database-compat@2.1.6(@firebase/app-compat@0.5.16)(@firebase/app@0.16.0)': dependencies: - '@firebase/component': 0.7.3 - '@firebase/database': 1.1.3 - '@firebase/database-types': 1.0.20 + '@firebase/component': 0.7.4 + '@firebase/database': 1.1.4 + '@firebase/database-types': 1.0.21 '@firebase/logger': 0.5.1 - '@firebase/util': 1.15.1 + '@firebase/util': 1.15.2 tslib: 2.8.1 + optionalDependencies: + '@firebase/app': 0.16.0 + '@firebase/app-compat': 0.5.16 - '@firebase/database-types@1.0.20': + '@firebase/database-types@1.0.21': dependencies: '@firebase/app-types': 0.9.5 - '@firebase/util': 1.15.1 + '@firebase/util': 1.15.2 - '@firebase/database@1.1.3': + '@firebase/database@1.1.4': dependencies: '@firebase/app-check-interop-types': 0.3.4 '@firebase/auth-interop-types': 0.2.5 - '@firebase/component': 0.7.3 + '@firebase/component': 0.7.4 '@firebase/logger': 0.5.1 - '@firebase/util': 1.15.1 + '@firebase/util': 1.15.2 faye-websocket: 0.11.4 tslib: 2.8.1 - '@firebase/firestore-compat@0.4.11(@firebase/app-compat@0.5.15)(@firebase/app-types@0.9.5)(@firebase/app@0.15.1)': + '@firebase/firestore-compat@0.4.12(@firebase/app-compat@0.5.16)(@firebase/app-types@0.9.5)(@firebase/app@0.16.0)': dependencies: - '@firebase/app-compat': 0.5.15 - '@firebase/component': 0.7.3 - '@firebase/firestore': 4.16.0(@firebase/app@0.15.1) - '@firebase/firestore-types': 3.0.4(@firebase/app-types@0.9.5)(@firebase/util@1.15.1) - '@firebase/util': 1.15.1 + '@firebase/app': 0.16.0 + '@firebase/app-compat': 0.5.16 + '@firebase/component': 0.7.4 + '@firebase/firestore': 4.17.0(@firebase/app@0.16.0) + '@firebase/firestore-types': 3.0.4(@firebase/app-types@0.9.5)(@firebase/util@1.15.2) + '@firebase/util': 1.15.2 tslib: 2.8.1 transitivePeerDependencies: - - '@firebase/app' - '@firebase/app-types' - '@firebase/firestore-types@3.0.4(@firebase/app-types@0.9.5)(@firebase/util@1.15.1)': + '@firebase/firestore-types@3.0.4(@firebase/app-types@0.9.5)(@firebase/util@1.15.2)': dependencies: '@firebase/app-types': 0.9.5 - '@firebase/util': 1.15.1 + '@firebase/util': 1.15.2 - '@firebase/firestore@4.16.0(@firebase/app@0.15.1)': + '@firebase/firestore@4.17.0(@firebase/app@0.16.0)': dependencies: - '@firebase/app': 0.15.1 - '@firebase/component': 0.7.3 + '@firebase/app': 0.16.0 + '@firebase/component': 0.7.4 '@firebase/logger': 0.5.1 - '@firebase/util': 1.15.1 + '@firebase/util': 1.15.2 '@firebase/webchannel-wrapper': 1.0.6 '@grpc/grpc-js': 1.9.16 '@grpc/proto-loader': 0.7.15 - re2js: 0.4.3 + re2js: 2.8.6 tslib: 2.8.1 - '@firebase/functions-compat@0.4.5(@firebase/app-compat@0.5.15)(@firebase/app@0.15.1)': + '@firebase/functions-compat@0.4.6(@firebase/app-compat@0.5.16)(@firebase/app@0.16.0)': dependencies: - '@firebase/app-compat': 0.5.15 - '@firebase/component': 0.7.3 - '@firebase/functions': 0.13.5(@firebase/app@0.15.1) + '@firebase/app': 0.16.0 + '@firebase/app-compat': 0.5.16 + '@firebase/component': 0.7.4 + '@firebase/functions': 0.13.6(@firebase/app@0.16.0) '@firebase/functions-types': 0.6.4 - '@firebase/util': 1.15.1 + '@firebase/util': 1.15.2 tslib: 2.8.1 - transitivePeerDependencies: - - '@firebase/app' '@firebase/functions-types@0.6.4': {} - '@firebase/functions@0.13.5(@firebase/app@0.15.1)': + '@firebase/functions@0.13.6(@firebase/app@0.16.0)': dependencies: - '@firebase/app': 0.15.1 + '@firebase/app': 0.16.0 '@firebase/app-check-interop-types': 0.3.4 '@firebase/auth-interop-types': 0.2.5 - '@firebase/component': 0.7.3 + '@firebase/component': 0.7.4 '@firebase/messaging-interop-types': 0.2.5 - '@firebase/util': 1.15.1 + '@firebase/util': 1.15.2 tslib: 2.8.1 - '@firebase/installations-compat@0.2.22(@firebase/app-compat@0.5.15)(@firebase/app-types@0.9.5)(@firebase/app@0.15.1)': + '@firebase/installations-compat@0.2.23(@firebase/app-compat@0.5.16)(@firebase/app-types@0.9.5)(@firebase/app@0.16.0)': dependencies: - '@firebase/app-compat': 0.5.15 - '@firebase/component': 0.7.3 - '@firebase/installations': 0.6.22(@firebase/app@0.15.1) + '@firebase/app': 0.16.0 + '@firebase/app-compat': 0.5.16 + '@firebase/component': 0.7.4 + '@firebase/installations': 0.6.23(@firebase/app@0.16.0) '@firebase/installations-types': 0.5.4(@firebase/app-types@0.9.5) - '@firebase/util': 1.15.1 + '@firebase/util': 1.15.2 tslib: 2.8.1 transitivePeerDependencies: - - '@firebase/app' - '@firebase/app-types' '@firebase/installations-types@0.5.4(@firebase/app-types@0.9.5)': dependencies: '@firebase/app-types': 0.9.5 - '@firebase/installations@0.6.22(@firebase/app@0.15.1)': + '@firebase/installations@0.6.23(@firebase/app@0.16.0)': dependencies: - '@firebase/app': 0.15.1 - '@firebase/component': 0.7.3 - '@firebase/util': 1.15.1 + '@firebase/app': 0.16.0 + '@firebase/component': 0.7.4 + '@firebase/util': 1.15.2 idb: 7.1.1 tslib: 2.8.1 @@ -9679,100 +9792,97 @@ snapshots: dependencies: tslib: 2.8.1 - '@firebase/messaging-compat@0.2.27(@firebase/app-compat@0.5.15)(@firebase/app@0.15.1)': + '@firebase/messaging-compat@0.2.28(@firebase/app-compat@0.5.16)(@firebase/app@0.16.0)': dependencies: - '@firebase/app-compat': 0.5.15 - '@firebase/component': 0.7.3 - '@firebase/messaging': 0.13.0(@firebase/app@0.15.1) - '@firebase/util': 1.15.1 + '@firebase/app': 0.16.0 + '@firebase/app-compat': 0.5.16 + '@firebase/component': 0.7.4 + '@firebase/messaging': 0.13.1(@firebase/app@0.16.0) + '@firebase/util': 1.15.2 tslib: 2.8.1 - transitivePeerDependencies: - - '@firebase/app' '@firebase/messaging-interop-types@0.2.5': {} - '@firebase/messaging@0.13.0(@firebase/app@0.15.1)': + '@firebase/messaging@0.13.1(@firebase/app@0.16.0)': dependencies: - '@firebase/app': 0.15.1 - '@firebase/component': 0.7.3 - '@firebase/installations': 0.6.22(@firebase/app@0.15.1) + '@firebase/app': 0.16.0 + '@firebase/component': 0.7.4 + '@firebase/installations': 0.6.23(@firebase/app@0.16.0) '@firebase/messaging-interop-types': 0.2.5 - '@firebase/util': 1.15.1 + '@firebase/util': 1.15.2 idb: 7.1.1 tslib: 2.8.1 - '@firebase/performance-compat@0.2.25(@firebase/app-compat@0.5.15)(@firebase/app@0.15.1)': + '@firebase/performance-compat@0.2.26(@firebase/app-compat@0.5.16)(@firebase/app@0.16.0)': dependencies: - '@firebase/app-compat': 0.5.15 - '@firebase/component': 0.7.3 + '@firebase/app': 0.16.0 + '@firebase/app-compat': 0.5.16 + '@firebase/component': 0.7.4 '@firebase/logger': 0.5.1 - '@firebase/performance': 0.7.12(@firebase/app@0.15.1) + '@firebase/performance': 0.7.13(@firebase/app@0.16.0) '@firebase/performance-types': 0.2.4 - '@firebase/util': 1.15.1 + '@firebase/util': 1.15.2 tslib: 2.8.1 - transitivePeerDependencies: - - '@firebase/app' '@firebase/performance-types@0.2.4': {} - '@firebase/performance@0.7.12(@firebase/app@0.15.1)': + '@firebase/performance@0.7.13(@firebase/app@0.16.0)': dependencies: - '@firebase/app': 0.15.1 - '@firebase/component': 0.7.3 - '@firebase/installations': 0.6.22(@firebase/app@0.15.1) + '@firebase/app': 0.16.0 + '@firebase/component': 0.7.4 + '@firebase/installations': 0.6.23(@firebase/app@0.16.0) '@firebase/logger': 0.5.1 - '@firebase/util': 1.15.1 + '@firebase/util': 1.15.2 tslib: 2.8.1 web-vitals: 4.2.4 - '@firebase/remote-config-compat@0.2.27(@firebase/app-compat@0.5.15)(@firebase/app@0.15.1)': + '@firebase/remote-config-compat@0.2.28(@firebase/app-compat@0.5.16)(@firebase/app@0.16.0)': dependencies: - '@firebase/app-compat': 0.5.15 - '@firebase/component': 0.7.3 + '@firebase/app': 0.16.0 + '@firebase/app-compat': 0.5.16 + '@firebase/component': 0.7.4 '@firebase/logger': 0.5.1 - '@firebase/remote-config': 0.9.0(@firebase/app@0.15.1) + '@firebase/remote-config': 0.9.1(@firebase/app@0.16.0) '@firebase/remote-config-types': 0.5.1 - '@firebase/util': 1.15.1 + '@firebase/util': 1.15.2 tslib: 2.8.1 - transitivePeerDependencies: - - '@firebase/app' '@firebase/remote-config-types@0.5.1': {} - '@firebase/remote-config@0.9.0(@firebase/app@0.15.1)': + '@firebase/remote-config@0.9.1(@firebase/app@0.16.0)': dependencies: - '@firebase/app': 0.15.1 - '@firebase/component': 0.7.3 - '@firebase/installations': 0.6.22(@firebase/app@0.15.1) + '@firebase/app': 0.16.0 + '@firebase/component': 0.7.4 + '@firebase/installations': 0.6.23(@firebase/app@0.16.0) '@firebase/logger': 0.5.1 - '@firebase/util': 1.15.1 + '@firebase/util': 1.15.2 tslib: 2.8.1 - '@firebase/storage-compat@0.4.3(@firebase/app-compat@0.5.15)(@firebase/app-types@0.9.5)(@firebase/app@0.15.1)': + '@firebase/storage-compat@0.4.4(@firebase/app-compat@0.5.16)(@firebase/app-types@0.9.5)(@firebase/app@0.16.0)': dependencies: - '@firebase/app-compat': 0.5.15 - '@firebase/component': 0.7.3 - '@firebase/storage': 0.14.3(@firebase/app@0.15.1) - '@firebase/storage-types': 0.8.4(@firebase/app-types@0.9.5)(@firebase/util@1.15.1) - '@firebase/util': 1.15.1 + '@firebase/app': 0.16.0 + '@firebase/app-compat': 0.5.16 + '@firebase/component': 0.7.4 + '@firebase/storage': 0.14.4(@firebase/app@0.16.0) + '@firebase/storage-types': 0.8.4(@firebase/app-types@0.9.5)(@firebase/util@1.15.2) + '@firebase/util': 1.15.2 tslib: 2.8.1 transitivePeerDependencies: - - '@firebase/app' - '@firebase/app-types' - '@firebase/storage-types@0.8.4(@firebase/app-types@0.9.5)(@firebase/util@1.15.1)': + '@firebase/storage-types@0.8.4(@firebase/app-types@0.9.5)(@firebase/util@1.15.2)': dependencies: '@firebase/app-types': 0.9.5 - '@firebase/util': 1.15.1 + '@firebase/util': 1.15.2 - '@firebase/storage@0.14.3(@firebase/app@0.15.1)': + '@firebase/storage@0.14.4(@firebase/app@0.16.0)': dependencies: - '@firebase/app': 0.15.1 - '@firebase/component': 0.7.3 - '@firebase/util': 1.15.1 + '@firebase/app': 0.16.0 + '@firebase/component': 0.7.4 + '@firebase/util': 1.15.2 tslib: 2.8.1 - '@firebase/util@1.15.1': + '@firebase/util@1.15.2': dependencies: tslib: 2.8.1 @@ -9839,12 +9949,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@google/genai@2.13.0(bufferutil@4.1.0)(supports-color@11.0.0)(utf-8-validate@6.0.6)': + '@google/genai@2.17.1(bufferutil@4.1.0)(supports-color@11.0.0)(utf-8-validate@6.0.6)': dependencies: google-auth-library: 10.9.1(supports-color@11.0.0) p-retry: 4.6.2 protobufjs: 7.6.5 - ws: 8.21.1(bufferutil@4.1.0)(utf-8-validate@6.0.6) + ws: 8.21.3(bufferutil@4.1.0)(utf-8-validate@6.0.6) transitivePeerDependencies: - bufferutil - supports-color @@ -9895,42 +10005,42 @@ snapshots: '@inquirer/ansi@2.0.7': {} - '@inquirer/checkbox@5.2.1(@types/node@22.20.1)': + '@inquirer/checkbox@5.2.2(@types/node@22.20.1)': dependencies: '@inquirer/ansi': 2.0.7 - '@inquirer/core': 11.2.1(@types/node@22.20.1) - '@inquirer/figures': 2.0.7 + '@inquirer/core': 12.0.0(@types/node@22.20.1) + '@inquirer/figures': 2.0.8 '@inquirer/type': 4.0.7(@types/node@22.20.1) optionalDependencies: '@types/node': 22.20.1 - '@inquirer/checkbox@5.2.1(@types/node@24.13.3)': + '@inquirer/checkbox@5.2.2(@types/node@24.13.3)': dependencies: '@inquirer/ansi': 2.0.7 - '@inquirer/core': 11.2.1(@types/node@24.13.3) - '@inquirer/figures': 2.0.7 + '@inquirer/core': 12.0.0(@types/node@24.13.3) + '@inquirer/figures': 2.0.8 '@inquirer/type': 4.0.7(@types/node@24.13.3) optionalDependencies: '@types/node': 24.13.3 - '@inquirer/confirm@6.1.1(@types/node@22.20.1)': + '@inquirer/confirm@6.2.0(@types/node@22.20.1)': dependencies: - '@inquirer/core': 11.2.1(@types/node@22.20.1) + '@inquirer/core': 12.0.0(@types/node@22.20.1) '@inquirer/type': 4.0.7(@types/node@22.20.1) optionalDependencies: '@types/node': 22.20.1 - '@inquirer/confirm@6.1.1(@types/node@24.13.3)': + '@inquirer/confirm@6.2.0(@types/node@24.13.3)': dependencies: - '@inquirer/core': 11.2.1(@types/node@24.13.3) + '@inquirer/core': 12.0.0(@types/node@24.13.3) '@inquirer/type': 4.0.7(@types/node@24.13.3) optionalDependencies: '@types/node': 24.13.3 - '@inquirer/core@11.2.1(@types/node@22.20.1)': + '@inquirer/core@12.0.0(@types/node@22.20.1)': dependencies: '@inquirer/ansi': 2.0.7 - '@inquirer/figures': 2.0.7 + '@inquirer/figures': 2.0.8 '@inquirer/type': 4.0.7(@types/node@22.20.1) cli-width: 4.1.0 fast-wrap-ansi: 0.2.2 @@ -9939,10 +10049,10 @@ snapshots: optionalDependencies: '@types/node': 22.20.1 - '@inquirer/core@11.2.1(@types/node@24.13.3)': + '@inquirer/core@12.0.0(@types/node@24.13.3)': dependencies: '@inquirer/ansi': 2.0.7 - '@inquirer/figures': 2.0.7 + '@inquirer/figures': 2.0.8 '@inquirer/type': 4.0.7(@types/node@24.13.3) cli-width: 4.1.0 fast-wrap-ansi: 0.2.2 @@ -9951,170 +10061,170 @@ snapshots: optionalDependencies: '@types/node': 24.13.3 - '@inquirer/editor@5.2.2(@types/node@22.20.1)': + '@inquirer/editor@5.3.0(@types/node@22.20.1)': dependencies: - '@inquirer/core': 11.2.1(@types/node@22.20.1) - '@inquirer/external-editor': 3.0.3(@types/node@22.20.1) + '@inquirer/core': 12.0.0(@types/node@22.20.1) + '@inquirer/external-editor': 3.0.4(@types/node@22.20.1) '@inquirer/type': 4.0.7(@types/node@22.20.1) optionalDependencies: '@types/node': 22.20.1 - '@inquirer/editor@5.2.2(@types/node@24.13.3)': + '@inquirer/editor@5.3.0(@types/node@24.13.3)': dependencies: - '@inquirer/core': 11.2.1(@types/node@24.13.3) - '@inquirer/external-editor': 3.0.3(@types/node@24.13.3) + '@inquirer/core': 12.0.0(@types/node@24.13.3) + '@inquirer/external-editor': 3.0.4(@types/node@24.13.3) '@inquirer/type': 4.0.7(@types/node@24.13.3) optionalDependencies: '@types/node': 24.13.3 - '@inquirer/expand@5.1.1(@types/node@22.20.1)': + '@inquirer/expand@5.1.2(@types/node@22.20.1)': dependencies: - '@inquirer/core': 11.2.1(@types/node@22.20.1) + '@inquirer/core': 12.0.0(@types/node@22.20.1) '@inquirer/type': 4.0.7(@types/node@22.20.1) optionalDependencies: '@types/node': 22.20.1 - '@inquirer/expand@5.1.1(@types/node@24.13.3)': + '@inquirer/expand@5.1.2(@types/node@24.13.3)': dependencies: - '@inquirer/core': 11.2.1(@types/node@24.13.3) + '@inquirer/core': 12.0.0(@types/node@24.13.3) '@inquirer/type': 4.0.7(@types/node@24.13.3) optionalDependencies: '@types/node': 24.13.3 - '@inquirer/external-editor@3.0.3(@types/node@22.20.1)': + '@inquirer/external-editor@3.0.4(@types/node@22.20.1)': dependencies: chardet: 2.2.0 iconv-lite: 0.7.3 optionalDependencies: '@types/node': 22.20.1 - '@inquirer/external-editor@3.0.3(@types/node@24.13.3)': + '@inquirer/external-editor@3.0.4(@types/node@24.13.3)': dependencies: chardet: 2.2.0 iconv-lite: 0.7.3 optionalDependencies: '@types/node': 24.13.3 - '@inquirer/figures@2.0.7': {} + '@inquirer/figures@2.0.8': {} - '@inquirer/input@5.1.2(@types/node@22.20.1)': + '@inquirer/input@5.1.3(@types/node@22.20.1)': dependencies: - '@inquirer/core': 11.2.1(@types/node@22.20.1) + '@inquirer/core': 12.0.0(@types/node@22.20.1) '@inquirer/type': 4.0.7(@types/node@22.20.1) optionalDependencies: '@types/node': 22.20.1 - '@inquirer/input@5.1.2(@types/node@24.13.3)': + '@inquirer/input@5.1.3(@types/node@24.13.3)': dependencies: - '@inquirer/core': 11.2.1(@types/node@24.13.3) + '@inquirer/core': 12.0.0(@types/node@24.13.3) '@inquirer/type': 4.0.7(@types/node@24.13.3) optionalDependencies: '@types/node': 24.13.3 - '@inquirer/number@4.1.1(@types/node@22.20.1)': + '@inquirer/number@4.2.0(@types/node@22.20.1)': dependencies: - '@inquirer/core': 11.2.1(@types/node@22.20.1) + '@inquirer/core': 12.0.0(@types/node@22.20.1) '@inquirer/type': 4.0.7(@types/node@22.20.1) optionalDependencies: '@types/node': 22.20.1 - '@inquirer/number@4.1.1(@types/node@24.13.3)': + '@inquirer/number@4.2.0(@types/node@24.13.3)': dependencies: - '@inquirer/core': 11.2.1(@types/node@24.13.3) + '@inquirer/core': 12.0.0(@types/node@24.13.3) '@inquirer/type': 4.0.7(@types/node@24.13.3) optionalDependencies: '@types/node': 24.13.3 - '@inquirer/password@5.1.1(@types/node@22.20.1)': + '@inquirer/password@5.1.2(@types/node@22.20.1)': dependencies: '@inquirer/ansi': 2.0.7 - '@inquirer/core': 11.2.1(@types/node@22.20.1) + '@inquirer/core': 12.0.0(@types/node@22.20.1) '@inquirer/type': 4.0.7(@types/node@22.20.1) optionalDependencies: '@types/node': 22.20.1 - '@inquirer/password@5.1.1(@types/node@24.13.3)': + '@inquirer/password@5.1.2(@types/node@24.13.3)': dependencies: '@inquirer/ansi': 2.0.7 - '@inquirer/core': 11.2.1(@types/node@24.13.3) + '@inquirer/core': 12.0.0(@types/node@24.13.3) '@inquirer/type': 4.0.7(@types/node@24.13.3) optionalDependencies: '@types/node': 24.13.3 - '@inquirer/prompts@8.5.2(@types/node@22.20.1)': - dependencies: - '@inquirer/checkbox': 5.2.1(@types/node@22.20.1) - '@inquirer/confirm': 6.1.1(@types/node@22.20.1) - '@inquirer/editor': 5.2.2(@types/node@22.20.1) - '@inquirer/expand': 5.1.1(@types/node@22.20.1) - '@inquirer/input': 5.1.2(@types/node@22.20.1) - '@inquirer/number': 4.1.1(@types/node@22.20.1) - '@inquirer/password': 5.1.1(@types/node@22.20.1) - '@inquirer/rawlist': 5.3.1(@types/node@22.20.1) - '@inquirer/search': 4.2.1(@types/node@22.20.1) - '@inquirer/select': 5.2.1(@types/node@22.20.1) - optionalDependencies: - '@types/node': 22.20.1 - '@inquirer/prompts@8.5.2(@types/node@24.13.3)': dependencies: - '@inquirer/checkbox': 5.2.1(@types/node@24.13.3) - '@inquirer/confirm': 6.1.1(@types/node@24.13.3) - '@inquirer/editor': 5.2.2(@types/node@24.13.3) - '@inquirer/expand': 5.1.1(@types/node@24.13.3) - '@inquirer/input': 5.1.2(@types/node@24.13.3) - '@inquirer/number': 4.1.1(@types/node@24.13.3) - '@inquirer/password': 5.1.1(@types/node@24.13.3) - '@inquirer/rawlist': 5.3.1(@types/node@24.13.3) - '@inquirer/search': 4.2.1(@types/node@24.13.3) - '@inquirer/select': 5.2.1(@types/node@24.13.3) + '@inquirer/checkbox': 5.2.2(@types/node@24.13.3) + '@inquirer/confirm': 6.2.0(@types/node@24.13.3) + '@inquirer/editor': 5.3.0(@types/node@24.13.3) + '@inquirer/expand': 5.1.2(@types/node@24.13.3) + '@inquirer/input': 5.1.3(@types/node@24.13.3) + '@inquirer/number': 4.2.0(@types/node@24.13.3) + '@inquirer/password': 5.1.2(@types/node@24.13.3) + '@inquirer/rawlist': 5.3.2(@types/node@24.13.3) + '@inquirer/search': 4.3.0(@types/node@24.13.3) + '@inquirer/select': 5.2.2(@types/node@24.13.3) optionalDependencies: '@types/node': 24.13.3 - '@inquirer/rawlist@5.3.1(@types/node@22.20.1)': + '@inquirer/prompts@8.6.0(@types/node@22.20.1)': + dependencies: + '@inquirer/checkbox': 5.2.2(@types/node@22.20.1) + '@inquirer/confirm': 6.2.0(@types/node@22.20.1) + '@inquirer/editor': 5.3.0(@types/node@22.20.1) + '@inquirer/expand': 5.1.2(@types/node@22.20.1) + '@inquirer/input': 5.1.3(@types/node@22.20.1) + '@inquirer/number': 4.2.0(@types/node@22.20.1) + '@inquirer/password': 5.1.2(@types/node@22.20.1) + '@inquirer/rawlist': 5.3.2(@types/node@22.20.1) + '@inquirer/search': 4.3.0(@types/node@22.20.1) + '@inquirer/select': 5.2.2(@types/node@22.20.1) + optionalDependencies: + '@types/node': 22.20.1 + + '@inquirer/rawlist@5.3.2(@types/node@22.20.1)': dependencies: - '@inquirer/core': 11.2.1(@types/node@22.20.1) + '@inquirer/core': 12.0.0(@types/node@22.20.1) '@inquirer/type': 4.0.7(@types/node@22.20.1) optionalDependencies: '@types/node': 22.20.1 - '@inquirer/rawlist@5.3.1(@types/node@24.13.3)': + '@inquirer/rawlist@5.3.2(@types/node@24.13.3)': dependencies: - '@inquirer/core': 11.2.1(@types/node@24.13.3) + '@inquirer/core': 12.0.0(@types/node@24.13.3) '@inquirer/type': 4.0.7(@types/node@24.13.3) optionalDependencies: '@types/node': 24.13.3 - '@inquirer/search@4.2.1(@types/node@22.20.1)': + '@inquirer/search@4.3.0(@types/node@22.20.1)': dependencies: - '@inquirer/core': 11.2.1(@types/node@22.20.1) - '@inquirer/figures': 2.0.7 + '@inquirer/core': 12.0.0(@types/node@22.20.1) + '@inquirer/figures': 2.0.8 '@inquirer/type': 4.0.7(@types/node@22.20.1) optionalDependencies: '@types/node': 22.20.1 - '@inquirer/search@4.2.1(@types/node@24.13.3)': + '@inquirer/search@4.3.0(@types/node@24.13.3)': dependencies: - '@inquirer/core': 11.2.1(@types/node@24.13.3) - '@inquirer/figures': 2.0.7 + '@inquirer/core': 12.0.0(@types/node@24.13.3) + '@inquirer/figures': 2.0.8 '@inquirer/type': 4.0.7(@types/node@24.13.3) optionalDependencies: '@types/node': 24.13.3 - '@inquirer/select@5.2.1(@types/node@22.20.1)': + '@inquirer/select@5.2.2(@types/node@22.20.1)': dependencies: '@inquirer/ansi': 2.0.7 - '@inquirer/core': 11.2.1(@types/node@22.20.1) - '@inquirer/figures': 2.0.7 + '@inquirer/core': 12.0.0(@types/node@22.20.1) + '@inquirer/figures': 2.0.8 '@inquirer/type': 4.0.7(@types/node@22.20.1) optionalDependencies: '@types/node': 22.20.1 - '@inquirer/select@5.2.1(@types/node@24.13.3)': + '@inquirer/select@5.2.2(@types/node@24.13.3)': dependencies: '@inquirer/ansi': 2.0.7 - '@inquirer/core': 11.2.1(@types/node@24.13.3) - '@inquirer/figures': 2.0.7 + '@inquirer/core': 12.0.0(@types/node@24.13.3) + '@inquirer/figures': 2.0.8 '@inquirer/type': 4.0.7(@types/node@24.13.3) optionalDependencies: '@types/node': 24.13.3 @@ -10138,7 +10248,7 @@ snapshots: '@istanbuljs/schema@0.1.6': {} - '@jasminejs/reporters@1.0.0': {} + '@jasminejs/reporters@1.1.0': {} '@jridgewell/gen-mapping@0.3.13': dependencies: @@ -10190,59 +10300,59 @@ snapshots: dependencies: tslib: 2.8.1 - '@jsonjoy.com/fs-core@4.64.0(tslib@2.8.1)': + '@jsonjoy.com/fs-core@4.68.1(tslib@2.8.1)': dependencies: - '@jsonjoy.com/fs-node-builtins': 4.64.0(tslib@2.8.1) - '@jsonjoy.com/fs-node-utils': 4.64.0(tslib@2.8.1) + '@jsonjoy.com/fs-node-builtins': 4.68.1(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.68.1(tslib@2.8.1) thingies: 2.6.1(tslib@2.8.1) tslib: 2.8.1 - '@jsonjoy.com/fs-fsa@4.64.0(tslib@2.8.1)': + '@jsonjoy.com/fs-fsa@4.68.1(tslib@2.8.1)': dependencies: - '@jsonjoy.com/fs-core': 4.64.0(tslib@2.8.1) - '@jsonjoy.com/fs-node-builtins': 4.64.0(tslib@2.8.1) - '@jsonjoy.com/fs-node-utils': 4.64.0(tslib@2.8.1) + '@jsonjoy.com/fs-core': 4.68.1(tslib@2.8.1) + '@jsonjoy.com/fs-node-builtins': 4.68.1(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.68.1(tslib@2.8.1) thingies: 2.6.1(tslib@2.8.1) tslib: 2.8.1 - '@jsonjoy.com/fs-node-builtins@4.64.0(tslib@2.8.1)': + '@jsonjoy.com/fs-node-builtins@4.68.1(tslib@2.8.1)': dependencies: tslib: 2.8.1 - '@jsonjoy.com/fs-node-to-fsa@4.64.0(tslib@2.8.1)': + '@jsonjoy.com/fs-node-to-fsa@4.68.1(tslib@2.8.1)': dependencies: - '@jsonjoy.com/fs-fsa': 4.64.0(tslib@2.8.1) - '@jsonjoy.com/fs-node-builtins': 4.64.0(tslib@2.8.1) - '@jsonjoy.com/fs-node-utils': 4.64.0(tslib@2.8.1) + '@jsonjoy.com/fs-fsa': 4.68.1(tslib@2.8.1) + '@jsonjoy.com/fs-node-builtins': 4.68.1(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.68.1(tslib@2.8.1) tslib: 2.8.1 - '@jsonjoy.com/fs-node-utils@4.64.0(tslib@2.8.1)': + '@jsonjoy.com/fs-node-utils@4.68.1(tslib@2.8.1)': dependencies: - '@jsonjoy.com/fs-node-builtins': 4.64.0(tslib@2.8.1) + '@jsonjoy.com/fs-node-builtins': 4.68.1(tslib@2.8.1) glob-to-regex.js: 1.2.0(tslib@2.8.1) tslib: 2.8.1 - '@jsonjoy.com/fs-node@4.64.0(tslib@2.8.1)': + '@jsonjoy.com/fs-node@4.68.1(tslib@2.8.1)': dependencies: - '@jsonjoy.com/fs-core': 4.64.0(tslib@2.8.1) - '@jsonjoy.com/fs-node-builtins': 4.64.0(tslib@2.8.1) - '@jsonjoy.com/fs-node-utils': 4.64.0(tslib@2.8.1) - '@jsonjoy.com/fs-print': 4.64.0(tslib@2.8.1) - '@jsonjoy.com/fs-snapshot': 4.64.0(tslib@2.8.1) + '@jsonjoy.com/fs-core': 4.68.1(tslib@2.8.1) + '@jsonjoy.com/fs-node-builtins': 4.68.1(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.68.1(tslib@2.8.1) + '@jsonjoy.com/fs-print': 4.68.1(tslib@2.8.1) + '@jsonjoy.com/fs-snapshot': 4.68.1(tslib@2.8.1) glob-to-regex.js: 1.2.0(tslib@2.8.1) thingies: 2.6.1(tslib@2.8.1) tslib: 2.8.1 - '@jsonjoy.com/fs-print@4.64.0(tslib@2.8.1)': + '@jsonjoy.com/fs-print@4.68.1(tslib@2.8.1)': dependencies: - '@jsonjoy.com/fs-node-utils': 4.64.0(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.68.1(tslib@2.8.1) tree-dump: 1.1.0(tslib@2.8.1) tslib: 2.8.1 - '@jsonjoy.com/fs-snapshot@4.64.0(tslib@2.8.1)': + '@jsonjoy.com/fs-snapshot@4.68.1(tslib@2.8.1)': dependencies: '@jsonjoy.com/buffers': 17.67.0(tslib@2.8.1) - '@jsonjoy.com/fs-node-utils': 4.64.0(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.68.1(tslib@2.8.1) '@jsonjoy.com/json-pack': 17.67.0(tslib@2.8.1) '@jsonjoy.com/util': 17.67.0(tslib@2.8.1) tslib: 2.8.1 @@ -10298,9 +10408,9 @@ snapshots: '@leichtgewicht/ip-codec@2.0.5': {} - '@listr2/prompt-adapter-inquirer@4.2.5(@inquirer/prompts@8.5.2(@types/node@22.20.1))(@types/node@22.20.1)(listr2@11.0.0)': + '@listr2/prompt-adapter-inquirer@4.2.5(@inquirer/prompts@8.6.0(@types/node@22.20.1))(@types/node@22.20.1)(listr2@11.0.0)': dependencies: - '@inquirer/prompts': 8.5.2(@types/node@22.20.1) + '@inquirer/prompts': 8.6.0(@types/node@22.20.1) '@inquirer/type': 4.0.7(@types/node@22.20.1) listr2: 11.0.0 transitivePeerDependencies: @@ -10438,20 +10548,6 @@ snapshots: '@napi-rs/nice-win32-x64-msvc': 1.1.1 optional: true - '@napi-rs/wasm-runtime@1.2.2(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)': - dependencies: - '@emnapi/core': 1.11.2 - '@emnapi/runtime': 1.11.2 - '@tybys/wasm-util': 0.10.3 - optional: true - - '@napi-rs/wasm-runtime@1.2.2(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3)': - dependencies: - '@emnapi/core': 2.0.0-alpha.3 - '@emnapi/runtime': 2.0.0-alpha.3 - '@tybys/wasm-util': 0.10.3 - optional: true - '@noble/hashes@1.4.0': {} '@nodelib/fs.scandir@2.1.5': @@ -10466,13 +10562,13 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 - '@octokit/auth-app@8.2.0': + '@octokit/auth-app@8.3.0': dependencies: '@octokit/auth-oauth-app': 9.0.4 '@octokit/auth-oauth-user': 6.0.3 - '@octokit/request': 10.0.13 + '@octokit/request': 10.0.15 '@octokit/request-error': 7.1.1 - '@octokit/types': 16.0.0 + '@octokit/types': 17.0.0 toad-cache: 3.7.4 universal-github-app-jwt: 2.2.2 universal-user-agent: 7.0.3 @@ -10481,34 +10577,34 @@ snapshots: dependencies: '@octokit/auth-oauth-device': 8.0.4 '@octokit/auth-oauth-user': 6.0.3 - '@octokit/request': 10.0.13 + '@octokit/request': 10.0.15 '@octokit/types': 17.0.0 universal-user-agent: 7.0.3 '@octokit/auth-oauth-device@8.0.4': dependencies: - '@octokit/oauth-methods': 6.0.3 - '@octokit/request': 10.0.13 + '@octokit/oauth-methods': 6.0.4 + '@octokit/request': 10.0.15 '@octokit/types': 17.0.0 universal-user-agent: 7.0.3 '@octokit/auth-oauth-user@6.0.3': dependencies: '@octokit/auth-oauth-device': 8.0.4 - '@octokit/oauth-methods': 6.0.3 - '@octokit/request': 10.0.13 + '@octokit/oauth-methods': 6.0.4 + '@octokit/request': 10.0.15 '@octokit/types': 17.0.0 universal-user-agent: 7.0.3 '@octokit/auth-token@6.0.0': {} - '@octokit/core@7.0.6': + '@octokit/core@7.0.7': dependencies: '@octokit/auth-token': 6.0.0 - '@octokit/graphql': 9.0.3 - '@octokit/request': 10.0.13 + '@octokit/graphql': 9.0.4 + '@octokit/request': 10.0.15 '@octokit/request-error': 7.1.1 - '@octokit/types': 16.0.0 + '@octokit/types': 17.0.0 before-after-hook: 4.0.0 universal-user-agent: 7.0.3 @@ -10522,18 +10618,18 @@ snapshots: graphql: 16.14.2 graphql-tag: 2.12.7(graphql@16.14.2) - '@octokit/graphql@9.0.3': + '@octokit/graphql@9.0.4': dependencies: - '@octokit/request': 10.0.13 - '@octokit/types': 16.0.0 + '@octokit/request': 10.0.15 + '@octokit/types': 17.0.0 universal-user-agent: 7.0.3 '@octokit/oauth-authorization-url@8.0.0': {} - '@octokit/oauth-methods@6.0.3': + '@octokit/oauth-methods@6.0.4': dependencies: '@octokit/oauth-authorization-url': 8.0.0 - '@octokit/request': 10.0.13 + '@octokit/request': 10.0.15 '@octokit/request-error': 7.1.1 '@octokit/types': 17.0.0 @@ -10541,53 +10637,49 @@ snapshots: '@octokit/openapi-types@28.0.0': {} - '@octokit/plugin-paginate-rest@14.0.0(@octokit/core@7.0.6)': + '@octokit/plugin-paginate-rest@14.0.0(@octokit/core@7.0.7)': dependencies: - '@octokit/core': 7.0.6 + '@octokit/core': 7.0.7 '@octokit/types': 16.0.0 - '@octokit/plugin-paginate-rest@15.0.0(@octokit/core@7.0.6)': + '@octokit/plugin-paginate-rest@15.0.0(@octokit/core@7.0.7)': dependencies: - '@octokit/core': 7.0.6 + '@octokit/core': 7.0.7 '@octokit/types': 17.0.0 - '@octokit/plugin-request-log@6.0.0(@octokit/core@7.0.6)': + '@octokit/plugin-request-log@6.0.0(@octokit/core@7.0.7)': dependencies: - '@octokit/core': 7.0.6 + '@octokit/core': 7.0.7 - '@octokit/plugin-rest-endpoint-methods@17.0.0(@octokit/core@7.0.6)': + '@octokit/plugin-rest-endpoint-methods@17.0.0(@octokit/core@7.0.7)': dependencies: - '@octokit/core': 7.0.6 + '@octokit/core': 7.0.7 '@octokit/types': 16.0.0 - '@octokit/plugin-rest-endpoint-methods@18.0.0(@octokit/core@7.0.6)': + '@octokit/plugin-rest-endpoint-methods@18.0.0(@octokit/core@7.0.7)': dependencies: - '@octokit/core': 7.0.6 + '@octokit/core': 7.0.7 '@octokit/types': 17.0.0 - '@octokit/request-error@7.1.0': - dependencies: - '@octokit/types': 16.0.0 - '@octokit/request-error@7.1.1': dependencies: '@octokit/types': 17.0.0 - '@octokit/request@10.0.13': + '@octokit/request@10.0.15': dependencies: '@octokit/endpoint': 11.0.4 '@octokit/request-error': 7.1.1 '@octokit/types': 17.0.0 - content-type: 2.0.0 - json-with-bigint: 3.5.10 + content-type: 3.0.0 + json-with-bigint: 3.5.12 universal-user-agent: 7.0.3 '@octokit/rest@22.0.1': dependencies: - '@octokit/core': 7.0.6 - '@octokit/plugin-paginate-rest': 14.0.0(@octokit/core@7.0.6) - '@octokit/plugin-request-log': 6.0.0(@octokit/core@7.0.6) - '@octokit/plugin-rest-endpoint-methods': 17.0.0(@octokit/core@7.0.6) + '@octokit/core': 7.0.7 + '@octokit/plugin-paginate-rest': 14.0.0(@octokit/core@7.0.7) + '@octokit/plugin-request-log': 6.0.0(@octokit/core@7.0.7) + '@octokit/plugin-rest-endpoint-methods': 17.0.0(@octokit/core@7.0.7) '@octokit/types@16.0.0': dependencies: @@ -10619,71 +10711,64 @@ snapshots: '@opentelemetry/semantic-conventions@1.43.0': {} - '@oxc-parser/binding-android-arm-eabi@0.142.0': + '@oxc-parser/binding-android-arm-eabi@0.146.0': optional: true - '@oxc-parser/binding-android-arm64@0.142.0': + '@oxc-parser/binding-android-arm64@0.146.0': optional: true - '@oxc-parser/binding-darwin-arm64@0.142.0': + '@oxc-parser/binding-darwin-arm64@0.146.0': optional: true - '@oxc-parser/binding-darwin-x64@0.142.0': + '@oxc-parser/binding-darwin-x64@0.146.0': optional: true - '@oxc-parser/binding-freebsd-x64@0.142.0': + '@oxc-parser/binding-freebsd-x64@0.146.0': optional: true - '@oxc-parser/binding-linux-arm-gnueabihf@0.142.0': + '@oxc-parser/binding-linux-arm-gnueabihf@0.146.0': optional: true - '@oxc-parser/binding-linux-arm-musleabihf@0.142.0': + '@oxc-parser/binding-linux-arm-musleabihf@0.146.0': optional: true - '@oxc-parser/binding-linux-arm64-gnu@0.142.0': + '@oxc-parser/binding-linux-arm64-gnu@0.146.0': optional: true - '@oxc-parser/binding-linux-arm64-musl@0.142.0': + '@oxc-parser/binding-linux-arm64-musl@0.146.0': optional: true - '@oxc-parser/binding-linux-ppc64-gnu@0.142.0': + '@oxc-parser/binding-linux-ppc64-gnu@0.146.0': optional: true - '@oxc-parser/binding-linux-riscv64-gnu@0.142.0': + '@oxc-parser/binding-linux-riscv64-gnu@0.146.0': optional: true - '@oxc-parser/binding-linux-riscv64-musl@0.142.0': + '@oxc-parser/binding-linux-riscv64-musl@0.146.0': optional: true - '@oxc-parser/binding-linux-s390x-gnu@0.142.0': + '@oxc-parser/binding-linux-s390x-gnu@0.146.0': optional: true - '@oxc-parser/binding-linux-x64-gnu@0.142.0': + '@oxc-parser/binding-linux-x64-gnu@0.146.0': optional: true - '@oxc-parser/binding-linux-x64-musl@0.142.0': + '@oxc-parser/binding-linux-x64-musl@0.146.0': optional: true - '@oxc-parser/binding-openharmony-arm64@0.142.0': + '@oxc-parser/binding-openharmony-arm64@0.146.0': optional: true - '@oxc-parser/binding-wasm32-wasi@0.142.0': - dependencies: - '@emnapi/core': 1.11.2 - '@emnapi/runtime': 1.11.2 - '@napi-rs/wasm-runtime': 1.2.2(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2) + '@oxc-parser/binding-win32-arm64-msvc@0.146.0': optional: true - '@oxc-parser/binding-win32-arm64-msvc@0.142.0': + '@oxc-parser/binding-win32-ia32-msvc@0.146.0': optional: true - '@oxc-parser/binding-win32-ia32-msvc@0.142.0': + '@oxc-parser/binding-win32-x64-msvc@0.146.0': optional: true - '@oxc-parser/binding-win32-x64-msvc@0.142.0': - optional: true - - '@oxc-project/types@0.142.0': {} + '@oxc-project/types@0.146.0': {} '@parcel/watcher-android-arm64@2.6.0': optional: true @@ -10740,80 +10825,79 @@ snapshots: '@parcel/watcher-linux-x64-musl': 2.6.0 '@parcel/watcher-win32-arm64': 2.6.0 '@parcel/watcher-win32-x64': 2.6.0 - optional: true - '@peculiar/asn1-cms@2.8.0': + '@peculiar/asn1-cms@2.9.4': dependencies: - '@peculiar/asn1-schema': 2.8.0 - '@peculiar/asn1-x509': 2.8.0 - '@peculiar/asn1-x509-attr': 2.8.0 + '@peculiar/asn1-schema': 2.9.4 + '@peculiar/asn1-x509': 2.9.4 + '@peculiar/asn1-x509-attr': 2.9.4 asn1js: 3.0.10 tslib: 2.8.1 - '@peculiar/asn1-csr@2.8.0': + '@peculiar/asn1-csr@2.9.4': dependencies: - '@peculiar/asn1-schema': 2.8.0 - '@peculiar/asn1-x509': 2.8.0 + '@peculiar/asn1-schema': 2.9.4 + '@peculiar/asn1-x509': 2.9.4 asn1js: 3.0.10 tslib: 2.8.1 - '@peculiar/asn1-ecc@2.8.0': + '@peculiar/asn1-ecc@2.9.4': dependencies: - '@peculiar/asn1-schema': 2.8.0 - '@peculiar/asn1-x509': 2.8.0 + '@peculiar/asn1-schema': 2.9.4 + '@peculiar/asn1-x509': 2.9.4 asn1js: 3.0.10 tslib: 2.8.1 - '@peculiar/asn1-pfx@2.8.0': + '@peculiar/asn1-pfx@2.9.4': dependencies: - '@peculiar/asn1-cms': 2.8.0 - '@peculiar/asn1-pkcs8': 2.8.0 - '@peculiar/asn1-rsa': 2.8.0 - '@peculiar/asn1-schema': 2.8.0 + '@peculiar/asn1-cms': 2.9.4 + '@peculiar/asn1-pkcs8': 2.9.4 + '@peculiar/asn1-rsa': 2.9.4 + '@peculiar/asn1-schema': 2.9.4 asn1js: 3.0.10 tslib: 2.8.1 - '@peculiar/asn1-pkcs8@2.8.0': + '@peculiar/asn1-pkcs8@2.9.4': dependencies: - '@peculiar/asn1-schema': 2.8.0 - '@peculiar/asn1-x509': 2.8.0 + '@peculiar/asn1-schema': 2.9.4 + '@peculiar/asn1-x509': 2.9.4 asn1js: 3.0.10 tslib: 2.8.1 - '@peculiar/asn1-pkcs9@2.8.0': + '@peculiar/asn1-pkcs9@2.9.4': dependencies: - '@peculiar/asn1-cms': 2.8.0 - '@peculiar/asn1-pfx': 2.8.0 - '@peculiar/asn1-pkcs8': 2.8.0 - '@peculiar/asn1-schema': 2.8.0 - '@peculiar/asn1-x509': 2.8.0 - '@peculiar/asn1-x509-attr': 2.8.0 + '@peculiar/asn1-cms': 2.9.4 + '@peculiar/asn1-pfx': 2.9.4 + '@peculiar/asn1-pkcs8': 2.9.4 + '@peculiar/asn1-schema': 2.9.4 + '@peculiar/asn1-x509': 2.9.4 + '@peculiar/asn1-x509-attr': 2.9.4 asn1js: 3.0.10 tslib: 2.8.1 - '@peculiar/asn1-rsa@2.8.0': + '@peculiar/asn1-rsa@2.9.4': dependencies: - '@peculiar/asn1-schema': 2.8.0 - '@peculiar/asn1-x509': 2.8.0 + '@peculiar/asn1-schema': 2.9.4 + '@peculiar/asn1-x509': 2.9.4 asn1js: 3.0.10 tslib: 2.8.1 - '@peculiar/asn1-schema@2.8.0': + '@peculiar/asn1-schema@2.9.4': dependencies: '@peculiar/utils': 2.0.3 asn1js: 3.0.10 tslib: 2.8.1 - '@peculiar/asn1-x509-attr@2.8.0': + '@peculiar/asn1-x509-attr@2.9.4': dependencies: - '@peculiar/asn1-schema': 2.8.0 - '@peculiar/asn1-x509': 2.8.0 + '@peculiar/asn1-schema': 2.9.4 + '@peculiar/asn1-x509': 2.9.4 asn1js: 3.0.10 tslib: 2.8.1 - '@peculiar/asn1-x509@2.8.0': + '@peculiar/asn1-x509@2.9.4': dependencies: - '@peculiar/asn1-schema': 2.8.0 + '@peculiar/asn1-schema': 2.9.4 '@peculiar/utils': 2.0.3 asn1js: 3.0.10 tslib: 2.8.1 @@ -10824,13 +10908,13 @@ snapshots: '@peculiar/x509@1.14.3': dependencies: - '@peculiar/asn1-cms': 2.8.0 - '@peculiar/asn1-csr': 2.8.0 - '@peculiar/asn1-ecc': 2.8.0 - '@peculiar/asn1-pkcs9': 2.8.0 - '@peculiar/asn1-rsa': 2.8.0 - '@peculiar/asn1-schema': 2.8.0 - '@peculiar/asn1-x509': 2.8.0 + '@peculiar/asn1-cms': 2.9.4 + '@peculiar/asn1-csr': 2.9.4 + '@peculiar/asn1-ecc': 2.9.4 + '@peculiar/asn1-pkcs9': 2.9.4 + '@peculiar/asn1-rsa': 2.9.4 + '@peculiar/asn1-schema': 2.9.4 + '@peculiar/asn1-x509': 2.9.4 pvtsutils: 1.3.6 reflect-metadata: 0.2.2 tslib: 2.8.1 @@ -10881,69 +10965,65 @@ snapshots: '@protobufjs/utf8@1.1.2': {} - '@puppeteer/browsers@3.0.6': + '@puppeteer/browsers@3.2.1': dependencies: - modern-tar: 0.7.7 + modern-tar: 0.8.4 yargs: 18.1.0 - '@rolldown/binding-android-arm64@1.2.1': + '@rolldown/binding-android-arm-eabi@1.2.5': optional: true - '@rolldown/binding-darwin-arm64@1.2.1': + '@rolldown/binding-android-arm64@1.2.5': optional: true - '@rolldown/binding-darwin-x64@1.2.1': + '@rolldown/binding-darwin-arm64@1.2.5': optional: true - '@rolldown/binding-freebsd-x64@1.2.1': + '@rolldown/binding-darwin-x64@1.2.5': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.2.1': + '@rolldown/binding-freebsd-x64@1.2.5': optional: true - '@rolldown/binding-linux-arm64-gnu@1.2.1': + '@rolldown/binding-linux-arm-gnueabihf@1.2.5': optional: true - '@rolldown/binding-linux-arm64-musl@1.2.1': + '@rolldown/binding-linux-arm64-gnu@1.2.5': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.2.1': + '@rolldown/binding-linux-arm64-musl@1.2.5': optional: true - '@rolldown/binding-linux-s390x-gnu@1.2.1': + '@rolldown/binding-linux-ppc64-gnu@1.2.5': optional: true - '@rolldown/binding-linux-x64-gnu@1.2.1': + '@rolldown/binding-linux-s390x-gnu@1.2.5': optional: true - '@rolldown/binding-linux-x64-musl@1.2.1': + '@rolldown/binding-linux-x64-gnu@1.2.5': optional: true - '@rolldown/binding-openharmony-arm64@1.2.1': + '@rolldown/binding-linux-x64-musl@1.2.5': optional: true - '@rolldown/binding-wasm32-wasi@1.2.1': - dependencies: - '@emnapi/core': 2.0.0-alpha.3 - '@emnapi/runtime': 2.0.0-alpha.3 - '@napi-rs/wasm-runtime': 1.2.2(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3) + '@rolldown/binding-openharmony-arm64@1.2.5': optional: true - '@rolldown/binding-win32-arm64-msvc@1.2.1': + '@rolldown/binding-win32-arm64-msvc@1.2.5': optional: true - '@rolldown/binding-win32-x64-msvc@1.2.1': + '@rolldown/binding-win32-x64-msvc@1.2.5': optional: true '@rolldown/pluginutils@1.0.1': {} - '@rollup/plugin-alias@6.0.0(rollup@4.62.4)': + '@rollup/plugin-alias@6.0.0(rollup@4.62.5)': optionalDependencies: - rollup: 4.62.4 + rollup: 4.62.5 - '@rollup/plugin-commonjs@29.0.3(rollup@4.62.4)': + '@rollup/plugin-commonjs@29.0.3(rollup@4.62.5)': dependencies: - '@rollup/pluginutils': 5.4.0(rollup@4.62.4) + '@rollup/pluginutils': 5.4.0(rollup@4.62.5) commondir: 1.0.1 estree-walker: 2.0.2 fdir: 6.5.0(picomatch@4.0.5) @@ -10951,108 +11031,108 @@ snapshots: magic-string: 0.30.21 picomatch: 4.0.5 optionalDependencies: - rollup: 4.62.4 + rollup: 4.62.5 - '@rollup/plugin-json@6.1.0(rollup@4.62.4)': + '@rollup/plugin-json@6.1.0(rollup@4.62.5)': dependencies: - '@rollup/pluginutils': 5.4.0(rollup@4.62.4) + '@rollup/pluginutils': 5.4.0(rollup@4.62.5) optionalDependencies: - rollup: 4.62.4 + rollup: 4.62.5 - '@rollup/plugin-node-resolve@16.0.3(rollup@4.62.4)': + '@rollup/plugin-node-resolve@16.0.3(rollup@4.62.5)': dependencies: - '@rollup/pluginutils': 5.4.0(rollup@4.62.4) + '@rollup/pluginutils': 5.4.0(rollup@4.62.5) '@types/resolve': 1.20.2 deepmerge: 4.3.1 is-module: 1.0.0 resolve: 1.22.12 optionalDependencies: - rollup: 4.62.4 + rollup: 4.62.5 - '@rollup/pluginutils@5.4.0(rollup@4.62.4)': + '@rollup/pluginutils@5.4.0(rollup@4.62.5)': dependencies: '@types/estree': 1.0.9 estree-walker: 2.0.2 picomatch: 4.0.5 optionalDependencies: - rollup: 4.62.4 + rollup: 4.62.5 - '@rollup/rollup-android-arm-eabi@4.62.4': + '@rollup/rollup-android-arm-eabi@4.62.5': optional: true - '@rollup/rollup-android-arm64@4.62.4': + '@rollup/rollup-android-arm64@4.62.5': optional: true - '@rollup/rollup-darwin-arm64@4.62.4': + '@rollup/rollup-darwin-arm64@4.62.5': optional: true - '@rollup/rollup-darwin-x64@4.62.4': + '@rollup/rollup-darwin-x64@4.62.5': optional: true - '@rollup/rollup-freebsd-arm64@4.62.4': + '@rollup/rollup-freebsd-arm64@4.62.5': optional: true - '@rollup/rollup-freebsd-x64@4.62.4': + '@rollup/rollup-freebsd-x64@4.62.5': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.62.4': + '@rollup/rollup-linux-arm-gnueabihf@4.62.5': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.62.4': + '@rollup/rollup-linux-arm-musleabihf@4.62.5': optional: true - '@rollup/rollup-linux-arm64-gnu@4.62.4': + '@rollup/rollup-linux-arm64-gnu@4.62.5': optional: true - '@rollup/rollup-linux-arm64-musl@4.62.4': + '@rollup/rollup-linux-arm64-musl@4.62.5': optional: true - '@rollup/rollup-linux-loong64-gnu@4.62.4': + '@rollup/rollup-linux-loong64-gnu@4.62.5': optional: true - '@rollup/rollup-linux-loong64-musl@4.62.4': + '@rollup/rollup-linux-loong64-musl@4.62.5': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.62.4': + '@rollup/rollup-linux-ppc64-gnu@4.62.5': optional: true - '@rollup/rollup-linux-ppc64-musl@4.62.4': + '@rollup/rollup-linux-ppc64-musl@4.62.5': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.62.4': + '@rollup/rollup-linux-riscv64-gnu@4.62.5': optional: true - '@rollup/rollup-linux-riscv64-musl@4.62.4': + '@rollup/rollup-linux-riscv64-musl@4.62.5': optional: true - '@rollup/rollup-linux-s390x-gnu@4.62.4': + '@rollup/rollup-linux-s390x-gnu@4.62.5': optional: true - '@rollup/rollup-linux-x64-gnu@4.62.4': + '@rollup/rollup-linux-x64-gnu@4.62.5': optional: true - '@rollup/rollup-linux-x64-musl@4.62.4': + '@rollup/rollup-linux-x64-musl@4.62.5': optional: true - '@rollup/rollup-openbsd-x64@4.62.4': + '@rollup/rollup-openbsd-x64@4.62.5': optional: true - '@rollup/rollup-openharmony-arm64@4.62.4': + '@rollup/rollup-openharmony-arm64@4.62.5': optional: true - '@rollup/rollup-win32-arm64-msvc@4.62.4': + '@rollup/rollup-win32-arm64-msvc@4.62.5': optional: true - '@rollup/rollup-win32-ia32-msvc@4.62.4': + '@rollup/rollup-win32-ia32-msvc@4.62.5': optional: true - '@rollup/rollup-win32-x64-gnu@4.62.4': + '@rollup/rollup-win32-x64-gnu@4.62.5': optional: true - '@rollup/rollup-win32-x64-msvc@4.62.4': + '@rollup/rollup-win32-x64-msvc@4.62.5': optional: true - '@rollup/wasm-node@4.62.4': + '@rollup/wasm-node@4.62.5': dependencies: '@types/estree': 1.0.9 optionalDependencies: @@ -11075,24 +11155,19 @@ snapshots: '@standard-schema/spec@1.1.0': {} - '@stylistic/eslint-plugin@5.10.0(eslint@10.8.0(jiti@2.7.0)(supports-color@11.0.0))': + '@stylistic/eslint-plugin@5.10.0(eslint@10.9.0(jiti@2.7.0)(supports-color@11.0.0))': dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.0(jiti@2.7.0)(supports-color@11.0.0)) - '@typescript-eslint/types': 8.65.0 - eslint: 10.8.0(jiti@2.7.0)(supports-color@11.0.0) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.9.0(jiti@2.7.0)(supports-color@11.0.0)) + '@typescript-eslint/types': 8.67.0 + eslint: 10.9.0(jiti@2.7.0)(supports-color@11.0.0) eslint-visitor-keys: 4.2.1 espree: 10.4.0 estraverse: 5.3.0 picomatch: 4.0.5 - '@tony.ganchev/eslint-plugin-header@3.4.4(eslint@10.8.0(jiti@2.7.0)(supports-color@11.0.0))': + '@tony.ganchev/eslint-plugin-header@3.4.4(eslint@10.9.0(jiti@2.7.0)(supports-color@11.0.0))': dependencies: - eslint: 10.8.0(jiti@2.7.0)(supports-color@11.0.0) - - '@tybys/wasm-util@0.10.3': - dependencies: - tslib: 2.8.1 - optional: true + eslint: 10.9.0(jiti@2.7.0)(supports-color@11.0.0) '@types/babel__core@7.20.5': dependencies: @@ -11186,10 +11261,6 @@ snapshots: '@types/gensync@1.0.5': {} - '@types/graceful-fs@4.1.9': - dependencies: - '@types/node': 22.20.1 - '@types/http-cache-semantics@4.2.0': {} '@types/http-errors@2.0.5': {} @@ -11219,10 +11290,10 @@ snapshots: '@types/less@3.0.8': {} - '@types/loader-utils@3.0.0(esbuild@0.28.1)': + '@types/loader-utils@3.0.0(esbuild@0.28.2)': dependencies: '@types/node': 22.20.1 - webpack: 5.109.2(esbuild@0.28.1) + webpack: 5.109.2(esbuild@0.28.2) transitivePeerDependencies: - '@minify-html/node' - '@swc/core' @@ -11280,8 +11351,6 @@ snapshots: '@types/retry@0.12.0': {} - '@types/semver@7.7.1': {} - '@types/semver@7.8.0': {} '@types/send@1.2.1': @@ -11305,11 +11374,6 @@ snapshots: '@types/urijs@1.19.26': {} - '@types/watchpack@2.4.5': - dependencies: - '@types/graceful-fs': 4.1.9 - '@types/node': 22.20.1 - '@types/which@3.0.4': {} '@types/ws@8.18.1': @@ -11324,15 +11388,15 @@ snapshots: '@types/yarnpkg__lockfile@1.1.9': {} - '@typescript-eslint/eslint-plugin@8.65.0(@typescript-eslint/parser@8.65.0(eslint@10.8.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0)(typescript@6.0.3))(eslint@10.8.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0)(typescript@6.0.3)': + '@typescript-eslint/eslint-plugin@8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.9.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0)(typescript@6.0.3))(eslint@10.9.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0)(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.65.0(eslint@10.8.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0)(typescript@6.0.3) - '@typescript-eslint/scope-manager': 8.65.0 - '@typescript-eslint/type-utils': 8.65.0(eslint@10.8.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.65.0(eslint@10.8.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0)(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.65.0 - eslint: 10.8.0(jiti@2.7.0)(supports-color@11.0.0) + '@typescript-eslint/parser': 8.67.0(eslint@10.9.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0)(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.67.0 + '@typescript-eslint/type-utils': 8.67.0(eslint@10.9.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0)(typescript@6.0.3) + '@typescript-eslint/utils': 8.67.0(eslint@10.9.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0)(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.67.0 + eslint: 10.9.0(jiti@2.7.0)(supports-color@11.0.0) ignore: 7.0.6 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@6.0.3) @@ -11340,56 +11404,56 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.65.0(eslint@10.8.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0)(typescript@6.0.3)': + '@typescript-eslint/parser@8.67.0(eslint@10.9.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0)(typescript@6.0.3)': dependencies: - '@typescript-eslint/scope-manager': 8.65.0 - '@typescript-eslint/types': 8.65.0 - '@typescript-eslint/typescript-estree': 8.65.0(supports-color@11.0.0)(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.65.0 + '@typescript-eslint/scope-manager': 8.67.0 + '@typescript-eslint/types': 8.67.0 + '@typescript-eslint/typescript-estree': 8.67.0(supports-color@11.0.0)(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.67.0 debug: 4.4.3(supports-color@11.0.0) - eslint: 10.8.0(jiti@2.7.0)(supports-color@11.0.0) + eslint: 10.9.0(jiti@2.7.0)(supports-color@11.0.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.65.0(supports-color@11.0.0)(typescript@6.0.3)': + '@typescript-eslint/project-service@8.67.0(supports-color@11.0.0)(typescript@6.0.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.65.0(typescript@6.0.3) - '@typescript-eslint/types': 8.65.0 + '@typescript-eslint/tsconfig-utils': 8.67.0(typescript@6.0.3) + '@typescript-eslint/types': 8.67.0 debug: 4.4.3(supports-color@11.0.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.65.0': + '@typescript-eslint/scope-manager@8.67.0': dependencies: - '@typescript-eslint/types': 8.65.0 - '@typescript-eslint/visitor-keys': 8.65.0 + '@typescript-eslint/types': 8.67.0 + '@typescript-eslint/visitor-keys': 8.67.0 - '@typescript-eslint/tsconfig-utils@8.65.0(typescript@6.0.3)': + '@typescript-eslint/tsconfig-utils@8.67.0(typescript@6.0.3)': dependencies: typescript: 6.0.3 - '@typescript-eslint/type-utils@8.65.0(eslint@10.8.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0)(typescript@6.0.3)': + '@typescript-eslint/type-utils@8.67.0(eslint@10.9.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0)(typescript@6.0.3)': dependencies: - '@typescript-eslint/types': 8.65.0 - '@typescript-eslint/typescript-estree': 8.65.0(supports-color@11.0.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.65.0(eslint@10.8.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0)(typescript@6.0.3) + '@typescript-eslint/types': 8.67.0 + '@typescript-eslint/typescript-estree': 8.67.0(supports-color@11.0.0)(typescript@6.0.3) + '@typescript-eslint/utils': 8.67.0(eslint@10.9.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0)(typescript@6.0.3) debug: 4.4.3(supports-color@11.0.0) - eslint: 10.8.0(jiti@2.7.0)(supports-color@11.0.0) + eslint: 10.9.0(jiti@2.7.0)(supports-color@11.0.0) ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.65.0': {} + '@typescript-eslint/types@8.67.0': {} - '@typescript-eslint/typescript-estree@8.65.0(supports-color@11.0.0)(typescript@6.0.3)': + '@typescript-eslint/typescript-estree@8.67.0(supports-color@11.0.0)(typescript@6.0.3)': dependencies: - '@typescript-eslint/project-service': 8.65.0(supports-color@11.0.0)(typescript@6.0.3) - '@typescript-eslint/tsconfig-utils': 8.65.0(typescript@6.0.3) - '@typescript-eslint/types': 8.65.0 - '@typescript-eslint/visitor-keys': 8.65.0 + '@typescript-eslint/project-service': 8.67.0(supports-color@11.0.0)(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.67.0(typescript@6.0.3) + '@typescript-eslint/types': 8.67.0 + '@typescript-eslint/visitor-keys': 8.67.0 debug: 4.4.3(supports-color@11.0.0) minimatch: 10.2.6 semver: 7.8.5 @@ -11399,49 +11463,49 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.65.0(eslint@10.8.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0)(typescript@6.0.3)': + '@typescript-eslint/utils@8.67.0(eslint@10.9.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0)(typescript@6.0.3)': dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.0(jiti@2.7.0)(supports-color@11.0.0)) - '@typescript-eslint/scope-manager': 8.65.0 - '@typescript-eslint/types': 8.65.0 - '@typescript-eslint/typescript-estree': 8.65.0(supports-color@11.0.0)(typescript@6.0.3) - eslint: 10.8.0(jiti@2.7.0)(supports-color@11.0.0) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.9.0(jiti@2.7.0)(supports-color@11.0.0)) + '@typescript-eslint/scope-manager': 8.67.0 + '@typescript-eslint/types': 8.67.0 + '@typescript-eslint/typescript-estree': 8.67.0(supports-color@11.0.0)(typescript@6.0.3) + eslint: 10.9.0(jiti@2.7.0)(supports-color@11.0.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.65.0': + '@typescript-eslint/visitor-keys@8.67.0': dependencies: - '@typescript-eslint/types': 8.65.0 + '@typescript-eslint/types': 8.67.0 eslint-visitor-keys: 5.0.1 - '@verdaccio/auth@8.1.1(supports-color@11.0.0)': + '@verdaccio/auth@8.1.2(supports-color@11.0.0)': dependencies: - '@verdaccio/config': 8.2.1(supports-color@11.0.0) - '@verdaccio/core': 8.2.1 - '@verdaccio/loaders': 8.1.1(supports-color@11.0.0) - '@verdaccio/signature': 8.1.1(supports-color@11.0.0) + '@verdaccio/config': 8.2.2(supports-color@11.0.0) + '@verdaccio/core': 8.2.2 + '@verdaccio/loaders': 8.1.2(supports-color@11.0.0) + '@verdaccio/signature': 8.1.2(supports-color@11.0.0) debug: 4.4.3(supports-color@11.0.0) lodash: 4.18.1 - verdaccio-htpasswd: 13.1.1(supports-color@11.0.0) + verdaccio-htpasswd: 13.1.2(supports-color@11.0.0) transitivePeerDependencies: - supports-color - '@verdaccio/config@8.2.1(supports-color@11.0.0)': + '@verdaccio/config@8.2.2(supports-color@11.0.0)': dependencies: - '@verdaccio/core': 8.2.1 + '@verdaccio/core': 8.2.2 debug: 4.4.3(supports-color@11.0.0) js-yaml: 5.2.2 lodash: 4.18.1 transitivePeerDependencies: - supports-color - '@verdaccio/core@8.2.1': + '@verdaccio/core@8.2.2': dependencies: ajv: 8.20.0 http-errors: 2.0.1 http-status-codes: 2.3.0 - minimatch: 10.2.5 + minimatch: 10.2.6 process-warning: 1.0.0 semver: 7.8.5 @@ -11449,27 +11513,27 @@ snapshots: dependencies: lockfile: 1.0.4 - '@verdaccio/hooks@8.1.2(supports-color@11.0.0)': + '@verdaccio/hooks@8.1.3(supports-color@11.0.0)': dependencies: - '@verdaccio/core': 8.2.1 - '@verdaccio/logger': 8.1.1(supports-color@11.0.0) + '@verdaccio/core': 8.2.2 + '@verdaccio/logger': 8.1.2(supports-color@11.0.0) debug: 4.4.3(supports-color@11.0.0) got: 15.1.0 handlebars: 4.7.9 transitivePeerDependencies: - supports-color - '@verdaccio/loaders@8.1.1(supports-color@11.0.0)': + '@verdaccio/loaders@8.1.2(supports-color@11.0.0)': dependencies: - '@verdaccio/core': 8.2.1 + '@verdaccio/core': 8.2.2 debug: 4.4.3(supports-color@11.0.0) lodash: 4.18.1 transitivePeerDependencies: - supports-color - '@verdaccio/local-storage-legacy@11.4.1(supports-color@11.0.0)': + '@verdaccio/local-storage-legacy@11.4.2(supports-color@11.0.0)': dependencies: - '@verdaccio/core': 8.2.1 + '@verdaccio/core': 8.2.2 '@verdaccio/file-locking': 13.1.0 '@verdaccio/streams': 10.3.0 debug: 4.4.3(supports-color@11.0.0) @@ -11481,9 +11545,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@verdaccio/logger-commons@8.1.1(supports-color@11.0.0)': + '@verdaccio/logger-commons@8.1.2(supports-color@11.0.0)': dependencies: - '@verdaccio/core': 8.2.1 + '@verdaccio/core': 8.2.2 '@verdaccio/logger-prettify': 8.1.0 colorette: 2.0.20 debug: 4.4.3(supports-color@11.0.0) @@ -11499,18 +11563,18 @@ snapshots: pino-abstract-transport: 1.2.0 sonic-boom: 3.8.1 - '@verdaccio/logger@8.1.1(supports-color@11.0.0)': + '@verdaccio/logger@8.1.2(supports-color@11.0.0)': dependencies: - '@verdaccio/logger-commons': 8.1.1(supports-color@11.0.0) + '@verdaccio/logger-commons': 8.1.2(supports-color@11.0.0) pino: 9.14.0 transitivePeerDependencies: - supports-color - '@verdaccio/middleware@8.1.1(supports-color@11.0.0)': + '@verdaccio/middleware@8.1.2(supports-color@11.0.0)': dependencies: - '@verdaccio/config': 8.2.1(supports-color@11.0.0) - '@verdaccio/core': 8.2.1 - '@verdaccio/url': 13.1.1(supports-color@11.0.0) + '@verdaccio/config': 8.2.2(supports-color@11.0.0) + '@verdaccio/core': 8.2.2 + '@verdaccio/url': 13.1.2(supports-color@11.0.0) debug: 4.4.3(supports-color@11.0.0) express: 4.22.2(supports-color@11.0.0) express-rate-limit: 5.5.1 @@ -11519,9 +11583,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@verdaccio/package-filter@13.1.1(supports-color@11.0.0)': + '@verdaccio/package-filter@13.2.0(supports-color@11.0.0)': dependencies: - '@verdaccio/core': 8.2.1 + '@verdaccio/core': 8.2.2 debug: 4.4.3(supports-color@11.0.0) semver: 7.8.5 transitivePeerDependencies: @@ -11534,10 +11598,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@verdaccio/signature@8.1.1(supports-color@11.0.0)': + '@verdaccio/signature@8.1.2(supports-color@11.0.0)': dependencies: - '@verdaccio/config': 8.2.1(supports-color@11.0.0) - '@verdaccio/core': 8.2.1 + '@verdaccio/config': 8.2.2(supports-color@11.0.0) + '@verdaccio/core': 8.2.2 debug: 4.4.3(supports-color@11.0.0) jsonwebtoken: 9.0.3 transitivePeerDependencies: @@ -11545,10 +11609,10 @@ snapshots: '@verdaccio/streams@10.3.0': {} - '@verdaccio/tarball@13.1.1(supports-color@11.0.0)': + '@verdaccio/tarball@13.1.2(supports-color@11.0.0)': dependencies: - '@verdaccio/core': 8.2.1 - '@verdaccio/url': 13.1.1(supports-color@11.0.0) + '@verdaccio/core': 8.2.2 + '@verdaccio/url': 13.1.2(supports-color@11.0.0) debug: 4.4.3(supports-color@11.0.0) gunzip-maybe: 1.4.2 tar-stream: 3.2.0 @@ -11558,34 +11622,34 @@ snapshots: - react-native-b4a - supports-color - '@verdaccio/ui-theme@9.0.0-next-9.23(supports-color@11.0.0)': + '@verdaccio/ui-theme@9.0.0-next-9.26(supports-color@11.0.0)': dependencies: debug: 4.4.3(supports-color@11.0.0) transitivePeerDependencies: - supports-color - '@verdaccio/url@13.1.1(supports-color@11.0.0)': + '@verdaccio/url@13.1.2(supports-color@11.0.0)': dependencies: - '@verdaccio/core': 8.2.1 + '@verdaccio/core': 8.2.2 debug: 4.4.3(supports-color@11.0.0) validator: 13.15.26 transitivePeerDependencies: - supports-color - '@verdaccio/utils@8.2.1': + '@verdaccio/utils@8.2.2': dependencies: - '@verdaccio/core': 8.2.1 + '@verdaccio/core': 8.2.2 lodash: 4.18.1 - minimatch: 10.2.5 + minimatch: 10.2.6 - '@vitejs/plugin-basic-ssl@2.3.0(vite@8.2.0(@types/node@22.20.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.8.1(supports-color@11.0.0))(sass@1.102.0)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0))': + '@vitejs/plugin-basic-ssl@2.3.0(vite@8.2.2(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.0(supports-color@11.0.0))(sass-embedded@1.103.1)(sass@1.103.1)(terser@5.50.0)(tsx@4.23.12)(yaml@2.9.0))': dependencies: - vite: 8.2.0(@types/node@22.20.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.8.1(supports-color@11.0.0))(sass@1.102.0)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0) + vite: 8.2.2(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.0(supports-color@11.0.0))(sass-embedded@1.103.1)(sass@1.103.1)(terser@5.50.0)(tsx@4.23.12)(yaml@2.9.0) - '@vitest/coverage-v8@4.1.10(vitest@4.1.10)': + '@vitest/coverage-v8@4.1.11(vitest@4.1.11)': dependencies: '@bcoe/v8-coverage': 1.0.2 - '@vitest/utils': 4.1.10 + '@vitest/utils': 4.1.11 ast-v8-to-istanbul: 1.0.5 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 @@ -11594,46 +11658,46 @@ snapshots: obug: 2.1.4 std-env: 4.2.0 tinyrainbow: 3.1.1 - vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@30.0.1)(less@4.8.1(supports-color@11.0.0))(sass@1.102.0)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0) + vitest: 4.1.11(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.0.1)(less@4.9.0(supports-color@11.0.0))(sass-embedded@1.103.1)(sass@1.103.1)(terser@5.50.0)(tsx@4.23.12)(yaml@2.9.0) - '@vitest/expect@4.1.10': + '@vitest/expect@4.1.11': dependencies: '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.3 - '@vitest/spy': 4.1.10 - '@vitest/utils': 4.1.10 + '@vitest/spy': 4.1.11 + '@vitest/utils': 4.1.11 chai: 6.2.2 tinyrainbow: 3.1.1 - '@vitest/mocker@4.1.10(vite@8.2.0(@types/node@22.20.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.8.1(supports-color@11.0.0))(sass@1.102.0)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0))': + '@vitest/mocker@4.1.11(vite@8.2.2(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.0(supports-color@11.0.0))(sass-embedded@1.103.1)(sass@1.103.1)(terser@5.50.0)(tsx@4.23.12)(yaml@2.9.0))': dependencies: - '@vitest/spy': 4.1.10 + '@vitest/spy': 4.1.11 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.2.0(@types/node@22.20.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.8.1(supports-color@11.0.0))(sass@1.102.0)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0) + vite: 8.2.2(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.0(supports-color@11.0.0))(sass-embedded@1.103.1)(sass@1.103.1)(terser@5.50.0)(tsx@4.23.12)(yaml@2.9.0) - '@vitest/pretty-format@4.1.10': + '@vitest/pretty-format@4.1.11': dependencies: tinyrainbow: 3.1.1 - '@vitest/runner@4.1.10': + '@vitest/runner@4.1.11': dependencies: - '@vitest/utils': 4.1.10 + '@vitest/utils': 4.1.11 pathe: 2.0.3 - '@vitest/snapshot@4.1.10': + '@vitest/snapshot@4.1.11': dependencies: - '@vitest/pretty-format': 4.1.10 - '@vitest/utils': 4.1.10 + '@vitest/pretty-format': 4.1.11 + '@vitest/utils': 4.1.11 magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@4.1.10': {} + '@vitest/spy@4.1.11': {} - '@vitest/utils@4.1.10': + '@vitest/utils@4.1.11': dependencies: - '@vitest/pretty-format': 4.1.10 + '@vitest/pretty-format': 4.1.11 convert-source-map: 2.0.0 tinyrainbow: 3.1.1 @@ -11713,7 +11777,7 @@ snapshots: '@webassemblyjs/ast': 1.14.1 '@xtuc/long': 4.2.2 - '@xmldom/xmldom@0.8.13': {} + '@xmldom/xmldom@0.8.15': {} '@xtuc/ieee754@1.2.0': {} @@ -11721,79 +11785,79 @@ snapshots: '@yarnpkg/lockfile@1.1.0': {} - '@yuku-codegen/binding-android-arm64@0.8.3': + '@yuku-codegen/binding-android-arm64@0.8.7': optional: true - '@yuku-codegen/binding-darwin-arm64@0.8.3': + '@yuku-codegen/binding-darwin-arm64@0.8.7': optional: true - '@yuku-codegen/binding-darwin-x64@0.8.3': + '@yuku-codegen/binding-darwin-x64@0.8.7': optional: true - '@yuku-codegen/binding-freebsd-x64@0.8.3': + '@yuku-codegen/binding-freebsd-x64@0.8.7': optional: true - '@yuku-codegen/binding-linux-arm-gnu@0.8.3': + '@yuku-codegen/binding-linux-arm-gnu@0.8.7': optional: true - '@yuku-codegen/binding-linux-arm-musl@0.8.3': + '@yuku-codegen/binding-linux-arm-musl@0.8.7': optional: true - '@yuku-codegen/binding-linux-arm64-gnu@0.8.3': + '@yuku-codegen/binding-linux-arm64-gnu@0.8.7': optional: true - '@yuku-codegen/binding-linux-arm64-musl@0.8.3': + '@yuku-codegen/binding-linux-arm64-musl@0.8.7': optional: true - '@yuku-codegen/binding-linux-x64-gnu@0.8.3': + '@yuku-codegen/binding-linux-x64-gnu@0.8.7': optional: true - '@yuku-codegen/binding-linux-x64-musl@0.8.3': + '@yuku-codegen/binding-linux-x64-musl@0.8.7': optional: true - '@yuku-codegen/binding-win32-arm64@0.8.3': + '@yuku-codegen/binding-win32-arm64@0.8.7': optional: true - '@yuku-codegen/binding-win32-x64@0.8.3': + '@yuku-codegen/binding-win32-x64@0.8.7': optional: true - '@yuku-parser/binding-android-arm64@0.8.3': + '@yuku-parser/binding-android-arm64@0.8.7': optional: true - '@yuku-parser/binding-darwin-arm64@0.8.3': + '@yuku-parser/binding-darwin-arm64@0.8.7': optional: true - '@yuku-parser/binding-darwin-x64@0.8.3': + '@yuku-parser/binding-darwin-x64@0.8.7': optional: true - '@yuku-parser/binding-freebsd-x64@0.8.3': + '@yuku-parser/binding-freebsd-x64@0.8.7': optional: true - '@yuku-parser/binding-linux-arm-gnu@0.8.3': + '@yuku-parser/binding-linux-arm-gnu@0.8.7': optional: true - '@yuku-parser/binding-linux-arm-musl@0.8.3': + '@yuku-parser/binding-linux-arm-musl@0.8.7': optional: true - '@yuku-parser/binding-linux-arm64-gnu@0.8.3': + '@yuku-parser/binding-linux-arm64-gnu@0.8.7': optional: true - '@yuku-parser/binding-linux-arm64-musl@0.8.3': + '@yuku-parser/binding-linux-arm64-musl@0.8.7': optional: true - '@yuku-parser/binding-linux-x64-gnu@0.8.3': + '@yuku-parser/binding-linux-x64-gnu@0.8.7': optional: true - '@yuku-parser/binding-linux-x64-musl@0.8.3': + '@yuku-parser/binding-linux-x64-musl@0.8.7': optional: true - '@yuku-parser/binding-win32-arm64@0.8.3': + '@yuku-parser/binding-win32-arm64@0.8.7': optional: true - '@yuku-parser/binding-win32-x64@0.8.3': + '@yuku-parser/binding-win32-x64@0.8.7': optional: true - '@yuku-toolchain/types@0.8.3': {} + '@yuku-toolchain/types@0.8.7': {} JSONStream@1.3.5: dependencies: @@ -11812,7 +11876,7 @@ snapshots: accepts@2.0.0: dependencies: mime-types: 3.0.2 - negotiator: 1.0.0 + negotiator: 1.1.0 acorn-jsx@5.3.2(acorn@8.18.0): dependencies: @@ -11858,7 +11922,7 @@ snapshots: ajv@8.20.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.5 + fast-uri: 3.1.6 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -11872,7 +11936,7 @@ snapshots: ansi-regex@5.0.1: {} - ansi-regex@6.2.2: {} + ansi-regex@6.3.0: {} ansi-styles@4.3.0: dependencies: @@ -11958,7 +12022,7 @@ snapshots: asn1js@3.0.10: dependencies: pvtsutils: 1.3.6 - pvutils: 1.1.5 + pvutils: 1.2.0 tslib: 2.8.1 assert-plus@1.0.0: {} @@ -11985,13 +12049,13 @@ snapshots: atomic-sleep@1.0.0: {} - autoprefixer@10.5.4(postcss@8.5.25): + autoprefixer@10.5.4(postcss@8.5.26): dependencies: - browserslist: 4.28.7 - caniuse-lite: 1.0.30001806 + browserslist: 4.28.8 + caniuse-lite: 1.0.30001809 fraction.js: 5.3.4 picocolors: 1.1.1 - postcss: 8.5.25 + postcss: 8.5.26 postcss-value-parser: 4.2.0 available-typed-arrays@1.0.7: @@ -12004,18 +12068,18 @@ snapshots: b4a@1.8.1: {} - babel-loader@10.1.1(@babel/core@8.0.1)(webpack@5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3)): + babel-loader@10.1.1(@babel/core@8.0.1)(webpack@5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3)): dependencies: '@babel/core': 8.0.1 find-up: 5.0.0 optionalDependencies: - webpack: 5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3) + webpack: 5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3) babel-plugin-polyfill-corejs3@1.0.0(@babel/core@8.0.1): dependencies: '@babel/core': 8.0.1 '@babel/helper-define-polyfill-provider': 1.0.0(@babel/core@8.0.1) - core-js-compat: 3.49.0 + core-js-compat: 3.50.0 balanced-match@1.0.2: {} @@ -12023,12 +12087,12 @@ snapshots: bare-events@2.9.1: {} - bare-fs@4.7.4: + bare-fs@4.8.0: dependencies: bare-events: 2.9.1 bare-path: 3.1.1 bare-stream: 2.13.3(bare-events@2.9.1) - bare-url: 2.4.7 + bare-url: 2.5.2 fast-fifo: 1.3.2 transitivePeerDependencies: - bare-abort-controller @@ -12046,7 +12110,7 @@ snapshots: transitivePeerDependencies: - react-native-b4a - bare-url@2.4.7: + bare-url@2.5.2: dependencies: bare-path: 3.1.1 @@ -12054,7 +12118,7 @@ snapshots: base64id@2.0.0: {} - baseline-browser-mapping@2.11.11: {} + baseline-browser-mapping@2.11.18: {} batch@0.6.1: {} @@ -12072,9 +12136,9 @@ snapshots: domhandler: 5.0.3 htmlparser2: 10.1.0 picocolors: 1.1.1 - postcss: 8.5.25 + postcss: 8.5.26 postcss-media-query-parser: 0.2.3 - postcss-safe-parser: 7.0.1(postcss@8.5.25) + postcss-safe-parser: 7.0.1(postcss@8.5.26) before-after-hook@4.0.0: {} @@ -12110,7 +12174,7 @@ snapshots: body-parser@2.3.0(supports-color@11.0.0): dependencies: bytes: 3.1.2 - content-type: 2.0.0 + content-type: 2.1.0 debug: 4.4.3(supports-color@11.0.0) http-errors: 2.0.1 iconv-lite: 0.7.3 @@ -12158,7 +12222,7 @@ snapshots: async-each-series: 0.1.1 chalk: 4.1.2 connect-history-api-fallback: 1.6.0 - immutable: 3.8.3 + immutable: 3.8.4 server-destroy: 1.0.1 socket.io-client: 4.8.3(bufferutil@4.1.0)(supports-color@11.0.0)(utf-8-validate@6.0.6) stream-throttle: 0.1.3 @@ -12183,7 +12247,7 @@ snapshots: fresh: 0.5.2 fs-extra: 3.0.1 http-proxy: 1.18.1(debug@4.4.3(supports-color@11.0.0)) - immutable: 3.8.3 + immutable: 3.8.4 micromatch: 4.0.8 opn: 5.3.0 portscanner: 2.2.0 @@ -12207,13 +12271,13 @@ snapshots: dependencies: pako: 0.2.9 - browserslist@4.28.7: + browserslist@4.28.8: dependencies: - baseline-browser-mapping: 2.11.11 - caniuse-lite: 1.0.30001806 - electron-to-chromium: 1.5.399 - node-releases: 2.0.51 - update-browserslist-db: 1.2.3(browserslist@4.28.7) + baseline-browser-mapping: 2.11.18 + caniuse-lite: 1.0.30001809 + electron-to-chromium: 1.5.412 + node-releases: 2.0.53 + update-browserslist-db: 1.3.1(browserslist@4.28.8) bs-recipes@1.3.4: {} @@ -12271,7 +12335,7 @@ snapshots: callsites@3.1.0: {} - caniuse-lite@1.0.30001806: {} + caniuse-lite@1.0.30001809: {} caseless@0.12.0: {} @@ -12308,13 +12372,13 @@ snapshots: chokidar@5.0.0: dependencies: - readdirp: 5.0.0 + readdirp: 5.1.1 chrome-trace-event@1.0.4: {} - chromium-bidi@17.0.2(devtools-protocol@0.0.1653615): + chromium-bidi@17.0.2(devtools-protocol@0.0.1666840): dependencies: - devtools-protocol: 0.0.1653615 + devtools-protocol: 0.0.1666840 mitt: 3.0.1 zod: 3.25.76 @@ -12375,6 +12439,8 @@ snapshots: colorette@2.0.20: {} + colorjs.io@0.7.1: {} + colors@1.4.0: {} combined-stream@1.0.8: @@ -12439,11 +12505,13 @@ snapshots: content-type@1.0.5: {} - content-type@2.0.0: {} + content-type@2.1.0: {} + + content-type@3.0.0: {} conventional-commits-filter@6.0.1: {} - conventional-commits-parser@7.1.1: + conventional-commits-parser@7.1.2: dependencies: '@simple-libs/stream-utils': 2.0.0 argue-cli: 3.1.0 @@ -12462,18 +12530,18 @@ snapshots: dependencies: is-what: 4.1.16 - copy-webpack-plugin@14.0.0(webpack@5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3)): + copy-webpack-plugin@14.0.0(webpack@5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3)): dependencies: glob-parent: 6.0.2 normalize-path: 3.0.0 schema-utils: 4.3.3 - serialize-javascript: 7.0.7 + serialize-javascript: 7.1.0 tinyglobby: 0.2.17 - webpack: 5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3) + webpack: 5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3) - core-js-compat@3.49.0: + core-js-compat@3.50.0: dependencies: - browserslist: 4.28.7 + browserslist: 4.28.8 core-util-is@1.0.2: {} @@ -12499,18 +12567,18 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - css-loader@7.1.4(webpack@5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3)): + css-loader@7.1.4(webpack@5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3)): dependencies: - icss-utils: 5.1.0(postcss@8.5.25) - postcss: 8.5.25 - postcss-modules-extract-imports: 3.1.0(postcss@8.5.25) - postcss-modules-local-by-default: 4.2.0(postcss@8.5.25) - postcss-modules-scope: 3.2.1(postcss@8.5.25) - postcss-modules-values: 4.0.0(postcss@8.5.25) + icss-utils: 5.1.0(postcss@8.5.26) + postcss: 8.5.26 + postcss-modules-extract-imports: 3.1.0(postcss@8.5.26) + postcss-modules-local-by-default: 4.2.0(postcss@8.5.26) + postcss-modules-scope: 3.2.1(postcss@8.5.26) + postcss-modules-values: 4.0.0(postcss@8.5.26) postcss-value-parser: 4.2.0 semver: 7.8.5 optionalDependencies: - webpack: 5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3) + webpack: 5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3) css-select@6.0.0: dependencies: @@ -12602,7 +12670,7 @@ snapshots: default-browser-id@5.0.1: {} - default-browser@5.5.0: + default-browser@5.5.1: dependencies: bundle-name: 4.1.0 default-browser-id: 5.0.1 @@ -12637,7 +12705,7 @@ snapshots: dev-ip@1.0.1: {} - devtools-protocol@0.0.1653615: {} + devtools-protocol@0.0.1666840: {} di@0.0.1: {} @@ -12723,7 +12791,7 @@ snapshots: ejs@6.0.1: {} - electron-to-chromium@1.5.399: {} + electron-to-chromium@1.5.412: {} emoji-regex@10.6.0: {} @@ -12752,7 +12820,7 @@ snapshots: '@socket.io/component-emitter': 3.1.2 debug: 4.4.3(supports-color@11.0.0) engine.io-parser: 5.2.3 - ws: 8.21.1(bufferutil@4.1.0)(utf-8-validate@6.0.6) + ws: 8.21.3(bufferutil@4.1.0)(utf-8-validate@6.0.6) xmlhttprequest-ssl: 2.1.2 transitivePeerDependencies: - bufferutil @@ -12772,7 +12840,7 @@ snapshots: cors: 2.8.6 debug: 4.4.3(supports-color@11.0.0) engine.io-parser: 5.2.3 - ws: 8.21.1(bufferutil@4.1.0)(utf-8-validate@6.0.6) + ws: 8.21.3(bufferutil@4.1.0)(utf-8-validate@6.0.6) transitivePeerDependencies: - bufferutil - supports-color @@ -12879,7 +12947,7 @@ snapshots: es-errors@1.3.0: {} - es-module-lexer@2.3.1: {} + es-module-lexer@2.3.2: {} es-object-atoms@1.1.2: dependencies: @@ -12905,36 +12973,36 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 - esbuild-wasm@0.28.1: {} + esbuild-wasm@0.28.2: {} - esbuild@0.28.1: + esbuild@0.28.2: optionalDependencies: - '@esbuild/aix-ppc64': 0.28.1 - '@esbuild/android-arm': 0.28.1 - '@esbuild/android-arm64': 0.28.1 - '@esbuild/android-x64': 0.28.1 - '@esbuild/darwin-arm64': 0.28.1 - '@esbuild/darwin-x64': 0.28.1 - '@esbuild/freebsd-arm64': 0.28.1 - '@esbuild/freebsd-x64': 0.28.1 - '@esbuild/linux-arm': 0.28.1 - '@esbuild/linux-arm64': 0.28.1 - '@esbuild/linux-ia32': 0.28.1 - '@esbuild/linux-loong64': 0.28.1 - '@esbuild/linux-mips64el': 0.28.1 - '@esbuild/linux-ppc64': 0.28.1 - '@esbuild/linux-riscv64': 0.28.1 - '@esbuild/linux-s390x': 0.28.1 - '@esbuild/linux-x64': 0.28.1 - '@esbuild/netbsd-arm64': 0.28.1 - '@esbuild/netbsd-x64': 0.28.1 - '@esbuild/openbsd-arm64': 0.28.1 - '@esbuild/openbsd-x64': 0.28.1 - '@esbuild/openharmony-arm64': 0.28.1 - '@esbuild/sunos-x64': 0.28.1 - '@esbuild/win32-arm64': 0.28.1 - '@esbuild/win32-ia32': 0.28.1 - '@esbuild/win32-x64': 0.28.1 + '@esbuild/aix-ppc64': 0.28.2 + '@esbuild/android-arm': 0.28.2 + '@esbuild/android-arm64': 0.28.2 + '@esbuild/android-x64': 0.28.2 + '@esbuild/darwin-arm64': 0.28.2 + '@esbuild/darwin-x64': 0.28.2 + '@esbuild/freebsd-arm64': 0.28.2 + '@esbuild/freebsd-x64': 0.28.2 + '@esbuild/linux-arm': 0.28.2 + '@esbuild/linux-arm64': 0.28.2 + '@esbuild/linux-ia32': 0.28.2 + '@esbuild/linux-loong64': 0.28.2 + '@esbuild/linux-mips64el': 0.28.2 + '@esbuild/linux-ppc64': 0.28.2 + '@esbuild/linux-riscv64': 0.28.2 + '@esbuild/linux-s390x': 0.28.2 + '@esbuild/linux-x64': 0.28.2 + '@esbuild/netbsd-arm64': 0.28.2 + '@esbuild/netbsd-x64': 0.28.2 + '@esbuild/openbsd-arm64': 0.28.2 + '@esbuild/openbsd-x64': 0.28.2 + '@esbuild/openharmony-arm64': 0.28.2 + '@esbuild/sunos-x64': 0.28.2 + '@esbuild/win32-arm64': 0.28.2 + '@esbuild/win32-ia32': 0.28.2 + '@esbuild/win32-x64': 0.28.2 escalade@3.2.0: {} @@ -12942,9 +13010,9 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-prettier@10.1.8(eslint@10.8.0(jiti@2.7.0)(supports-color@11.0.0)): + eslint-config-prettier@10.1.8(eslint@10.9.0(jiti@2.7.0)(supports-color@11.0.0)): dependencies: - eslint: 10.8.0(jiti@2.7.0)(supports-color@11.0.0) + eslint: 10.9.0(jiti@2.7.0)(supports-color@11.0.0) eslint-import-resolver-node@0.3.10(supports-color@11.0.0): dependencies: @@ -12954,17 +13022,17 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.14.0(@typescript-eslint/parser@8.65.0(eslint@10.8.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0)(typescript@6.0.3))(eslint-import-resolver-node@0.3.10(supports-color@11.0.0))(eslint@10.8.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0): + eslint-module-utils@2.14.0(@typescript-eslint/parser@8.67.0(eslint@10.9.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0)(typescript@6.0.3))(eslint-import-resolver-node@0.3.10(supports-color@11.0.0))(eslint@10.9.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0): dependencies: debug: 3.2.7(supports-color@11.0.0) optionalDependencies: - '@typescript-eslint/parser': 8.65.0(eslint@10.8.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0)(typescript@6.0.3) - eslint: 10.8.0(jiti@2.7.0)(supports-color@11.0.0) + '@typescript-eslint/parser': 8.67.0(eslint@10.9.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0)(typescript@6.0.3) + eslint: 10.9.0(jiti@2.7.0)(supports-color@11.0.0) eslint-import-resolver-node: 0.3.10(supports-color@11.0.0) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.65.0(eslint@10.8.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0)(typescript@6.0.3))(eslint@10.8.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.67.0(eslint@10.9.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0)(typescript@6.0.3))(eslint@10.9.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -12973,9 +13041,9 @@ snapshots: array.prototype.flatmap: 1.3.3 debug: 3.2.7(supports-color@11.0.0) doctrine: 2.1.0 - eslint: 10.8.0(jiti@2.7.0)(supports-color@11.0.0) + eslint: 10.9.0(jiti@2.7.0)(supports-color@11.0.0) eslint-import-resolver-node: 0.3.10(supports-color@11.0.0) - eslint-module-utils: 2.14.0(@typescript-eslint/parser@8.65.0(eslint@10.8.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0)(typescript@6.0.3))(eslint-import-resolver-node@0.3.10(supports-color@11.0.0))(eslint@10.8.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0) + eslint-module-utils: 2.14.0(@typescript-eslint/parser@8.67.0(eslint@10.9.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0)(typescript@6.0.3))(eslint-import-resolver-node@0.3.10(supports-color@11.0.0))(eslint@10.9.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0) hasown: 2.0.4 is-core-module: 2.16.2 is-glob: 4.0.3 @@ -12987,7 +13055,7 @@ snapshots: string.prototype.trimend: 1.0.10 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.65.0(eslint@10.8.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0)(typescript@6.0.3) + '@typescript-eslint/parser': 8.67.0(eslint@10.9.0(jiti@2.7.0)(supports-color@11.0.0))(supports-color@11.0.0)(typescript@6.0.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -13011,9 +13079,9 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@10.8.0(jiti@2.7.0)(supports-color@11.0.0): + eslint@10.9.0(jiti@2.7.0)(supports-color@11.0.0): dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.0(jiti@2.7.0)(supports-color@11.0.0)) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.9.0(jiti@2.7.0)(supports-color@11.0.0)) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.23.5(supports-color@11.0.0) '@eslint/config-helpers': 0.7.0 @@ -13195,7 +13263,7 @@ snapshots: dependencies: fast-string-truncated-width: 3.0.3 - fast-uri@3.1.5: {} + fast-uri@3.1.6: {} fast-wrap-ansi@0.2.2: dependencies: @@ -13285,36 +13353,36 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 - firebase@12.16.0: + firebase@12.17.1: dependencies: - '@firebase/ai': 2.13.1(@firebase/app-types@0.9.5)(@firebase/app@0.15.1) - '@firebase/analytics': 0.10.22(@firebase/app@0.15.1) - '@firebase/analytics-compat': 0.2.28(@firebase/app-compat@0.5.15)(@firebase/app@0.15.1) - '@firebase/app': 0.15.1 - '@firebase/app-check': 0.12.0(@firebase/app@0.15.1) - '@firebase/app-check-compat': 0.4.5(@firebase/app-compat@0.5.15)(@firebase/app@0.15.1) - '@firebase/app-compat': 0.5.15 + '@firebase/ai': 2.14.0(@firebase/app-types@0.9.5)(@firebase/app@0.16.0) + '@firebase/analytics': 0.10.23(@firebase/app@0.16.0) + '@firebase/analytics-compat': 0.2.29(@firebase/app-compat@0.5.16)(@firebase/app@0.16.0) + '@firebase/app': 0.16.0 + '@firebase/app-check': 0.13.0(@firebase/app@0.16.0) + '@firebase/app-check-compat': 0.4.6(@firebase/app-compat@0.5.16)(@firebase/app@0.16.0) + '@firebase/app-compat': 0.5.16 '@firebase/app-types': 0.9.5 - '@firebase/auth': 1.13.3(@firebase/app@0.15.1) - '@firebase/auth-compat': 0.6.8(@firebase/app-compat@0.5.15)(@firebase/app-types@0.9.5)(@firebase/app@0.15.1) - '@firebase/data-connect': 0.7.1(@firebase/app@0.15.1) - '@firebase/database': 1.1.3 - '@firebase/database-compat': 2.1.4 - '@firebase/firestore': 4.16.0(@firebase/app@0.15.1) - '@firebase/firestore-compat': 0.4.11(@firebase/app-compat@0.5.15)(@firebase/app-types@0.9.5)(@firebase/app@0.15.1) - '@firebase/functions': 0.13.5(@firebase/app@0.15.1) - '@firebase/functions-compat': 0.4.5(@firebase/app-compat@0.5.15)(@firebase/app@0.15.1) - '@firebase/installations': 0.6.22(@firebase/app@0.15.1) - '@firebase/installations-compat': 0.2.22(@firebase/app-compat@0.5.15)(@firebase/app-types@0.9.5)(@firebase/app@0.15.1) - '@firebase/messaging': 0.13.0(@firebase/app@0.15.1) - '@firebase/messaging-compat': 0.2.27(@firebase/app-compat@0.5.15)(@firebase/app@0.15.1) - '@firebase/performance': 0.7.12(@firebase/app@0.15.1) - '@firebase/performance-compat': 0.2.25(@firebase/app-compat@0.5.15)(@firebase/app@0.15.1) - '@firebase/remote-config': 0.9.0(@firebase/app@0.15.1) - '@firebase/remote-config-compat': 0.2.27(@firebase/app-compat@0.5.15)(@firebase/app@0.15.1) - '@firebase/storage': 0.14.3(@firebase/app@0.15.1) - '@firebase/storage-compat': 0.4.3(@firebase/app-compat@0.5.15)(@firebase/app-types@0.9.5)(@firebase/app@0.15.1) - '@firebase/util': 1.15.1 + '@firebase/auth': 1.13.4(@firebase/app@0.16.0) + '@firebase/auth-compat': 0.6.9(@firebase/app-compat@0.5.16)(@firebase/app-types@0.9.5)(@firebase/app@0.16.0) + '@firebase/data-connect': 0.7.3(@firebase/app@0.16.0) + '@firebase/database': 1.1.4 + '@firebase/database-compat': 2.1.6(@firebase/app-compat@0.5.16)(@firebase/app@0.16.0) + '@firebase/firestore': 4.17.0(@firebase/app@0.16.0) + '@firebase/firestore-compat': 0.4.12(@firebase/app-compat@0.5.16)(@firebase/app-types@0.9.5)(@firebase/app@0.16.0) + '@firebase/functions': 0.13.6(@firebase/app@0.16.0) + '@firebase/functions-compat': 0.4.6(@firebase/app-compat@0.5.16)(@firebase/app@0.16.0) + '@firebase/installations': 0.6.23(@firebase/app@0.16.0) + '@firebase/installations-compat': 0.2.23(@firebase/app-compat@0.5.16)(@firebase/app-types@0.9.5)(@firebase/app@0.16.0) + '@firebase/messaging': 0.13.1(@firebase/app@0.16.0) + '@firebase/messaging-compat': 0.2.28(@firebase/app-compat@0.5.16)(@firebase/app@0.16.0) + '@firebase/performance': 0.7.13(@firebase/app@0.16.0) + '@firebase/performance-compat': 0.2.26(@firebase/app-compat@0.5.16)(@firebase/app@0.16.0) + '@firebase/remote-config': 0.9.1(@firebase/app@0.16.0) + '@firebase/remote-config-compat': 0.2.28(@firebase/app-compat@0.5.16)(@firebase/app@0.16.0) + '@firebase/storage': 0.14.4(@firebase/app@0.16.0) + '@firebase/storage-compat': 0.4.4(@firebase/app-compat@0.5.16)(@firebase/app-types@0.9.5)(@firebase/app@0.16.0) + '@firebase/util': 1.15.2 transitivePeerDependencies: - '@react-native-async-storage/async-storage' @@ -13413,7 +13481,7 @@ snapshots: transitivePeerDependencies: - supports-color - gaxios@7.3.0(supports-color@11.0.0): + gaxios@7.3.1(supports-color@11.0.0): dependencies: extend: 3.0.2 https-proxy-agent: 7.0.6(supports-color@11.0.0) @@ -13423,7 +13491,7 @@ snapshots: gcp-metadata@8.1.2(supports-color@11.0.0): dependencies: - gaxios: 7.3.0(supports-color@11.0.0) + gaxios: 7.3.1(supports-color@11.0.0) google-logging-utils: 1.1.3 json-bigint: 1.0.0 transitivePeerDependencies: @@ -13522,7 +13590,7 @@ snapshots: globals@14.0.0: {} - globals@17.9.0: {} + globals@17.11.0: {} globalthis@1.0.4: dependencies: @@ -13542,7 +13610,7 @@ snapshots: dependencies: base64-js: 1.5.1 ecdsa-sig-formatter: 1.0.11 - gaxios: 7.3.0(supports-color@11.0.0) + gaxios: 7.3.1(supports-color@11.0.0) gcp-metadata: 8.1.4(supports-color@11.0.0) google-logging-utils: 1.1.3 gtoken: 8.0.0(supports-color@11.0.0) @@ -13554,7 +13622,7 @@ snapshots: dependencies: base64-js: 1.5.1 ecdsa-sig-formatter: 1.0.11 - gaxios: 7.3.0(supports-color@11.0.0) + gaxios: 7.3.1(supports-color@11.0.0) gcp-metadata: 8.1.2(supports-color@11.0.0) google-logging-utils: 1.1.3 jws: 4.0.1 @@ -13612,7 +13680,7 @@ snapshots: gtoken@8.0.0(supports-color@11.0.0): dependencies: - gaxios: 7.3.0(supports-color@11.0.0) + gaxios: 7.3.1(supports-color@11.0.0) jws: 4.0.1 transitivePeerDependencies: - supports-color @@ -13777,9 +13845,9 @@ snapshots: dependencies: safer-buffer: 2.1.2 - icss-utils@5.1.0(postcss@8.5.25): + icss-utils@5.1.0(postcss@8.5.26): dependencies: - postcss: 8.5.25 + postcss: 8.5.26 idb@7.1.1: {} @@ -13789,7 +13857,7 @@ snapshots: ignore@7.0.6: {} - immutable@3.8.3: {} + immutable@3.8.4: {} immutable@5.1.9: {} @@ -13821,7 +13889,7 @@ snapshots: ipaddr.js@1.9.1: {} - ipaddr.js@2.4.0: {} + ipaddr.js@2.5.0: {} is-array-buffer@3.0.5: dependencies: @@ -14074,7 +14142,7 @@ snapshots: jasmine-reporters@2.5.2: dependencies: - '@xmldom/xmldom': 0.8.13 + '@xmldom/xmldom': 0.8.15 mkdirp: 1.0.4 jasmine-spec-reporter@7.0.0: @@ -14083,7 +14151,7 @@ snapshots: jasmine@6.3.0: dependencies: - '@jasminejs/reporters': 1.0.0 + '@jasminejs/reporters': 1.1.0 glob: 13.0.6 jasmine-core: 6.3.0 @@ -14111,10 +14179,10 @@ snapshots: jsdom@30.0.1: dependencies: - '@asamuzakjp/css-color': 6.0.5 + '@asamuzakjp/css-color': 6.0.7 '@asamuzakjp/dom-selector': 8.3.2 '@bramus/specificity': 2.4.2 - '@csstools/css-syntax-patches-for-csstree': 1.1.7(css-tree@3.2.1) + '@csstools/css-syntax-patches-for-csstree': 1.1.8(css-tree@3.2.1) '@exodus/bytes': 1.15.1 css-tree: 3.2.1 data-urls: 7.0.0 @@ -14126,7 +14194,7 @@ snapshots: saxes: 6.0.0 symbol-tree: 3.2.4 tough-cookie: 6.0.2 - undici: 8.9.0 + undici: 8.10.0 w3c-xmlserializer: 5.0.0 webidl-conversions: 8.0.1 whatwg-mimetype: 5.0.0 @@ -14155,7 +14223,7 @@ snapshots: json-stringify-safe@5.0.1: {} - json-with-bigint@3.5.10: {} + json-with-bigint@3.5.12: {} json5@1.0.2: dependencies: @@ -14221,7 +14289,7 @@ snapshots: transitivePeerDependencies: - supports-color - karma-jasmine-html-reporter@2.2.0(jasmine-core@6.3.0)(karma-jasmine@5.1.0(karma@6.4.4(bufferutil@4.1.0)(debug@4.4.3(supports-color@11.0.0))(supports-color@11.0.0)(utf-8-validate@6.0.6)))(karma@6.4.4(bufferutil@4.1.0)(debug@4.4.3(supports-color@11.0.0))(supports-color@11.0.0)(utf-8-validate@6.0.6)): + karma-jasmine-html-reporter@2.3.0(jasmine-core@6.3.0)(karma-jasmine@5.1.0(karma@6.4.4(bufferutil@4.1.0)(debug@4.4.3(supports-color@11.0.0))(supports-color@11.0.0)(utf-8-validate@6.0.6)))(karma@6.4.4(bufferutil@4.1.0)(debug@4.4.3(supports-color@11.0.0))(supports-color@11.0.0)(utf-8-validate@6.0.6)): dependencies: jasmine-core: 6.3.0 karma: 6.4.4(bufferutil@4.1.0)(debug@4.4.3(supports-color@11.0.0))(supports-color@11.0.0)(utf-8-validate@6.0.6) @@ -14283,14 +14351,14 @@ snapshots: picocolors: 1.1.1 shell-quote: 1.10.0 - less-loader@13.0.0(less@4.8.1(supports-color@11.0.0))(webpack@5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3)): + less-loader@13.0.0(less@4.9.0(supports-color@11.0.0))(webpack@5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3)): dependencies: '@types/less': 3.0.8 - less: 4.8.1(supports-color@11.0.0) + less: 4.9.0(supports-color@11.0.0) optionalDependencies: - webpack: 5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3) + webpack: 5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3) - less@4.8.1(supports-color@11.0.0): + less@4.9.0(supports-color@11.0.0): dependencies: copy-anything: 3.0.5 parse-node-version: 1.0.1 @@ -14300,7 +14368,7 @@ snapshots: make-dir: 5.1.0 mime: 1.6.0 needle: 3.5.0 - probe-image-size: 7.3.0(supports-color@11.0.0) + probe-image-size: 7.4.0(supports-color@11.0.0) source-map: 0.6.1 transitivePeerDependencies: - supports-color @@ -14310,11 +14378,11 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 - license-webpack-plugin@4.0.2(webpack@5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3)): + license-webpack-plugin@4.0.2(webpack@5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3)): dependencies: webpack-sources: 3.5.1 optionalDependencies: - webpack: 5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3) + webpack: 5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3) lightningcss-android-arm64@1.33.0: optional: true @@ -14375,7 +14443,7 @@ snapshots: dependencies: cli-truncate: 6.1.1 log-update: 8.0.0 - wrap-ansi: 10.0.0 + wrap-ansi: 10.0.1 lmdb@3.5.6: dependencies: @@ -14450,7 +14518,7 @@ snapshots: slice-ansi: 9.0.0 string-width: 8.2.2 strip-ansi: 7.2.0 - wrap-ansi: 10.0.0 + wrap-ansi: 10.0.1 log4js@6.9.1(supports-color@11.0.0): dependencies: @@ -14490,7 +14558,7 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - magic-string@1.1.0: + magic-string@1.2.2: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -14515,16 +14583,16 @@ snapshots: media-typer@1.1.1: {} - memfs@4.64.0(tslib@2.8.1): + memfs@4.68.1: dependencies: - '@jsonjoy.com/fs-core': 4.64.0(tslib@2.8.1) - '@jsonjoy.com/fs-fsa': 4.64.0(tslib@2.8.1) - '@jsonjoy.com/fs-node': 4.64.0(tslib@2.8.1) - '@jsonjoy.com/fs-node-builtins': 4.64.0(tslib@2.8.1) - '@jsonjoy.com/fs-node-to-fsa': 4.64.0(tslib@2.8.1) - '@jsonjoy.com/fs-node-utils': 4.64.0(tslib@2.8.1) - '@jsonjoy.com/fs-print': 4.64.0(tslib@2.8.1) - '@jsonjoy.com/fs-snapshot': 4.64.0(tslib@2.8.1) + '@jsonjoy.com/fs-core': 4.68.1(tslib@2.8.1) + '@jsonjoy.com/fs-fsa': 4.68.1(tslib@2.8.1) + '@jsonjoy.com/fs-node': 4.68.1(tslib@2.8.1) + '@jsonjoy.com/fs-node-builtins': 4.68.1(tslib@2.8.1) + '@jsonjoy.com/fs-node-to-fsa': 4.68.1(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.68.1(tslib@2.8.1) + '@jsonjoy.com/fs-print': 4.68.1(tslib@2.8.1) + '@jsonjoy.com/fs-snapshot': 4.68.1(tslib@2.8.1) '@jsonjoy.com/json-pack': 1.21.0(tslib@2.8.1) '@jsonjoy.com/util': 1.9.0(tslib@2.8.1) glob-to-regex.js: 1.2.0(tslib@2.8.1) @@ -14569,15 +14637,11 @@ snapshots: mimic-response@4.0.0: {} - mini-css-extract-plugin@2.10.2(webpack@5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3)): + mini-css-extract-plugin@2.10.2(webpack@5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3)): dependencies: schema-utils: 4.3.3 tapable: 2.3.3 - webpack: 5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3) - - minimatch@10.2.5: - dependencies: - brace-expansion: 5.0.9 + webpack: 5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3) minimatch@10.2.6: dependencies: @@ -14597,28 +14661,28 @@ snapshots: minimist@1.2.8: {} - minimizer-webpack-plugin@5.6.1(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3)(webpack@5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3)): + minimizer-webpack-plugin@5.6.1(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3)(webpack@5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3)): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 - terser: 5.49.0 - webpack: 5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3) + terser: 5.50.0 + webpack: 5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3) optionalDependencies: - esbuild: 0.28.1 + esbuild: 0.28.2 lightningcss: 1.33.0 - postcss: 8.5.25 + postcss: 8.5.26 uglify-js: 3.19.3 - minimizer-webpack-plugin@5.6.1(esbuild@0.28.1)(webpack@5.109.2(esbuild@0.28.1)): + minimizer-webpack-plugin@5.6.1(esbuild@0.28.2)(webpack@5.109.2(esbuild@0.28.2)): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 - terser: 5.49.0 - webpack: 5.109.2(esbuild@0.28.1) + terser: 5.50.0 + webpack: 5.109.2(esbuild@0.28.2) optionalDependencies: - esbuild: 0.28.1 + esbuild: 0.28.2 minipass@7.1.3: {} @@ -14632,7 +14696,7 @@ snapshots: mkdirp@1.0.4: {} - modern-tar@0.7.7: {} + modern-tar@0.8.4: {} mrmime@2.0.1: {} @@ -14670,7 +14734,7 @@ snapshots: mute-stream@3.0.0: {} - nanoid@3.3.16: {} + nanoid@3.3.18: {} natural-compare@1.4.0: {} @@ -14693,31 +14757,33 @@ snapshots: negotiator@0.6.4: {} - negotiator@1.0.0: {} + negotiator@1.1.0: + dependencies: + content-type: 2.1.0 neo-async@2.6.2: {} - ng-packagr@22.2.0-next.2(@angular/compiler-cli@22.2.0-next.0(@angular/compiler@22.2.0-next.0)(typescript@6.0.3))(supports-color@11.0.0)(tslib@2.8.1)(typescript@6.0.3): + ng-packagr@22.2.0-next.3(@angular/compiler-cli@22.2.0-next.3(@angular/compiler@22.2.0-next.3)(typescript@6.0.3))(supports-color@11.0.0)(tslib@2.8.1)(typescript@6.0.3): dependencies: '@ampproject/remapping': 2.3.0 - '@angular/compiler-cli': 22.2.0-next.0(@angular/compiler@22.2.0-next.0)(typescript@6.0.3) + '@angular/compiler-cli': 22.2.0-next.3(@angular/compiler@22.2.0-next.3)(typescript@6.0.3) ajv: 8.20.0 - browserslist: 4.28.7 + browserslist: 4.28.8 chokidar: 5.0.0 commander: 15.0.0 dependency-graph: 1.0.0 - esbuild: 0.28.1 + esbuild: 0.28.2 find-cache-directory: 6.0.0 injection-js: 2.6.1 jsonc-parser: 3.3.1 - less: 4.8.1(supports-color@11.0.0) + less: 4.9.0(supports-color@11.0.0) ora: 9.4.1 - piscina: 5.3.0 - postcss: 8.5.25 - rolldown: 1.2.1 - rolldown-plugin-dts: 0.27.14(rolldown@1.2.1)(typescript@6.0.3) + piscina: 5.3.1 + postcss: 8.5.26 + rolldown: 1.2.5 + rolldown-plugin-dts: 0.28.2(rolldown@1.2.5)(typescript@6.0.3) rxjs: 7.8.2 - sass: 1.102.0 + sass: 1.103.1 tinyglobby: 0.2.17 tslib: 2.8.1 typescript: 6.0.3 @@ -14728,7 +14794,7 @@ snapshots: - supports-color - vue-tsc - nock@14.0.16: + nock@14.0.17: dependencies: '@mswjs/interceptors': 0.41.9 json-stringify-safe: 5.0.1 @@ -14737,8 +14803,7 @@ snapshots: node-addon-api@6.1.0: optional: true - node-addon-api@7.1.1: - optional: true + node-addon-api@7.1.1: {} node-domexception@1.0.0: {} @@ -14770,7 +14835,7 @@ snapshots: node-gyp-build@4.8.4: {} - node-releases@2.0.51: {} + node-releases@2.0.53: {} normalize-path@3.0.0: {} @@ -14853,14 +14918,14 @@ snapshots: dependencies: mimic-function: 5.0.1 - open@11.0.0: + open@11.0.1: dependencies: - default-browser: 5.5.0 + default-browser: 5.5.1 define-lazy-prop: 3.0.0 is-in-ssh: 1.0.0 is-inside-container: 1.0.0 - powershell-utils: 0.1.0 - wsl-utils: 0.3.1 + powershell-utils: 0.2.0 + wsl-utils: 1.0.0 opn@5.3.0: dependencies: @@ -14898,30 +14963,29 @@ snapshots: object-keys: 1.1.1 safe-push-apply: 1.0.0 - oxc-parser@0.142.0: + oxc-parser@0.146.0: dependencies: - '@oxc-project/types': 0.142.0 + '@oxc-project/types': 0.146.0 optionalDependencies: - '@oxc-parser/binding-android-arm-eabi': 0.142.0 - '@oxc-parser/binding-android-arm64': 0.142.0 - '@oxc-parser/binding-darwin-arm64': 0.142.0 - '@oxc-parser/binding-darwin-x64': 0.142.0 - '@oxc-parser/binding-freebsd-x64': 0.142.0 - '@oxc-parser/binding-linux-arm-gnueabihf': 0.142.0 - '@oxc-parser/binding-linux-arm-musleabihf': 0.142.0 - '@oxc-parser/binding-linux-arm64-gnu': 0.142.0 - '@oxc-parser/binding-linux-arm64-musl': 0.142.0 - '@oxc-parser/binding-linux-ppc64-gnu': 0.142.0 - '@oxc-parser/binding-linux-riscv64-gnu': 0.142.0 - '@oxc-parser/binding-linux-riscv64-musl': 0.142.0 - '@oxc-parser/binding-linux-s390x-gnu': 0.142.0 - '@oxc-parser/binding-linux-x64-gnu': 0.142.0 - '@oxc-parser/binding-linux-x64-musl': 0.142.0 - '@oxc-parser/binding-openharmony-arm64': 0.142.0 - '@oxc-parser/binding-wasm32-wasi': 0.142.0 - '@oxc-parser/binding-win32-arm64-msvc': 0.142.0 - '@oxc-parser/binding-win32-ia32-msvc': 0.142.0 - '@oxc-parser/binding-win32-x64-msvc': 0.142.0 + '@oxc-parser/binding-android-arm-eabi': 0.146.0 + '@oxc-parser/binding-android-arm64': 0.146.0 + '@oxc-parser/binding-darwin-arm64': 0.146.0 + '@oxc-parser/binding-darwin-x64': 0.146.0 + '@oxc-parser/binding-freebsd-x64': 0.146.0 + '@oxc-parser/binding-linux-arm-gnueabihf': 0.146.0 + '@oxc-parser/binding-linux-arm-musleabihf': 0.146.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.146.0 + '@oxc-parser/binding-linux-arm64-musl': 0.146.0 + '@oxc-parser/binding-linux-ppc64-gnu': 0.146.0 + '@oxc-parser/binding-linux-riscv64-gnu': 0.146.0 + '@oxc-parser/binding-linux-riscv64-musl': 0.146.0 + '@oxc-parser/binding-linux-s390x-gnu': 0.146.0 + '@oxc-parser/binding-linux-x64-gnu': 0.146.0 + '@oxc-parser/binding-linux-x64-musl': 0.146.0 + '@oxc-parser/binding-openharmony-arm64': 0.146.0 + '@oxc-parser/binding-win32-arm64-msvc': 0.146.0 + '@oxc-parser/binding-win32-ia32-msvc': 0.146.0 + '@oxc-parser/binding-win32-x64-msvc': 0.146.0 p-finally@1.0.0: {} @@ -15053,7 +15117,7 @@ snapshots: sonic-boom: 4.2.1 thread-stream: 3.2.0 - piscina@5.3.0: + piscina@5.3.1: optionalDependencies: '@napi-rs/nice': 1.1.1 @@ -15067,7 +15131,7 @@ snapshots: asn1js: 3.0.10 bytestreamjs: 2.0.1 pvtsutils: 1.3.6 - pvutils: 1.1.5 + pvutils: 1.2.0 tslib: 2.8.1 pluralize@8.0.0: {} @@ -15079,64 +15143,66 @@ snapshots: possible-typed-array-names@1.1.0: {} - postcss-loader@8.2.1(postcss@8.5.25)(typescript@6.0.3)(webpack@5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3)): + postcss-loader@8.2.1(postcss@8.5.26)(typescript@6.0.3)(webpack@5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3)): dependencies: cosmiconfig: 9.0.2(typescript@6.0.3) jiti: 2.7.0 - postcss: 8.5.25 + postcss: 8.5.26 semver: 7.8.5 optionalDependencies: - webpack: 5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3) + webpack: 5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3) transitivePeerDependencies: - typescript postcss-media-query-parser@0.2.3: {} - postcss-modules-extract-imports@3.1.0(postcss@8.5.25): + postcss-modules-extract-imports@3.1.0(postcss@8.5.26): dependencies: - postcss: 8.5.25 + postcss: 8.5.26 - postcss-modules-local-by-default@4.2.0(postcss@8.5.25): + postcss-modules-local-by-default@4.2.0(postcss@8.5.26): dependencies: - icss-utils: 5.1.0(postcss@8.5.25) - postcss: 8.5.25 - postcss-selector-parser: 7.1.4 + icss-utils: 5.1.0(postcss@8.5.26) + postcss: 8.5.26 + postcss-selector-parser: 7.1.5 postcss-value-parser: 4.2.0 - postcss-modules-scope@3.2.1(postcss@8.5.25): + postcss-modules-scope@3.2.1(postcss@8.5.26): dependencies: - postcss: 8.5.25 - postcss-selector-parser: 7.1.4 + postcss: 8.5.26 + postcss-selector-parser: 7.1.5 - postcss-modules-values@4.0.0(postcss@8.5.25): + postcss-modules-values@4.0.0(postcss@8.5.26): dependencies: - icss-utils: 5.1.0(postcss@8.5.25) - postcss: 8.5.25 + icss-utils: 5.1.0(postcss@8.5.26) + postcss: 8.5.26 - postcss-safe-parser@7.0.1(postcss@8.5.25): + postcss-safe-parser@7.0.1(postcss@8.5.26): dependencies: - postcss: 8.5.25 + postcss: 8.5.26 - postcss-selector-parser@7.1.4: + postcss-selector-parser@7.1.5: dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 postcss-value-parser@4.2.0: {} - postcss@8.5.25: + postcss@8.5.26: dependencies: - nanoid: 3.3.16 + nanoid: 3.3.18 picocolors: 1.1.1 source-map-js: 1.2.1 powershell-utils@0.1.0: {} + powershell-utils@0.2.0: {} + prelude-ls@1.2.1: {} prettier@3.9.6: {} - probe-image-size@7.3.0(supports-color@11.0.0): + probe-image-size@7.4.0(supports-color@11.0.0): dependencies: lodash.merge: 4.6.2 needle: 2.9.1(supports-color@11.0.0) @@ -15200,27 +15266,27 @@ snapshots: punycode@2.3.1: {} - puppeteer-core@25.4.0(bufferutil@4.1.0)(utf-8-validate@6.0.6): + puppeteer-core@25.8.0(bufferutil@4.1.0)(utf-8-validate@6.0.6): dependencies: - '@puppeteer/browsers': 3.0.6 - chromium-bidi: 17.0.2(devtools-protocol@0.0.1653615) - devtools-protocol: 0.0.1653615 + '@puppeteer/browsers': 3.2.1 + chromium-bidi: 17.0.2(devtools-protocol@0.0.1666840) + devtools-protocol: 0.0.1666840 typed-query-selector: 2.12.2 webdriver-bidi-protocol: 0.4.2 - ws: 8.21.1(bufferutil@4.1.0)(utf-8-validate@6.0.6) + ws: 8.21.3(bufferutil@4.1.0)(utf-8-validate@6.0.6) transitivePeerDependencies: - bufferutil - proxy-agent - utf-8-validate - yauzl - puppeteer@25.4.0(bufferutil@4.1.0)(utf-8-validate@6.0.6): + puppeteer@25.8.0(bufferutil@4.1.0)(utf-8-validate@6.0.6): dependencies: - '@puppeteer/browsers': 3.0.6 - chromium-bidi: 17.0.2(devtools-protocol@0.0.1653615) - devtools-protocol: 0.0.1653615 + '@puppeteer/browsers': 3.2.1 + chromium-bidi: 17.0.2(devtools-protocol@0.0.1666840) + devtools-protocol: 0.0.1666840 lilconfig: 3.1.3 - puppeteer-core: 25.4.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) + puppeteer-core: 25.8.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) typed-query-selector: 2.12.2 transitivePeerDependencies: - bufferutil @@ -15232,7 +15298,7 @@ snapshots: dependencies: tslib: 2.8.1 - pvutils@1.1.5: {} + pvutils@1.2.0: {} qjobs@1.2.0: {} @@ -15281,7 +15347,7 @@ snapshots: iconv-lite: 0.7.3 unpipe: 1.0.0 - re2js@0.4.3: {} + re2js@2.8.6: {} readable-stream@2.3.8: dependencies: @@ -15319,7 +15385,7 @@ snapshots: dependencies: picomatch: 2.3.2 - readdirp@5.0.0: {} + readdirp@5.1.1: {} real-require@0.2.0: {} @@ -15385,7 +15451,7 @@ snapshots: adjust-sourcemap-loader: 4.0.0 convert-source-map: 1.9.0 loader-utils: 2.0.4 - postcss: 8.5.25 + postcss: 8.5.26 source-map: 0.6.1 resolve@1.22.12: @@ -15441,40 +15507,40 @@ snapshots: dependencies: glob: 10.5.0 - rolldown-plugin-dts@0.27.14(rolldown@1.2.1)(typescript@6.0.3): + rolldown-plugin-dts@0.28.2(rolldown@1.2.5)(typescript@6.0.3): dependencies: dts-resolver: 3.0.0 get-tsconfig: 5.0.0-beta.5 obug: 2.1.4 - rolldown: 1.2.1 - yuku-ast: 0.8.3 - yuku-codegen: 0.8.3 - yuku-parser: 0.8.3 + rolldown: 1.2.5 + yuku-ast: 0.8.7 + yuku-codegen: 0.8.7 + yuku-parser: 0.8.7 optionalDependencies: typescript: 6.0.3 transitivePeerDependencies: - oxc-resolver - rolldown@1.2.1: + rolldown@1.2.5: dependencies: - '@oxc-project/types': 0.142.0 + '@oxc-project/types': 0.146.0 '@rolldown/pluginutils': 1.0.1 optionalDependencies: - '@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 + '@rolldown/binding-android-arm-eabi': 1.2.5 + '@rolldown/binding-android-arm64': 1.2.5 + '@rolldown/binding-darwin-arm64': 1.2.5 + '@rolldown/binding-darwin-x64': 1.2.5 + '@rolldown/binding-freebsd-x64': 1.2.5 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.5 + '@rolldown/binding-linux-arm64-gnu': 1.2.5 + '@rolldown/binding-linux-arm64-musl': 1.2.5 + '@rolldown/binding-linux-ppc64-gnu': 1.2.5 + '@rolldown/binding-linux-s390x-gnu': 1.2.5 + '@rolldown/binding-linux-x64-gnu': 1.2.5 + '@rolldown/binding-linux-x64-musl': 1.2.5 + '@rolldown/binding-openharmony-arm64': 1.2.5 + '@rolldown/binding-win32-arm64-msvc': 1.2.5 + '@rolldown/binding-win32-x64-msvc': 1.2.5 rollup-license-plugin@3.2.1: dependencies: @@ -15483,54 +15549,54 @@ snapshots: semver: 7.8.5 spdx-expression-validate: 2.0.0 - rollup-plugin-dts@6.4.1(rollup@4.62.4)(typescript@6.0.3): + rollup-plugin-dts@6.5.1(rollup@4.62.5)(typescript@6.0.3): dependencies: '@jridgewell/remapping': 2.3.5 '@jridgewell/sourcemap-codec': 1.5.5 convert-source-map: 2.0.0 magic-string: 0.30.21 - rollup: 4.62.4 + rollup: 4.62.5 typescript: 6.0.3 optionalDependencies: - '@babel/code-frame': 7.29.7 + '@babel/code-frame': 8.0.0 - rollup-plugin-sourcemaps2@0.5.8(@types/node@22.20.1)(rollup@4.62.4): + rollup-plugin-sourcemaps2@0.5.8(@types/node@22.20.1)(rollup@4.62.5): dependencies: - '@rollup/pluginutils': 5.4.0(rollup@4.62.4) - rollup: 4.62.4 + '@rollup/pluginutils': 5.4.0(rollup@4.62.5) + rollup: 4.62.5 optionalDependencies: '@types/node': 22.20.1 - rollup@4.62.4: + rollup@4.62.5: dependencies: '@types/estree': 1.0.9 optionalDependencies: '@napi-rs/lzma-linux-x64-gnu': 1.5.1 - '@rollup/rollup-android-arm-eabi': 4.62.4 - '@rollup/rollup-android-arm64': 4.62.4 - '@rollup/rollup-darwin-arm64': 4.62.4 - '@rollup/rollup-darwin-x64': 4.62.4 - '@rollup/rollup-freebsd-arm64': 4.62.4 - '@rollup/rollup-freebsd-x64': 4.62.4 - '@rollup/rollup-linux-arm-gnueabihf': 4.62.4 - '@rollup/rollup-linux-arm-musleabihf': 4.62.4 - '@rollup/rollup-linux-arm64-gnu': 4.62.4 - '@rollup/rollup-linux-arm64-musl': 4.62.4 - '@rollup/rollup-linux-loong64-gnu': 4.62.4 - '@rollup/rollup-linux-loong64-musl': 4.62.4 - '@rollup/rollup-linux-ppc64-gnu': 4.62.4 - '@rollup/rollup-linux-ppc64-musl': 4.62.4 - '@rollup/rollup-linux-riscv64-gnu': 4.62.4 - '@rollup/rollup-linux-riscv64-musl': 4.62.4 - '@rollup/rollup-linux-s390x-gnu': 4.62.4 - '@rollup/rollup-linux-x64-gnu': 4.62.4 - '@rollup/rollup-linux-x64-musl': 4.62.4 - '@rollup/rollup-openbsd-x64': 4.62.4 - '@rollup/rollup-openharmony-arm64': 4.62.4 - '@rollup/rollup-win32-arm64-msvc': 4.62.4 - '@rollup/rollup-win32-ia32-msvc': 4.62.4 - '@rollup/rollup-win32-x64-gnu': 4.62.4 - '@rollup/rollup-win32-x64-msvc': 4.62.4 + '@rollup/rollup-android-arm-eabi': 4.62.5 + '@rollup/rollup-android-arm64': 4.62.5 + '@rollup/rollup-darwin-arm64': 4.62.5 + '@rollup/rollup-darwin-x64': 4.62.5 + '@rollup/rollup-freebsd-arm64': 4.62.5 + '@rollup/rollup-freebsd-x64': 4.62.5 + '@rollup/rollup-linux-arm-gnueabihf': 4.62.5 + '@rollup/rollup-linux-arm-musleabihf': 4.62.5 + '@rollup/rollup-linux-arm64-gnu': 4.62.5 + '@rollup/rollup-linux-arm64-musl': 4.62.5 + '@rollup/rollup-linux-loong64-gnu': 4.62.5 + '@rollup/rollup-linux-loong64-musl': 4.62.5 + '@rollup/rollup-linux-ppc64-gnu': 4.62.5 + '@rollup/rollup-linux-ppc64-musl': 4.62.5 + '@rollup/rollup-linux-riscv64-gnu': 4.62.5 + '@rollup/rollup-linux-riscv64-musl': 4.62.5 + '@rollup/rollup-linux-s390x-gnu': 4.62.5 + '@rollup/rollup-linux-x64-gnu': 4.62.5 + '@rollup/rollup-linux-x64-musl': 4.62.5 + '@rollup/rollup-openbsd-x64': 4.62.5 + '@rollup/rollup-openharmony-arm64': 4.62.5 + '@rollup/rollup-win32-arm64-msvc': 4.62.5 + '@rollup/rollup-win32-ia32-msvc': 4.62.5 + '@rollup/rollup-win32-x64-gnu': 4.62.5 + '@rollup/rollup-win32-x64-msvc': 4.62.5 fsevents: 2.3.3 router@2.2.0(supports-color@11.0.0): @@ -15586,12 +15652,100 @@ snapshots: dependencies: truncate-utf8-bytes: 1.0.2 - sass-loader@17.0.0(sass@1.102.0)(webpack@5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3)): + sass-embedded-all-unknown@1.103.1: + dependencies: + sass: 1.103.1 + optional: true + + sass-embedded-android-arm64@1.103.1: + optional: true + + sass-embedded-android-arm@1.103.1: + optional: true + + sass-embedded-android-riscv64@1.103.1: + optional: true + + sass-embedded-android-x64@1.103.1: + optional: true + + sass-embedded-darwin-arm64@1.103.1: + optional: true + + sass-embedded-darwin-x64@1.103.1: + optional: true + + sass-embedded-linux-arm64@1.103.1: + optional: true + + sass-embedded-linux-arm@1.103.1: + optional: true + + sass-embedded-linux-musl-arm64@1.103.1: + optional: true + + sass-embedded-linux-musl-arm@1.103.1: + optional: true + + sass-embedded-linux-musl-riscv64@1.103.1: + optional: true + + sass-embedded-linux-musl-x64@1.103.1: + optional: true + + sass-embedded-linux-riscv64@1.103.1: + optional: true + + sass-embedded-linux-x64@1.103.1: + optional: true + + sass-embedded-unknown-all@1.103.1: + dependencies: + sass: 1.103.1 + optional: true + + sass-embedded-win32-arm64@1.103.1: + optional: true + + sass-embedded-win32-x64@1.103.1: + optional: true + + sass-embedded@1.103.1: + dependencies: + '@bufbuild/protobuf': 2.14.0 + colorjs.io: 0.7.1 + immutable: 5.1.9 + rxjs: 7.8.2 + supports-color: 8.1.1 + sync-child-process: 1.0.2 + varint: 6.0.0 + optionalDependencies: + sass-embedded-all-unknown: 1.103.1 + sass-embedded-android-arm: 1.103.1 + sass-embedded-android-arm64: 1.103.1 + sass-embedded-android-riscv64: 1.103.1 + sass-embedded-android-x64: 1.103.1 + sass-embedded-darwin-arm64: 1.103.1 + sass-embedded-darwin-x64: 1.103.1 + sass-embedded-linux-arm: 1.103.1 + sass-embedded-linux-arm64: 1.103.1 + sass-embedded-linux-musl-arm: 1.103.1 + sass-embedded-linux-musl-arm64: 1.103.1 + sass-embedded-linux-musl-riscv64: 1.103.1 + sass-embedded-linux-musl-x64: 1.103.1 + sass-embedded-linux-riscv64: 1.103.1 + sass-embedded-linux-x64: 1.103.1 + sass-embedded-unknown-all: 1.103.1 + sass-embedded-win32-arm64: 1.103.1 + sass-embedded-win32-x64: 1.103.1 + + sass-loader@17.0.0(sass-embedded@1.103.1)(sass@1.103.1)(webpack@5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3)): optionalDependencies: - sass: 1.102.0 - webpack: 5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3) + sass: 1.103.1 + sass-embedded: 1.103.1 + webpack: 5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3) - sass@1.102.0: + sass@1.103.1: dependencies: chokidar: 5.0.0 immutable: 5.1.9 @@ -15656,7 +15810,7 @@ snapshots: transitivePeerDependencies: - supports-color - serialize-javascript@7.0.7: {} + serialize-javascript@7.1.0: {} serve-index@1.9.2(supports-color@11.0.0): dependencies: @@ -15770,7 +15924,7 @@ snapshots: socket.io-adapter@2.5.8(bufferutil@4.1.0)(supports-color@11.0.0)(utf-8-validate@6.0.6): dependencies: debug: 4.4.3(supports-color@11.0.0) - ws: 8.21.1(bufferutil@4.1.0)(utf-8-validate@6.0.6) + ws: 8.21.3(bufferutil@4.1.0)(utf-8-validate@6.0.6) transitivePeerDependencies: - bufferutil - supports-color @@ -15818,11 +15972,11 @@ snapshots: source-map-js@1.2.1: {} - source-map-loader@5.0.0(webpack@5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3)): + source-map-loader@5.0.0(webpack@5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3)): dependencies: iconv-lite: 0.6.3 source-map-js: 1.2.1 - webpack: 5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3) + webpack: 5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3) source-map-support@0.5.21: dependencies: @@ -15994,7 +16148,7 @@ snapshots: strip-ansi@7.2.0: dependencies: - ansi-regex: 6.2.2 + ansi-regex: 6.3.0 strip-bom@3.0.0: {} @@ -16016,6 +16170,12 @@ snapshots: symbol-tree@3.2.4: {} + sync-child-process@1.0.2: + dependencies: + sync-message-port: 1.2.0 + + sync-message-port@1.2.0: {} + tagged-tag@1.0.0: {} tapable@2.3.3: {} @@ -16023,7 +16183,7 @@ snapshots: tar-stream@3.2.0: dependencies: b4a: 1.8.1 - bare-fs: 4.7.4 + bare-fs: 4.8.0 fast-fifo: 1.3.2 streamx: 2.28.0 transitivePeerDependencies: @@ -16047,7 +16207,7 @@ snapshots: - bare-abort-controller - react-native-b4a - terser@5.49.0: + terser@5.50.0: dependencies: '@jridgewell/source-map': 0.3.11 acorn: 8.18.0 @@ -16153,9 +16313,9 @@ snapshots: tslib@2.8.1: {} - tsx@4.23.1: + tsx@4.23.12: dependencies: - esbuild: 0.28.1 + esbuild: 0.28.2 optionalDependencies: fsevents: 2.3.3 @@ -16188,7 +16348,7 @@ snapshots: type-is@2.1.0: dependencies: - content-type: 2.0.0 + content-type: 2.1.0 media-typer: 1.1.1 mime-types: 3.0.2 @@ -16255,7 +16415,7 @@ snapshots: undici@6.28.0: {} - undici@8.9.0: {} + undici@8.10.0: {} unenv@1.10.0: dependencies: @@ -16296,9 +16456,9 @@ snapshots: unpipe@1.0.0: {} - update-browserslist-db@1.2.3(browserslist@4.28.7): + update-browserslist-db@1.3.1(browserslist@4.28.8): dependencies: - browserslist: 4.28.7 + browserslist: 4.28.8 escalade: 3.2.0 picocolors: 1.1.1 @@ -16322,12 +16482,14 @@ snapshots: validator@13.15.26: {} + varint@6.0.0: {} + vary@1.1.2: {} - verdaccio-audit@13.1.1(encoding@0.1.13)(supports-color@11.0.0): + verdaccio-audit@13.1.2(encoding@0.1.13)(supports-color@11.0.0): dependencies: - '@verdaccio/config': 8.2.1(supports-color@11.0.0) - '@verdaccio/core': 8.2.1 + '@verdaccio/config': 8.2.2(supports-color@11.0.0) + '@verdaccio/core': 8.2.2 express: 4.22.2(supports-color@11.0.0) https-proxy-agent: 5.0.1(supports-color@11.0.0) node-fetch: 2.6.7(encoding@0.1.13) @@ -16335,16 +16497,16 @@ snapshots: - encoding - supports-color - verdaccio-auth-memory@13.1.1(supports-color@11.0.0): + verdaccio-auth-memory@13.1.2(supports-color@11.0.0): dependencies: - '@verdaccio/core': 8.2.1 + '@verdaccio/core': 8.2.2 debug: 4.4.3(supports-color@11.0.0) transitivePeerDependencies: - supports-color - verdaccio-htpasswd@13.1.1(supports-color@11.0.0): + verdaccio-htpasswd@13.1.2(supports-color@11.0.0): dependencies: - '@verdaccio/core': 8.2.1 + '@verdaccio/core': 8.2.2 '@verdaccio/file-locking': 13.1.0 apache-md5: 1.1.8 bcryptjs: 2.4.3 @@ -16354,25 +16516,25 @@ snapshots: transitivePeerDependencies: - supports-color - verdaccio@6.9.2(encoding@0.1.13)(supports-color@11.0.0): + verdaccio@6.10.0(encoding@0.1.13)(supports-color@11.0.0): dependencies: '@cypress/request': 4.0.1 - '@verdaccio/auth': 8.1.1(supports-color@11.0.0) - '@verdaccio/config': 8.2.1(supports-color@11.0.0) - '@verdaccio/core': 8.2.1 - '@verdaccio/hooks': 8.1.2(supports-color@11.0.0) - '@verdaccio/loaders': 8.1.1(supports-color@11.0.0) - '@verdaccio/local-storage-legacy': 11.4.1(supports-color@11.0.0) - '@verdaccio/logger': 8.1.1(supports-color@11.0.0) - '@verdaccio/middleware': 8.1.1(supports-color@11.0.0) - '@verdaccio/package-filter': 13.1.1(supports-color@11.0.0) + '@verdaccio/auth': 8.1.2(supports-color@11.0.0) + '@verdaccio/config': 8.2.2(supports-color@11.0.0) + '@verdaccio/core': 8.2.2 + '@verdaccio/hooks': 8.1.3(supports-color@11.0.0) + '@verdaccio/loaders': 8.1.2(supports-color@11.0.0) + '@verdaccio/local-storage-legacy': 11.4.2(supports-color@11.0.0) + '@verdaccio/logger': 8.1.2(supports-color@11.0.0) + '@verdaccio/middleware': 8.1.2(supports-color@11.0.0) + '@verdaccio/package-filter': 13.2.0(supports-color@11.0.0) '@verdaccio/search-indexer': 8.1.0(supports-color@11.0.0) - '@verdaccio/signature': 8.1.1(supports-color@11.0.0) + '@verdaccio/signature': 8.1.2(supports-color@11.0.0) '@verdaccio/streams': 10.3.0 - '@verdaccio/tarball': 13.1.1(supports-color@11.0.0) - '@verdaccio/ui-theme': 9.0.0-next-9.23(supports-color@11.0.0) - '@verdaccio/url': 13.1.1(supports-color@11.0.0) - '@verdaccio/utils': 8.2.1 + '@verdaccio/tarball': 13.1.2(supports-color@11.0.0) + '@verdaccio/ui-theme': 9.0.0-next-9.26(supports-color@11.0.0) + '@verdaccio/url': 13.1.2(supports-color@11.0.0) + '@verdaccio/utils': 8.2.2 JSONStream: 1.3.5 async: 3.2.6 clipanion: 4.0.0-rc.4 @@ -16385,8 +16547,8 @@ snapshots: lru-cache: 7.18.3 mime: 3.0.0 semver: 7.8.5 - verdaccio-audit: 13.1.1(encoding@0.1.13)(supports-color@11.0.0) - verdaccio-htpasswd: 13.1.1(supports-color@11.0.0) + verdaccio-audit: 13.1.2(encoding@0.1.13)(supports-color@11.0.0) + verdaccio-htpasswd: 13.1.2(supports-color@11.0.0) transitivePeerDependencies: - bare-abort-controller - bare-buffer @@ -16400,34 +16562,35 @@ snapshots: core-util-is: 1.0.2 extsprintf: 1.3.0 - vite@8.2.0(@types/node@22.20.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.8.1(supports-color@11.0.0))(sass@1.102.0)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0): + vite@8.2.2(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.0(supports-color@11.0.0))(sass-embedded@1.103.1)(sass@1.103.1)(terser@5.50.0)(tsx@4.23.12)(yaml@2.9.0): dependencies: lightningcss: 1.33.0 picomatch: 4.0.5 - postcss: 8.5.25 - rolldown: 1.2.1 + postcss: 8.5.26 + rolldown: 1.2.5 tinyglobby: 0.2.17 optionalDependencies: '@types/node': 22.20.1 - esbuild: 0.28.1 + esbuild: 0.28.2 fsevents: 2.3.3 jiti: 2.7.0 - less: 4.8.1(supports-color@11.0.0) - sass: 1.102.0 - terser: 5.49.0 - tsx: 4.23.1 + less: 4.9.0(supports-color@11.0.0) + sass: 1.103.1 + sass-embedded: 1.103.1 + terser: 5.50.0 + tsx: 4.23.12 yaml: 2.9.0 - vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(@vitest/coverage-v8@4.1.10)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@30.0.1)(less@4.8.1(supports-color@11.0.0))(sass@1.102.0)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0): + vitest@4.1.11(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.0.1)(less@4.9.0(supports-color@11.0.0))(sass-embedded@1.103.1)(sass@1.103.1)(terser@5.50.0)(tsx@4.23.12)(yaml@2.9.0): dependencies: - '@vitest/expect': 4.1.10 - '@vitest/mocker': 4.1.10(vite@8.2.0(@types/node@22.20.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.8.1(supports-color@11.0.0))(sass@1.102.0)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0)) - '@vitest/pretty-format': 4.1.10 - '@vitest/runner': 4.1.10 - '@vitest/snapshot': 4.1.10 - '@vitest/spy': 4.1.10 - '@vitest/utils': 4.1.10 - es-module-lexer: 2.3.1 + '@vitest/expect': 4.1.11 + '@vitest/mocker': 4.1.11(vite@8.2.2(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.0(supports-color@11.0.0))(sass-embedded@1.103.1)(sass@1.103.1)(terser@5.50.0)(tsx@4.23.12)(yaml@2.9.0)) + '@vitest/pretty-format': 4.1.11 + '@vitest/runner': 4.1.11 + '@vitest/snapshot': 4.1.11 + '@vitest/spy': 4.1.11 + '@vitest/utils': 4.1.11 + es-module-lexer: 2.3.2 expect-type: 1.4.0 magic-string: 0.30.21 obug: 2.1.4 @@ -16438,12 +16601,12 @@ snapshots: tinyexec: 1.3.0 tinyglobby: 0.2.17 tinyrainbow: 3.1.1 - vite: 8.2.0(@types/node@22.20.1)(esbuild@0.28.1)(jiti@2.7.0)(less@4.8.1(supports-color@11.0.0))(sass@1.102.0)(terser@5.49.0)(tsx@4.23.1)(yaml@2.9.0) + vite: 8.2.2(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(less@4.9.0(supports-color@11.0.0))(sass-embedded@1.103.1)(sass@1.103.1)(terser@5.50.0)(tsx@4.23.12)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.1 '@types/node': 22.20.1 - '@vitest/coverage-v8': 4.1.10(vitest@4.1.10) + '@vitest/coverage-v8': 4.1.11(vitest@4.1.11) jsdom: 30.0.1 transitivePeerDependencies: - '@vitejs/devtools' @@ -16482,18 +16645,16 @@ snapshots: webidl-conversions@8.0.1: {} - webpack-dev-middleware@8.1.1(tslib@2.8.1)(webpack@5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3)): + webpack-dev-middleware@8.1.1(webpack@5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3)): dependencies: - memfs: 4.64.0(tslib@2.8.1) + memfs: 4.68.1 mime-types: 3.0.2 range-parser: 1.3.0 schema-utils: 4.3.3 optionalDependencies: - webpack: 5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3) - transitivePeerDependencies: - - tslib + webpack: 5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3) - webpack-dev-server@6.0.0(bufferutil@4.1.0)(supports-color@11.0.0)(tslib@2.8.1)(utf-8-validate@6.0.6)(webpack@5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3)): + webpack-dev-server@6.0.0(bufferutil@4.1.0)(supports-color@11.0.0)(utf-8-validate@6.0.6)(webpack@5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3)): dependencies: '@types/bonjour': 3.5.13 '@types/connect-history-api-fallback': 1.5.4 @@ -16510,22 +16671,21 @@ snapshots: express: 5.2.1(supports-color@11.0.0) graceful-fs: 4.2.11 http-proxy-middleware: 4.2.0(supports-color@11.0.0) - ipaddr.js: 2.4.0 + ipaddr.js: 2.5.0 launch-editor: 2.14.1 - open: 11.0.0 + open: 11.0.1 p-retry: 8.0.0 schema-utils: 4.3.3 selfsigned: 5.5.0 serve-index: 1.9.2(supports-color@11.0.0) tinyglobby: 0.2.17 - webpack-dev-middleware: 8.1.1(tslib@2.8.1)(webpack@5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3)) - ws: 8.21.1(bufferutil@4.1.0)(utf-8-validate@6.0.6) + webpack-dev-middleware: 8.1.1(webpack@5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3)) + ws: 8.21.3(bufferutil@4.1.0)(utf-8-validate@6.0.6) optionalDependencies: - webpack: 5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3) + webpack: 5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3) transitivePeerDependencies: - bufferutil - supports-color - - tslib - utf-8-validate webpack-merge@6.0.1: @@ -16536,12 +16696,12 @@ snapshots: webpack-sources@3.5.1: {} - webpack-subresource-integrity@5.1.0(webpack@5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3)): + webpack-subresource-integrity@5.1.0(webpack@5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3)): dependencies: typed-assert: 1.0.9 - webpack: 5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3) + webpack: 5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3) - webpack@5.109.2(esbuild@0.28.1): + webpack@5.109.2(esbuild@0.28.2): dependencies: '@types/estree': 1.0.9 '@types/json-schema': 7.0.15 @@ -16549,15 +16709,15 @@ snapshots: '@webassemblyjs/wasm-edit': 1.14.1 '@webassemblyjs/wasm-parser': 1.14.1 acorn: 8.18.0 - browserslist: 4.28.7 + browserslist: 4.28.8 chrome-trace-event: 1.0.4 enhanced-resolve: 5.24.5 - es-module-lexer: 2.3.1 + es-module-lexer: 2.3.2 eslint-scope: 5.1.1 events: 3.3.0 graceful-fs: 4.2.11 mime-db: 1.54.0 - minimizer-webpack-plugin: 5.6.1(esbuild@0.28.1)(webpack@5.109.2(esbuild@0.28.1)) + minimizer-webpack-plugin: 5.6.1(esbuild@0.28.2)(webpack@5.109.2(esbuild@0.28.2)) neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.3 @@ -16577,7 +16737,7 @@ snapshots: - postcss - uglify-js - webpack@5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3): + webpack@5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3): dependencies: '@types/estree': 1.0.9 '@types/json-schema': 7.0.15 @@ -16585,15 +16745,15 @@ snapshots: '@webassemblyjs/wasm-edit': 1.14.1 '@webassemblyjs/wasm-parser': 1.14.1 acorn: 8.18.0 - browserslist: 4.28.7 + browserslist: 4.28.8 chrome-trace-event: 1.0.4 enhanced-resolve: 5.24.5 - es-module-lexer: 2.3.1 + es-module-lexer: 2.3.2 eslint-scope: 5.1.1 events: 3.3.0 graceful-fs: 4.2.11 mime-db: 1.54.0 - minimizer-webpack-plugin: 5.6.1(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3)(webpack@5.109.2(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)(uglify-js@3.19.3)) + minimizer-webpack-plugin: 5.6.1(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3)(webpack@5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3)) neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.3 @@ -16708,11 +16868,10 @@ snapshots: wordwrap@1.0.0: {} - wrap-ansi@10.0.0: + wrap-ansi@10.0.1: dependencies: ansi-styles: 6.2.3 string-width: 8.2.2 - strip-ansi: 7.2.0 wrap-ansi@7.0.0: dependencies: @@ -16734,12 +16893,12 @@ snapshots: wrappy@1.0.2: {} - ws@8.21.1(bufferutil@4.1.0)(utf-8-validate@6.0.6): + ws@8.21.3(bufferutil@4.1.0)(utf-8-validate@6.0.6): optionalDependencies: bufferutil: 4.1.0 utf-8-validate: 6.0.6 - wsl-utils@0.3.1: + wsl-utils@1.0.0: dependencies: is-wsl: 3.1.1 powershell-utils: 0.1.0 @@ -16754,6 +16913,8 @@ snapshots: xtend@4.0.2: {} + xxhash-wasm@1.1.0: {} + y18n@5.0.8: {} yallist@3.1.1: {} @@ -16799,44 +16960,44 @@ snapshots: yoctocolors@2.2.0: {} - yuku-ast@0.8.3: + yuku-ast@0.8.7: dependencies: - '@yuku-toolchain/types': 0.8.3 + '@yuku-toolchain/types': 0.8.7 - yuku-codegen@0.8.3: + yuku-codegen@0.8.7: dependencies: - '@yuku-toolchain/types': 0.8.3 + '@yuku-toolchain/types': 0.8.7 optionalDependencies: - '@yuku-codegen/binding-android-arm64': 0.8.3 - '@yuku-codegen/binding-darwin-arm64': 0.8.3 - '@yuku-codegen/binding-darwin-x64': 0.8.3 - '@yuku-codegen/binding-freebsd-x64': 0.8.3 - '@yuku-codegen/binding-linux-arm-gnu': 0.8.3 - '@yuku-codegen/binding-linux-arm-musl': 0.8.3 - '@yuku-codegen/binding-linux-arm64-gnu': 0.8.3 - '@yuku-codegen/binding-linux-arm64-musl': 0.8.3 - '@yuku-codegen/binding-linux-x64-gnu': 0.8.3 - '@yuku-codegen/binding-linux-x64-musl': 0.8.3 - '@yuku-codegen/binding-win32-arm64': 0.8.3 - '@yuku-codegen/binding-win32-x64': 0.8.3 - - yuku-parser@0.8.3: - dependencies: - '@yuku-toolchain/types': 0.8.3 - yuku-ast: 0.8.3 + '@yuku-codegen/binding-android-arm64': 0.8.7 + '@yuku-codegen/binding-darwin-arm64': 0.8.7 + '@yuku-codegen/binding-darwin-x64': 0.8.7 + '@yuku-codegen/binding-freebsd-x64': 0.8.7 + '@yuku-codegen/binding-linux-arm-gnu': 0.8.7 + '@yuku-codegen/binding-linux-arm-musl': 0.8.7 + '@yuku-codegen/binding-linux-arm64-gnu': 0.8.7 + '@yuku-codegen/binding-linux-arm64-musl': 0.8.7 + '@yuku-codegen/binding-linux-x64-gnu': 0.8.7 + '@yuku-codegen/binding-linux-x64-musl': 0.8.7 + '@yuku-codegen/binding-win32-arm64': 0.8.7 + '@yuku-codegen/binding-win32-x64': 0.8.7 + + yuku-parser@0.8.7: + dependencies: + '@yuku-toolchain/types': 0.8.7 + yuku-ast: 0.8.7 optionalDependencies: - '@yuku-parser/binding-android-arm64': 0.8.3 - '@yuku-parser/binding-darwin-arm64': 0.8.3 - '@yuku-parser/binding-darwin-x64': 0.8.3 - '@yuku-parser/binding-freebsd-x64': 0.8.3 - '@yuku-parser/binding-linux-arm-gnu': 0.8.3 - '@yuku-parser/binding-linux-arm-musl': 0.8.3 - '@yuku-parser/binding-linux-arm64-gnu': 0.8.3 - '@yuku-parser/binding-linux-arm64-musl': 0.8.3 - '@yuku-parser/binding-linux-x64-gnu': 0.8.3 - '@yuku-parser/binding-linux-x64-musl': 0.8.3 - '@yuku-parser/binding-win32-arm64': 0.8.3 - '@yuku-parser/binding-win32-x64': 0.8.3 + '@yuku-parser/binding-android-arm64': 0.8.7 + '@yuku-parser/binding-darwin-arm64': 0.8.7 + '@yuku-parser/binding-darwin-x64': 0.8.7 + '@yuku-parser/binding-freebsd-x64': 0.8.7 + '@yuku-parser/binding-linux-arm-gnu': 0.8.7 + '@yuku-parser/binding-linux-arm-musl': 0.8.7 + '@yuku-parser/binding-linux-arm64-gnu': 0.8.7 + '@yuku-parser/binding-linux-arm64-musl': 0.8.7 + '@yuku-parser/binding-linux-x64-gnu': 0.8.7 + '@yuku-parser/binding-linux-x64-musl': 0.8.7 + '@yuku-parser/binding-win32-arm64': 0.8.7 + '@yuku-parser/binding-win32-x64': 0.8.7 zod@3.25.76: {} diff --git a/renovate.json b/renovate.json index 49d7aa1a4f46..ffc86a850807 100644 --- a/renovate.json +++ b/renovate.json @@ -20,6 +20,7 @@ "packages/schematics/angular/utility/latest-versions/package.json" ], "matchPackageNames": ["*"], + "matchUpdateTypes": ["minor", "patch"], "groupName": "schematics dependencies", "lockFileMaintenance": { "enabled": false diff --git a/tests/e2e/ng-snapshot/package.json b/tests/e2e/ng-snapshot/package.json index 352804392464..aa97dcca2190 100644 --- a/tests/e2e/ng-snapshot/package.json +++ b/tests/e2e/ng-snapshot/package.json @@ -2,21 +2,21 @@ "description": "snapshot versions of Angular for e2e testing", "private": true, "dependencies": { - "@angular/animations": "github:angular/animations-builds#0e7ff3554caf6d3d4f960a26febbfb6b6d756615", - "@angular/cdk": "github:angular/cdk-builds#318c54859a534fcba8c27661d229688a7e97d0b1", - "@angular/common": "github:angular/common-builds#49b4ac49211d1d070eecf4fa841416ea8ab76824", - "@angular/compiler": "github:angular/compiler-builds#9c17a8f0723623334ecef12c4f283386f3e32fb0", - "@angular/compiler-cli": "github:angular/compiler-cli-builds#c02569ab47b1a91cfbdd16d107e948fc7a3c4743", - "@angular/core": "github:angular/core-builds#809df2fb8d59910f8a1333bb35f2f22491b0ec1f", - "@angular/forms": "github:angular/forms-builds#b9b74fc3cc070af39fd56031ea4fdfb234ed712f", - "@angular/language-service": "github:angular/language-service-builds#06ad60c1eb4437e15542f4b5debe14ce57543f2c", - "@angular/localize": "github:angular/localize-builds#db7b32d752c69bbd0e35c1e6bf4426be2ddff00c", - "@angular/material": "github:angular/material-builds#8d26607e3f632b78ecb09ae93d229ed3c05776b2", - "@angular/material-moment-adapter": "github:angular/material-moment-adapter-builds#26e4a977aa158b30801e8b1f52e467624948a181", - "@angular/platform-browser": "github:angular/platform-browser-builds#f8040fb8a9e7943ca05eff8780c9b64d29519aa9", - "@angular/platform-browser-dynamic": "github:angular/platform-browser-dynamic-builds#002ce4a00a52c4987463b3ac34c9b32b11240971", - "@angular/platform-server": "github:angular/platform-server-builds#d0169883f344e678f90f9645df887debf6d6ff92", - "@angular/router": "github:angular/router-builds#ac54692555b70315f17f0c20d4ea5b326177c203", - "@angular/service-worker": "github:angular/service-worker-builds#a147d674eae2a01932d6d26a4f959e97f5c8187f" + "@angular/animations": "github:angular/animations-builds#383cdb66a8b166172c60c6247b8f62433fc036ce", + "@angular/cdk": "github:angular/cdk-builds#7e9e3a08b985ff5f6b1c28c84732f67043c750cc", + "@angular/common": "github:angular/common-builds#a2042413ef60c154437ed8395276b75cc928a4af", + "@angular/compiler": "github:angular/compiler-builds#7aad56f2fe4720c93804a7fcfb0797e9327db4c0", + "@angular/compiler-cli": "github:angular/compiler-cli-builds#49e31924991ee9186a9aea250d101820e79bb4a6", + "@angular/core": "github:angular/core-builds#6deca1aa4567d280ec97e66b0d70970f6b506332", + "@angular/forms": "github:angular/forms-builds#6d9eb4089cb8795077db81049e7702bc688c2d58", + "@angular/language-service": "github:angular/language-service-builds#8e758b0550fb522155bd24a58b291e6e7344d327", + "@angular/localize": "github:angular/localize-builds#d627d4f27929f8f5a27d1f6a349543cdaf3bfb42", + "@angular/material": "github:angular/material-builds#972986b028de9bb40e498551ff034e115d961e8b", + "@angular/material-moment-adapter": "github:angular/material-moment-adapter-builds#55d307862a1da6877154314fadf554b18da0a811", + "@angular/platform-browser": "github:angular/platform-browser-builds#8dcc3b87feb7b4479a3debf34989feb8a7dd9f16", + "@angular/platform-browser-dynamic": "github:angular/platform-browser-dynamic-builds#91e2b4643158129289707721bb72b58910efda5f", + "@angular/platform-server": "github:angular/platform-server-builds#d1e8965023f08bbe4e166749eb0e4149ab3af9dd", + "@angular/router": "github:angular/router-builds#8a1452377452f1c53f9fe37f360f5dc6ca575213", + "@angular/service-worker": "github:angular/service-worker-builds#3222e80c93a80abf873e16ad50b7b731a5878dad" } } diff --git a/tests/e2e/tests/build/incremental-watch.ts b/tests/e2e/tests/build/incremental-watch.ts index b2d1662469bb..344d2fb9e916 100644 --- a/tests/e2e/tests/build/incremental-watch.ts +++ b/tests/e2e/tests/build/incremental-watch.ts @@ -7,6 +7,32 @@ import { execAndWaitForOutputToMatch, waitForAnyProcessOutputToMatch } from '../ const buildReadyRegEx = /Application bundle generation complete\./; +async function getOutputFiles( + dir: string, + predicate: (files: string[]) => boolean, + timeout = 10_000, +): Promise { + const start = Date.now(); + while (Date.now() - start < timeout) { + try { + const files = await readdir(dir); + if (predicate(files)) { + return files; + } + } catch (err: any) { + if (err?.code !== 'ENOENT') { + throw err; + } + } + await setTimeout(50); + } + + const files = await readdir(dir); + assert(predicate(files), `Condition not met for files in ${dir}: ${JSON.stringify(files)}`); + + return files; +} + export default async function () { const usingApplicationBuilder = getGlobalVariable('argv')['esbuild']; assert( @@ -20,15 +46,17 @@ export default async function () { ['build', '--watch', '--configuration=development'], buildReadyRegEx, ); - await setTimeout(500); - const initialOutputFiles = await readdir('dist/test-project/browser'); + const initialOutputFiles = await getOutputFiles( + 'dist/test-project/browser', + (files) => files.length > 0, + ); const originalMain = await readFile('src/main.ts'); // Add a dynamic import to create an additional output chunk await Promise.all([ waitForAnyProcessOutputToMatch(buildReadyRegEx), - await writeFile( + writeFile( 'src/a.ts', ` export function sayHi() { @@ -38,8 +66,10 @@ export default async function () { ), appendToFile('src/main.ts', `\nimport('./a').then((m) => m.sayHi());`), ]); - await setTimeout(500); - const intermediateOutputFiles = await readdir('dist/test-project/browser'); + const intermediateOutputFiles = await getOutputFiles( + 'dist/test-project/browser', + (files) => files.length > initialOutputFiles.length, + ); assert( initialOutputFiles.length < intermediateOutputFiles.length, 'Additional chunks should be present', @@ -50,8 +80,10 @@ export default async function () { waitForAnyProcessOutputToMatch(buildReadyRegEx), writeFile('src/main.ts', originalMain), ]); - await setTimeout(500); - const finalOutputFiles = await readdir('dist/test-project/browser'); + const finalOutputFiles = await getOutputFiles( + 'dist/test-project/browser', + (files) => files.length === initialOutputFiles.length, + ); assert.equal( initialOutputFiles.length, finalOutputFiles.length, diff --git a/tests/e2e/tests/test/test-scripts.ts b/tests/e2e/tests/test/test-scripts.ts index 1537cdddf349..953ed52cfbd7 100644 --- a/tests/e2e/tests/test/test-scripts.ts +++ b/tests/e2e/tests/test/test-scripts.ts @@ -5,10 +5,6 @@ import { updateJsonFile } from '../../utils/project'; import { expectToFail } from '../../utils/utils'; export default async function () { - // TODO(architect): Delete this test. It is now in devkit/build-angular. - - await ng('test', '--watch=false'); - // prepare global scripts test files await writeMultipleFiles({ 'src/string-script.js': `globalThis.stringScriptGlobal = 'string-scripts.js';`, diff --git a/tests/e2e/tests/vite/reuse-dep-optimization-cache.ts b/tests/e2e/tests/vite/reuse-dep-optimization-cache.ts index 56ecdfee8cd0..07be3f2a911b 100644 --- a/tests/e2e/tests/vite/reuse-dep-optimization-cache.ts +++ b/tests/e2e/tests/vite/reuse-dep-optimization-cache.ts @@ -1,29 +1,20 @@ import assert from 'node:assert'; import { findFreePort } from '../../utils/network'; -import { - execAndWaitForOutputToMatch, - killAllProcesses, - ng, - waitForAnyProcessOutputToMatch, -} from '../../utils/process'; +import { execAndWaitForOutputToMatch, killAllProcesses, ng } from '../../utils/process'; export default async function () { await ng('cache', 'clean'); await ng('cache', 'on'); const port = await findFreePort(); - const serveReady = execAndWaitForOutputToMatch( + await execAndWaitForOutputToMatch( 'ng', ['serve', '--port', `${port}`], - /Application bundle generation complete/, + /dependencies optimized/, // Use CI:0 to force caching { ...process.env, DEBUG: 'vite:deps', CI: '0', NO_COLOR: 'true' }, ); - // Note: Don't await `serveReady` before, as otherwise we might not see - // the dependencies optimized output. There is some debouncing for `ng serve` - // going on that could cause this. - await Promise.all([serveReady, waitForAnyProcessOutputToMatch(/dependencies optimized/, 10_000)]); const response = await fetch(`http://localhost:${port}/main.js`); assert(response.ok, `Expected 'response.ok' to be 'true'.`); diff --git a/tests/e2e/tests/vitest/larger-project.ts b/tests/e2e/tests/vitest/larger-project.ts index 90bb283f2d8a..7b8db953c30c 100644 --- a/tests/e2e/tests/vitest/larger-project.ts +++ b/tests/e2e/tests/vitest/larger-project.ts @@ -1,7 +1,9 @@ -import { ng } from '../../utils/process'; -import { applyVitestBuilder } from '../../utils/vitest'; import assert from 'node:assert'; +import { mkdir, writeFile } from 'node:fs/promises'; +import { dirname } from 'node:path'; import { installPackage } from '../../utils/packages'; +import { ng } from '../../utils/process'; +import { applyVitestBuilder } from '../../utils/vitest'; export default async function () { await applyVitestBuilder(); @@ -39,31 +41,110 @@ export default async function () { } async function generateArtifactsInBatches(artifactCount: number): Promise { - const BATCH_SIZE = 5; - let commands: Promise[] = []; + const files: { [path: string]: string } = {}; for (let i = 0; i < artifactCount; i++) { const type = i % 3; const name = `test-artifact-${i}`; - let generateType: string; switch (type) { case 0: - generateType = 'component'; + files[`src/app/${name}/${name}.ts`] = ` +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-${name}', + template: '', +}) +export class TestArtifact${i}Component {} +`; + files[`src/app/${name}/${name}.spec.ts`] = ` +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { TestArtifact${i}Component } from './${name}'; + +describe('TestArtifact${i}Component', () => { + let component: TestArtifact${i}Component; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [TestArtifact${i}Component], + }).compileComponents(); + + fixture = TestBed.createComponent(TestArtifact${i}Component); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); +`; break; case 1: - generateType = 'service'; + files[`src/app/${name}.ts`] = ` +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root', +}) +export class TestArtifact${i}Service {} +`; + files[`src/app/${name}.spec.ts`] = ` +import { TestBed } from '@angular/core/testing'; +import { TestArtifact${i}Service } from './${name}'; + +describe('TestArtifact${i}Service', () => { + let service: TestArtifact${i}Service; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(TestArtifact${i}Service); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); +`; break; default: - generateType = 'pipe'; - break; - } + files[`src/app/${name}-pipe.ts`] = ` +import { Pipe, PipeTransform } from '@angular/core'; - commands.push(ng('generate', generateType, name, '--skip-tests=false')); +@Pipe({ + name: 'testArtifact${i}', +}) +export class TestArtifact${i}Pipe implements PipeTransform { + transform(value: unknown): unknown { + return null; + } +} +`; + files[`src/app/${name}-pipe.spec.ts`] = ` +import { TestArtifact${i}Pipe } from './${name}-pipe'; - if (commands.length === BATCH_SIZE || i === artifactCount - 1) { - await Promise.all(commands); - commands = []; +describe('TestArtifact${i}Pipe', () => { + it('create an instance', () => { + const pipe = new TestArtifact${i}Pipe(); + expect(pipe).toBeTruthy(); + }); +}); +`; + break; } } + + const entries = Object.entries(files); + const CONCURRENCY_LIMIT = 100; + for (let i = 0; i < entries.length; i += CONCURRENCY_LIMIT) { + const chunk = entries.slice(i, i + CONCURRENCY_LIMIT); + await Promise.all( + chunk.map(async ([filePath, content]) => { + await mkdir(dirname(filePath), { recursive: true }); + await writeFile(filePath, content.trim()); + }), + ); + } } diff --git a/tests/e2e/utils/puppeteer.ts b/tests/e2e/utils/puppeteer.ts index d33411938639..ae8aae45761a 100644 --- a/tests/e2e/utils/puppeteer.ts +++ b/tests/e2e/utils/puppeteer.ts @@ -40,7 +40,7 @@ export async function executeBrowserTest(options: BrowserTestOptions = {}) { const browser = await launch({ executablePath: process.env['CHROME_BIN'], headless: true, - args: ['--no-sandbox'], + args: ['--no-sandbox', '--disable-dev-shm-usage', '--disable-gpu'], }); try { const page = await browser.newPage();