diff --git a/.github/workflows/assistant-to-the-branch-manager.yml b/.github/workflows/assistant-to-the-branch-manager.yml index c624b9aa858e..d32cd27e86d8 100644 --- a/.github/workflows/assistant-to-the-branch-manager.yml +++ b/.github/workflows/assistant-to-the-branch-manager.yml @@ -17,6 +17,6 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - - uses: angular/dev-infra/github-actions/branch-manager@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + - uses: angular/dev-infra/github-actions/branch-manager@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc with: angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 165b143eb20d..9235a0e18f71 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@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/bazel/setup@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - 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@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/bazel/setup@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/bazel/configure-remote@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc 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@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/bazel/setup@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/bazel/configure-remote@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc 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@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - name: Install node modules run: pnpm install --frozen-lockfile - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/bazel/setup@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/bazel/configure-remote@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc 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@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/bazel/setup@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/bazel/configure-remote@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc 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@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - 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@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - name: Install node modules run: pnpm install --frozen-lockfile - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/bazel/setup@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/bazel/configure-remote@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc 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@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - name: Install node modules run: pnpm install --frozen-lockfile - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/bazel/setup@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/bazel/configure-remote@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc 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@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - name: Install node modules run: pnpm install --frozen-lockfile - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/bazel/setup@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/bazel/configure-remote@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc with: google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }} - name: Run E2E Browser tests @@ -244,11 +244,11 @@ jobs: CIRCLE_BRANCH: ${{ github.ref_name }} steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - name: Install node modules run: pnpm install --frozen-lockfile - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/bazel/setup@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - run: pnpm admin snapshots --verbose env: SNAPSHOT_BUILDS_GITHUB_TOKEN: ${{ secrets.SNAPSHOT_BUILDS_GITHUB_TOKEN }} diff --git a/.github/workflows/dev-infra.yml b/.github/workflows/dev-infra.yml index 9451bdbf330f..e2931fd90072 100644 --- a/.github/workflows/dev-infra.yml +++ b/.github/workflows/dev-infra.yml @@ -15,21 +15,21 @@ jobs: if: github.event_name == 'pull_request_target' runs-on: ubuntu-latest steps: - - uses: angular/dev-infra/github-actions/labeling/pull-request@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + - uses: angular/dev-infra/github-actions/labeling/pull-request@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc 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@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + - uses: angular/dev-infra/github-actions/post-approval-changes@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc 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@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + - uses: angular/dev-infra/github-actions/labeling/issue@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc with: angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }} google-generative-ai-key: ${{ secrets.GOOGLE_GENERATIVE_AI_KEY }} diff --git a/.github/workflows/feature-requests.yml b/.github/workflows/feature-requests.yml index 779a81c01f6a..a3c99b4b471b 100644 --- a/.github/workflows/feature-requests.yml +++ b/.github/workflows/feature-requests.yml @@ -16,6 +16,6 @@ jobs: if: github.repository == 'angular/angular-cli' runs-on: ubuntu-latest steps: - - uses: angular/dev-infra/github-actions/feature-request@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + - uses: angular/dev-infra/github-actions/feature-request@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc with: angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }} diff --git a/.github/workflows/perf.yml b/.github/workflows/perf.yml index 7591c75d92b3..b1671b229d8a 100644 --- a/.github/workflows/perf.yml +++ b/.github/workflows/perf.yml @@ -23,7 +23,7 @@ jobs: workflows: ${{ steps.workflows.outputs.workflows }} steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - name: Install node modules run: pnpm install --frozen-lockfile - id: workflows @@ -38,9 +38,9 @@ jobs: workflow: ${{ fromJSON(needs.list.outputs.workflows) }} steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/bazel/setup@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - 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 08af52b06b5c..11330223c837 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -34,9 +34,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/bazel/setup@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - name: Setup ESLint Caching uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 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@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/linting/licenses@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc build: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/bazel/setup@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/bazel/configure-remote@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - 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@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/bazel/setup@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/bazel/configure-remote@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - 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@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - name: Install node modules run: pnpm install --frozen-lockfile - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/bazel/setup@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/bazel/configure-remote@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - 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@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/bazel/setup@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/bazel/configure-remote@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - 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@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - 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@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - name: Install node modules run: pnpm install --frozen-lockfile - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/bazel/setup@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/bazel/configure-remote@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - 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@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - name: Install node modules run: pnpm install --frozen-lockfile - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/bazel/setup@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@2f6d3ae5b1db37b5165f200fb53f30b9330983e4 + uses: angular/dev-infra/github-actions/bazel/configure-remote@55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc - 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/CHANGELOG.md b/CHANGELOG.md index ebd79d6908e1..a6e5f434a182 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,24 @@ + + +# 21.2.6 (2026-04-01) + +### @angular/cli + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------- | +| [ea14f28cc](https://github.com/angular/angular-cli/commit/ea14f28ccfc6e5534eaef516bf1bfbe21582da04) | fix | fix sourceRoot resolution for MCP projects tool | + +### @angular/build + +| Commit | Type | Description | +| --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------------------- | +| [9136eb376](https://github.com/angular/angular-cli/commit/9136eb37630d6315891b3c881cd0ba4037c3254c) | fix | ensure transitive SCSS partial errors are tracked in watch mode | +| [8186faa11](https://github.com/angular/angular-cli/commit/8186faa117803ffb6ac8e2c4cd6ab7873502308d) | fix | ensure Vitest mock patching is executed only once | +| [107d1a9e2](https://github.com/angular/angular-cli/commit/107d1a9e26fc59c7878254e563758818866f0f6e) | fix | preserve error stack traces during prerendering | +| [b7f457253](https://github.com/angular/angular-cli/commit/b7f4572533675729e87532bdc23509feb2f3a28d) | fix | scope CHROME_BIN executable path to individual playwright instances | + + + # 21.2.5 (2026-03-27) diff --git a/MODULE.bazel b/MODULE.bazel index df7172969df6..7608ca41f96e 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -19,14 +19,14 @@ bazel_dep(name = "aspect_rules_jasmine", version = "2.0.4") bazel_dep(name = "rules_angular") git_override( module_name = "rules_angular", - commit = "af626f77ad610d1a9c47ee317af88e2c8edd66a4", + commit = "19a4a8fb4d6f035b5506ca21bbbd309ab5f5e729", remote = "https://github.com/angular/rules_angular.git", ) bazel_dep(name = "devinfra") git_override( module_name = "devinfra", - commit = "2f6d3ae5b1db37b5165f200fb53f30b9330983e4", + commit = "55af1f65dbeb0fe6a3de1d1134bd0dd8bc5e60cc", remote = "https://github.com/angular/dev-infra.git", ) @@ -111,8 +111,8 @@ use_repo( pnpm = use_extension("@aspect_rules_js//npm:extensions.bzl", "pnpm") pnpm.pnpm( name = "pnpm", - pnpm_version = "10.32.1", - pnpm_version_integrity = "sha512-pwaTjw6JrBRWtlY+q07fHR+vM2jRGR/FxZeQ6W3JGORFarLmfWE94QQ9LoyB+HMD5rQNT/7KnfFe8a1Wc0jyvg==", + pnpm_version = "10.33.0", + pnpm_version_integrity = "sha512-EFaLtKavtYyes2MNqQzJUWQXq+vT+rvmc58K55VyjaFJHp21pUTHatjrdXD1xLs9bGN7LLQb/c20f6gjyGSTGQ==", ) use_repo(pnpm, "pnpm") diff --git a/package.json b/package.json index 1b7787da3f8a..4ff49b3b7db4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@angular/devkit-repo", - "version": "21.2.5", + "version": "21.2.6", "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@10.32.1", + "packageManager": "pnpm@10.33.0", "engines": { "node": "^20.19.0 || ^22.12.0 || >=24.0.0", "npm": "Please use pnpm instead of NPM to install dependencies", "yarn": "Please use pnpm instead of Yarn to install dependencies", - "pnpm": "10.32.1" + "pnpm": "10.33.0" }, "author": "Angular Authors", "license": "MIT", diff --git a/packages/angular/build/package.json b/packages/angular/build/package.json index bca9e583729f..e04ef0c7b3a3 100644 --- a/packages/angular/build/package.json +++ b/packages/angular/build/package.json @@ -54,7 +54,7 @@ "@angular/ssr": "workspace:*", "jsdom": "28.1.0", "less": "4.4.2", - "ng-packagr": "21.2.1", + "ng-packagr": "21.2.2", "postcss": "8.5.6", "rxjs": "7.8.2", "vitest": "4.0.18" diff --git a/packages/angular/build/src/builders/application/tests/behavior/rebuild-component_styles_spec.ts b/packages/angular/build/src/builders/application/tests/behavior/rebuild-component_styles_spec.ts index 26ae35a8221f..08b683439684 100644 --- a/packages/angular/build/src/builders/application/tests/behavior/rebuild-component_styles_spec.ts +++ b/packages/angular/build/src/builders/application/tests/behavior/rebuild-component_styles_spec.ts @@ -58,5 +58,85 @@ describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { ]); }); } + + it('rebuilds component after error on rebuild from transitive import', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + watch: true, + }); + + await harness.modifyFile('src/app/app.component.ts', (content) => + content.replace('app.component.css', 'app.component.scss'), + ); + await harness.writeFile('src/app/app.component.scss', "@import './a';"); + await harness.writeFile('src/app/a.scss', '$primary: aqua;\\nh1 { color: $primary; }'); + + await harness.executeWithCases([ + async ({ result }) => { + expect(result?.success).toBe(true); + + harness.expectFile('dist/browser/main.js').content.toContain('color: aqua'); + + // Introduce a syntax error + await harness.writeFile( + 'src/app/a.scss', + 'invalid-invalid-invalid\\nh1 { color: $primary; }', + ); + }, + async ({ result }) => { + expect(result?.success).toBe(false); + + // Fix the syntax error + await harness.writeFile('src/app/a.scss', '$primary: blue;\\nh1 { color: $primary; }'); + }, + ({ result }) => { + expect(result?.success).toBe(true); + + harness.expectFile('dist/browser/main.js').content.toContain('color: blue'); + }, + ]); + }); + + it('rebuilds component after error on rebuild from deep transitive import with partials', async () => { + harness.useTarget('build', { + ...BASE_OPTIONS, + watch: true, + }); + + await harness.modifyFile('src/app/app.component.ts', (content) => + content.replace('app.component.css', 'app.component.scss'), + ); + await harness.writeFile('src/app/app.component.scss', "@import './intermediary';"); + await harness.writeFile('src/app/_intermediary.scss', "@import './partial';"); + await harness.writeFile('src/app/_partial.scss', '$primary: aqua;\\nh1 { color: $primary; }'); + + await harness.executeWithCases([ + async ({ result }) => { + expect(result?.success).toBe(true); + + harness.expectFile('dist/browser/main.js').content.toContain('color: aqua'); + + // Introduce a syntax error deeply + await harness.writeFile( + 'src/app/_partial.scss', + 'invalid-invalid-invalid\\nh1 { color: $primary; }', + ); + }, + async ({ result }) => { + expect(result?.success).toBe(false); + + // Fix the syntax error deeply + await harness.writeFile( + 'src/app/_partial.scss', + '$primary: blue;\\nh1 { color: $primary; }', + ); + }, + ({ result }) => { + expect(result?.success).toBe(true); + + harness.expectFile('dist/browser/main.js').content.toContain('color: blue'); + }, + ]); + }); }); }); diff --git a/packages/angular/build/src/builders/unit-test/runners/vitest/browser-provider.ts b/packages/angular/build/src/builders/unit-test/runners/vitest/browser-provider.ts index 503f551c15cb..0ca80f4fa60f 100644 --- a/packages/angular/build/src/builders/unit-test/runners/vitest/browser-provider.ts +++ b/packages/angular/build/src/builders/unit-test/runners/vitest/browser-provider.ts @@ -7,7 +7,11 @@ */ import { createRequire } from 'node:module'; -import type { BrowserBuiltinProvider, BrowserConfigOptions } from 'vitest/node'; +import type { + BrowserBuiltinProvider, + BrowserConfigOptions, + BrowserProviderOption, +} from 'vitest/node'; import { assertIsError } from '../../../../utils/error'; export interface BrowserConfiguration { @@ -37,7 +41,13 @@ function findBrowserProvider( return undefined; } -function normalizeBrowserName(browserName: string): { browser: string; headless: boolean } { +export interface BrowserInstanceConfiguration { + browser: string; + headless: boolean; + provider?: BrowserProviderOption; +} + +function normalizeBrowserName(browserName: string): BrowserInstanceConfiguration { // Normalize browser names to match Vitest's expectations for headless but also supports karma's names // e.g., 'ChromeHeadless' -> 'chrome', 'FirefoxHeadless' -> 'firefox' // and 'Chrome' -> 'chrome', 'Firefox' -> 'firefox'. @@ -50,6 +60,67 @@ function normalizeBrowserName(browserName: string): { browser: string; headless: }; } +/** + * Mutates the provided browser instances to apply standard headless execution + * constraints based on the chosen provider, user options, and CI environment presence. + * + * @param instances The normalized browser instances to mutate. + * @param providerName The identifier for the chosen Vitest browser provider. + * @param headless The user-provided headless configuration option. + * @param isCI Whether the current environment is running in CI. + * @returns An array of informational messages generated during evaluation. + */ +export function applyHeadlessConfiguration( + instances: BrowserInstanceConfiguration[], + providerName: BrowserBuiltinProvider | undefined, + headless: boolean | undefined, + isCI: boolean, +): string[] { + const messages: string[] = []; + + if (providerName === 'preview') { + instances.forEach((instance) => { + // Preview mode only supports headed execution + instance.headless = false; + }); + + if (headless) { + messages.push('The "headless" option is ignored when using the "preview" provider.'); + } + } else if (headless !== undefined) { + if (headless) { + const allHeadlessByDefault = isCI || instances.every((i) => i.headless); + if (allHeadlessByDefault) { + messages.push( + 'The "headless" option is unnecessary as all browsers are already configured to run in headless mode.', + ); + } + } + + instances.forEach((instance) => { + instance.headless = headless; + }); + } else if (isCI) { + instances.forEach((instance) => { + instance.headless = true; + }); + } + + return messages; +} + +/** + * Resolves and configures the Vitest browser provider for the unit test builder. + * Dynamically discovers and imports the necessary provider (Playwright, WebdriverIO, or Preview), + * maps the requested browser instances, and applies environment-specific execution logic. + * + * @param browsers An array of requested browser names (e.g., 'chrome', 'firefox'). + * @param headless User-provided configuration for headless execution. + * @param debug Whether the builder is running in watch or debug mode. + * @param projectSourceRoot The root directory of the project being tested for resolving installed packages. + * @param viewport Optional viewport dimensions to apply to the launched browser instances. + * @returns A fully resolved Vitest browser configuration object alongside any generated warning or error messages. + */ export async function setupBrowserConfiguration( browsers: string[] | undefined, headless: boolean | undefined, @@ -79,6 +150,8 @@ export async function setupBrowserConfiguration( ); } + const instances = browsers.map(normalizeBrowserName); + let provider: import('vitest/node').BrowserProviderOption | undefined; if (providerName) { const providerPackage = `@vitest/browser-${providerName}`; @@ -90,17 +163,25 @@ export async function setupBrowserConfiguration( if (typeof providerFactory === 'function') { if (providerName === 'playwright') { const executablePath = process.env['CHROME_BIN']; - provider = providerFactory({ - launchOptions: executablePath - ? { - executablePath, - } - : undefined, + const baseOptions = { contextOptions: { // Enables `prefer-color-scheme` for Vitest browser instead of `light` colorScheme: null, }, - }); + }; + + provider = providerFactory(baseOptions); + + if (executablePath) { + for (const instance of instances) { + if (instance.browser === 'chrome' || instance.browser === 'chromium') { + instance.provider = providerFactory({ + ...baseOptions, + launchOptions: { executablePath }, + }); + } + } + } } else { provider = providerFactory(); } @@ -133,36 +214,7 @@ export async function setupBrowserConfiguration( } const isCI = !!process.env['CI']; - const instances = browsers.map(normalizeBrowserName); - const messages: string[] = []; - - if (providerName === 'preview') { - instances.forEach((instance) => { - // Preview mode only supports headed execution - instance.headless = false; - }); - - if (headless) { - messages.push('The "headless" option is ignored when using the "preview" provider.'); - } - } else if (headless !== undefined) { - if (headless) { - const allHeadlessByDefault = isCI || instances.every((i) => i.headless); - if (allHeadlessByDefault) { - messages.push( - 'The "headless" option is unnecessary as all browsers are already configured to run in headless mode.', - ); - } - } - - instances.forEach((instance) => { - instance.headless = headless; - }); - } else if (isCI) { - instances.forEach((instance) => { - instance.headless = true; - }); - } + const messages = applyHeadlessConfiguration(instances, providerName, headless, isCI); const browser = { enabled: true, diff --git a/packages/angular/build/src/builders/unit-test/runners/vitest/browser-provider_spec.ts b/packages/angular/build/src/builders/unit-test/runners/vitest/browser-provider_spec.ts index 66f7254593b0..0dd0778420bd 100644 --- a/packages/angular/build/src/builders/unit-test/runners/vitest/browser-provider_spec.ts +++ b/packages/angular/build/src/builders/unit-test/runners/vitest/browser-provider_spec.ts @@ -9,7 +9,7 @@ import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; -import { setupBrowserConfiguration } from './browser-provider'; +import { applyHeadlessConfiguration, setupBrowserConfiguration } from './browser-provider'; describe('setupBrowserConfiguration', () => { let workspaceRoot: string; @@ -47,8 +47,8 @@ describe('setupBrowserConfiguration', () => { expect(browser?.enabled).toBeTrue(); expect(browser?.instances).toEqual([ - { browser: 'chrome', headless: true }, - { browser: 'firefox', headless: false }, + jasmine.objectContaining({ browser: 'chrome', headless: true }), + jasmine.objectContaining({ browser: 'firefox', headless: false }), ]); }); @@ -66,8 +66,8 @@ describe('setupBrowserConfiguration', () => { ); expect(browser?.instances).toEqual([ - { browser: 'chrome', headless: true }, - { browser: 'firefox', headless: true }, + jasmine.objectContaining({ browser: 'chrome', headless: true }), + jasmine.objectContaining({ browser: 'firefox', headless: true }), ]); } finally { if (originalCI === undefined) { @@ -196,8 +196,8 @@ describe('setupBrowserConfiguration', () => { ); expect(browser?.instances).toEqual([ - { browser: 'chrome', headless: true }, - { browser: 'firefox', headless: true }, + jasmine.objectContaining({ browser: 'chrome', headless: true }), + jasmine.objectContaining({ browser: 'firefox', headless: true }), ]); expect(messages).toEqual([]); }); @@ -215,4 +215,106 @@ describe('setupBrowserConfiguration', () => { 'The "headless" option is unnecessary as all browsers are already configured to run in headless mode.', ]); }); + + describe('CHROME_BIN usage', () => { + let originalChromeBin: string | undefined; + + beforeEach(() => { + originalChromeBin = process.env['CHROME_BIN']; + process.env['CHROME_BIN'] = '/custom/path/to/chrome'; + }); + + afterEach(() => { + if (originalChromeBin === undefined) { + delete process.env['CHROME_BIN']; + } else { + process.env['CHROME_BIN'] = originalChromeBin; + } + }); + + it('should set executablePath on the individual chrome instance', async () => { + const { browser } = await setupBrowserConfiguration( + ['ChromeHeadless', 'Chromium'], + undefined, + false, + workspaceRoot, + undefined, + ); + + // Verify the global provider does NOT have executablePath + // eslint-disable-next-line @typescript-eslint/no-explicit-any + expect((browser?.provider as any)?.options?.launchOptions?.executablePath).toBeUndefined(); + + // Verify the individual instances have executablePath + expect( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (browser?.instances?.[0]?.provider as any)?.options?.launchOptions?.executablePath, + ).toBe('/custom/path/to/chrome'); + expect( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (browser?.instances?.[1]?.provider as any)?.options?.launchOptions?.executablePath, + ).toBe('/custom/path/to/chrome'); + }); + + it('should set executablePath for chrome instances but not for others when mixed browsers are requested', async () => { + const { browser } = await setupBrowserConfiguration( + ['ChromeHeadless', 'Firefox'], + undefined, + false, + workspaceRoot, + undefined, + ); + + // Verify the global provider does NOT have executablePath + // eslint-disable-next-line @typescript-eslint/no-explicit-any + expect((browser?.provider as any)?.options?.launchOptions?.executablePath).toBeUndefined(); + + // Verify chrome gets it + expect( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (browser?.instances?.[0]?.provider as any)?.options?.launchOptions?.executablePath, + ).toBe('/custom/path/to/chrome'); + + // Verify firefox does not + expect(browser?.instances?.[1]?.provider).toBeUndefined(); + }); + }); + + describe('applyHeadlessConfiguration', () => { + it('should set headless false and issue warning when using preview provider with headless true', () => { + const instances = [{ browser: 'chrome', headless: true }]; + const messages = applyHeadlessConfiguration(instances, 'preview', true, false); + + expect(instances[0].headless).toBeFalse(); + expect(messages).toEqual([ + 'The "headless" option is ignored when using the "preview" provider.', + ]); + }); + + it('should force headless mode when headless option is true', () => { + const instances = [{ browser: 'chrome', headless: false }]; + const messages = applyHeadlessConfiguration(instances, 'playwright', true, false); + + expect(instances[0].headless).toBeTrue(); + expect(messages).toEqual([]); + }); + + it('should return information message when headless option is redundant', () => { + const instances = [{ browser: 'chrome', headless: true }]; + const messages = applyHeadlessConfiguration(instances, 'playwright', true, false); + + expect(instances[0].headless).toBeTrue(); + expect(messages).toEqual([ + 'The "headless" option is unnecessary as all browsers are already configured to run in headless mode.', + ]); + }); + + it('should force headless mode in CI environment when headless is undefined', () => { + const instances = [{ browser: 'chrome', headless: false }]; + const messages = applyHeadlessConfiguration(instances, 'playwright', undefined, true); + + expect(instances[0].headless).toBeTrue(); + expect(messages).toEqual([]); + }); + }); }); 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 9933f663aa86..27519844312a 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 @@ -157,31 +157,36 @@ export async function getVitestBuildOptions( const mockPatchContents = ` import { vi } from 'vitest'; - const error = new Error( - 'The "vi.mock" and related methods are not supported for relative imports with the Angular unit-test system. ' + - 'Please use Angular TestBed for mocking dependencies.' - ); - - // Store original implementations - const { mock, doMock, importMock, unmock, doUnmock } = vi; - - function patch(original) { - return (path, ...args) => { - // Check if the path is a string and starts with a character that indicates a relative path. - if (typeof path === 'string' && /^[./]/.test(path)) { - throw error; - } - - // Call the original function for non-relative paths. - return original(path, ...args); - }; + const ANGULAR_VITEST_MOCK_PATCH = Symbol.for('@angular/cli/vitest-mock-patch'); + if (!globalThis[ANGULAR_VITEST_MOCK_PATCH]) { + globalThis[ANGULAR_VITEST_MOCK_PATCH] = true; + + const error = new Error( + 'The "vi.mock" and related methods are not supported for relative imports with the Angular unit-test system. ' + + 'Please use Angular TestBed for mocking dependencies.' + ); + + // Store original implementations + const { mock, doMock, importMock, unmock, doUnmock } = vi; + + function patch(original) { + return (path, ...args) => { + // Check if the path is a string and starts with a character that indicates a relative path. + if (typeof path === 'string' && /^[./]/.test(path)) { + throw error; + } + + // Call the original function for non-relative paths. + return original(path, ...args); + }; + } + + vi.mock = patch(mock); + vi.doMock = patch(doMock); + vi.importMock = patch(importMock); + vi.unmock = patch(unmock); + vi.doUnmock = patch(doUnmock); } - - vi.mock = patch(mock); - vi.doMock = patch(doMock); - vi.importMock = patch(importMock); - vi.unmock = patch(unmock); - vi.doUnmock = patch(doUnmock); `; return { 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 af4dcaea01fb..1bcb8c40500a 100644 --- a/packages/angular/build/src/tools/esbuild/angular/compiler-plugin.ts +++ b/packages/angular/build/src/tools/esbuild/angular/compiler-plugin.ts @@ -220,10 +220,19 @@ export function createCompilerPlugin( if (stylesheetResult.errors) { (result.errors ??= []).push(...stylesheetResult.errors); + const { referencedFiles } = stylesheetResult; + if (referencedFiles) { + referencedFileTracker.add(containingFile, referencedFiles); + if (stylesheetFile) { + referencedFileTracker.add(stylesheetFile, referencedFiles); + } + } + return ''; } const { contents, outputFiles, metafile, referencedFiles } = stylesheetResult; + additionalResults.set(resultSource, { outputFiles, metafile, diff --git a/packages/angular/build/src/utils/server-rendering/prerender.ts b/packages/angular/build/src/utils/server-rendering/prerender.ts index f0e822eb3de9..1033a7575f88 100644 --- a/packages/angular/build/src/utils/server-rendering/prerender.ts +++ b/packages/angular/build/src/utils/server-rendering/prerender.ts @@ -116,8 +116,12 @@ export async function prerenderPages( sourcemap, outputMode, ).catch((err) => { + assertIsError(err); + return { - errors: [`An error occurred while extracting routes.\n\n${err.message ?? err.stack ?? err}`], + errors: [ + `An error occurred while extracting routes.\n\n${err.stack ?? err.message ?? err.code ?? err}`, + ], serializedRouteTree: [], appShellRoute: undefined, }; @@ -265,8 +269,9 @@ async function renderPages( } }) .catch((err) => { + assertIsError(err); errors.push( - `An error occurred while prerendering route '${route}'.\n\n${err.message ?? err.stack ?? err.code ?? err}`, + `An error occurred while prerendering route '${route}'.\n\n${err.stack ?? err.message ?? err.code ?? err}`, ); void renderWorker.destroy(); }); @@ -371,7 +376,7 @@ async function getAllRoutes( return { errors: [ - `An error occurred while extracting routes.\n\n${err.message ?? err.stack ?? err.code ?? err}`, + `An error occurred while extracting routes.\n\n${err.stack ?? err.message ?? err.code ?? err}`, ], serializedRouteTree: [], }; diff --git a/packages/angular/cli/src/commands/mcp/tools/projects.ts b/packages/angular/cli/src/commands/mcp/tools/projects.ts index 8c6eb5d332f6..c53adb7828df 100644 --- a/packages/angular/cli/src/commands/mcp/tools/projects.ts +++ b/packages/angular/cli/src/commands/mcp/tools/projects.ts @@ -467,7 +467,7 @@ async function loadAndParseWorkspace( const projects = []; const workspaceRoot = dirname(configFile); for (const [name, project] of ws.projects.entries()) { - const sourceRoot = posix.join(project.root, project.sourceRoot ?? 'src'); + const sourceRoot = project.sourceRoot ?? posix.join(project.root, 'src'); const fullSourceRoot = join(workspaceRoot, sourceRoot); const unitTestFramework = getUnitTestFramework(project.targets.get('test')); const styleLanguage = await getProjectStyleLanguage(project, ws, fullSourceRoot); diff --git a/packages/angular_devkit/build_angular/package.json b/packages/angular_devkit/build_angular/package.json index ed47eb31a2bf..f4e7f207c2ec 100644 --- a/packages/angular_devkit/build_angular/package.json +++ b/packages/angular_devkit/build_angular/package.json @@ -68,7 +68,7 @@ "@angular/ssr": "workspace:*", "@web/test-runner": "0.20.2", "browser-sync": "3.0.4", - "ng-packagr": "21.2.1", + "ng-packagr": "21.2.2", "undici": "7.24.4" }, "peerDependencies": { diff --git a/packages/angular_devkit/schematics/tasks/repo-init/executor.ts b/packages/angular_devkit/schematics/tasks/repo-init/executor.ts index 97b2b12a3619..607e1bfc5cba 100644 --- a/packages/angular_devkit/schematics/tasks/repo-init/executor.ts +++ b/packages/angular_devkit/schematics/tasks/repo-init/executor.ts @@ -29,7 +29,6 @@ export default function ( const errorStream = ignoreErrorStream ? 'ignore' : process.stderr; const spawnOptions: SpawnOptions = { stdio: [process.stdin, outputStream, errorStream], - shell: true, cwd: path.join(rootDirectory, options.workingDirectory || ''), env: { ...process.env, @@ -41,7 +40,7 @@ export default function ( }; return new Promise((resolve, reject) => { - spawn(`git ${args.join(' ')}`, spawnOptions).on('close', (code: number) => { + spawn('git', args, spawnOptions).on('close', (code: number) => { if (code === 0) { resolve(); } else { @@ -82,7 +81,7 @@ export default function ( if (options.commit) { const message = options.message || 'initial commit'; - await execute(['commit', `-m "${message}"`]); + await execute(['commit', '-m', message]); } context.logger.info('Successfully initialized git.'); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9a13dd4a5849..58b5e00312e0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -432,8 +432,8 @@ importers: specifier: 4.4.2 version: 4.4.2 ng-packagr: - specifier: 21.2.1 - version: 21.2.1(@angular/compiler-cli@21.2.6(@angular/compiler@21.2.6)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3) + specifier: 21.2.2 + version: 21.2.2(@angular/compiler-cli@21.2.6(@angular/compiler@21.2.6)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3) postcss: specifier: 8.5.6 version: 8.5.6 @@ -741,8 +741,8 @@ importers: specifier: 3.0.4 version: 3.0.4(bufferutil@4.1.0)(utf-8-validate@6.0.6) ng-packagr: - specifier: 21.2.1 - version: 21.2.1(@angular/compiler-cli@21.2.6(@angular/compiler@21.2.6)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3) + specifier: 21.2.2 + version: 21.2.2(@angular/compiler-cli@21.2.6(@angular/compiler@21.2.6)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3) undici: specifier: 7.24.4 version: 7.24.4 @@ -7141,8 +7141,8 @@ packages: resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} engines: {node: '>= 0.4.0'} - ng-packagr@21.2.1: - resolution: {integrity: sha512-rk0aL0wWkC+FTA4wyzJIfjcUgAKMAEB19ULvP0QkAoAkzjS+3SBEf0n3MS6z7gcOW8SRU9rw1BmsouEAXD+SCw==} + ng-packagr@21.2.2: + resolution: {integrity: sha512-VO0y7RU3Ik8E14QdrryVyVbTAyqO2MK9W9GrG4e/4N8+ti+DWiBSQmw0tIhnV67lEjQwCccPA3ZBoIn3B1vJ1Q==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} hasBin: true peerDependencies: @@ -16629,7 +16629,7 @@ snapshots: netmask@2.0.2: {} - ng-packagr@21.2.1(@angular/compiler-cli@21.2.6(@angular/compiler@21.2.6)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3): + ng-packagr@21.2.2(@angular/compiler-cli@21.2.6(@angular/compiler@21.2.6)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3): dependencies: '@ampproject/remapping': 2.3.0 '@angular/compiler-cli': 21.2.6(@angular/compiler@21.2.6)(typescript@5.9.3) diff --git a/scripts/devkit-admin.mts b/scripts/devkit-admin.mts index 0a17df9f45a1..7d8522547100 100644 --- a/scripts/devkit-admin.mts +++ b/scripts/devkit-admin.mts @@ -33,12 +33,16 @@ process.chdir(path.join(scriptDir, '..')); const originalConsole = { ...console }; console.warn = function (...args) { - const [m, ...rest] = args; - originalConsole.warn(styleText(['yellow'], m), ...rest); + if (typeof args[0] === 'string') { + args[0] = styleText(['yellow'], args[0]); + } + originalConsole.warn(...args); }; console.error = function (...args) { - const [m, ...rest] = args; - originalConsole.error(styleText(['red'], m), ...rest); + if (typeof args[0] === 'string') { + args[0] = styleText(['red'], args[0]); + } + originalConsole.error(...args); }; try { @@ -47,6 +51,6 @@ try { process.exitCode = typeof exitCode === 'number' ? exitCode : 0; // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (err: any) { - console.error(err.stack); + console.error(err.stack ?? err); process.exitCode = 99; } diff --git a/tests/e2e/tests/mcp/projects-sourceroot-resolution.ts b/tests/e2e/tests/mcp/projects-sourceroot-resolution.ts new file mode 100644 index 000000000000..01ebb8c1ca05 --- /dev/null +++ b/tests/e2e/tests/mcp/projects-sourceroot-resolution.ts @@ -0,0 +1,47 @@ +import { exec, ProcessOutput, silentNpm } from '../../utils/process'; +import { updateJsonFile } from '../../utils/project'; +import assert from 'node:assert/strict'; + +const MCP_INSPECTOR_PACKAGE_NAME = '@modelcontextprotocol/inspector-cli'; +const MCP_INSPECTOR_PACKAGE_VERSION = '0.16.2'; +const MCP_INSPECTOR_COMMAND_NAME = 'mcp-inspector-cli'; + +async function runInspector(...args: string[]): Promise { + return exec(MCP_INSPECTOR_COMMAND_NAME, '--cli', 'npx', '--no', '@angular/cli', 'mcp', ...args); +} + +export default async function () { + await silentNpm( + 'install', + '--ignore-scripts', + '-g', + `${MCP_INSPECTOR_PACKAGE_NAME}@${MCP_INSPECTOR_PACKAGE_VERSION}`, + ); + + try { + // 1. Add a sample project with a non-root path to angular.json + await updateJsonFile('angular.json', (workspaceJson) => { + workspaceJson.projects ??= {}; + workspaceJson.projects['sample-lib'] = { + root: 'projects/sample-lib', + sourceRoot: 'projects/sample-lib/src', + projectType: 'library', + }; + }); + + // 2. Call list_projects + const { stdout } = await runInspector('--method', 'tools/call', '--tool-name', 'list_projects'); + + // 3. Verify output + assert.match(stdout, /"name": "sample-lib"/); + // Assert that sourceRoot is NOT duplicated + assert.match(stdout, /"sourceRoot": "projects\/sample-lib\/src"/); + assert.doesNotMatch(stdout, /"sourceRoot": "projects\/sample-lib\/projects\/sample-lib\/src"/); + } finally { + // 4. Cleanup angular.json + await updateJsonFile('angular.json', (workspaceJson) => { + delete workspaceJson.projects['sample-lib']; + }); + await silentNpm('uninstall', '-g', MCP_INSPECTOR_PACKAGE_NAME); + } +}