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);
+ }
+}