diff --git a/.github/workflows/assistant-to-the-branch-manager.yml b/.github/workflows/assistant-to-the-branch-manager.yml
index 30585fa707fd..1fcd15f0c5b9 100644
--- a/.github/workflows/assistant-to-the-branch-manager.yml
+++ b/.github/workflows/assistant-to-the-branch-manager.yml
@@ -18,6 +18,6 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- - uses: angular/dev-infra/github-actions/branch-manager@183403ae13b785698eaf13c819dda55b9fed430b # main
+ - uses: angular/dev-infra/github-actions/branch-manager@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 5fad471f1244..cee6fbdfbda5 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@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Setup Bazel
- uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/bazel/setup@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Generate JSON schema types
@@ -44,11 +44,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
- uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Setup Bazel
- uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/bazel/setup@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Setup Bazel RBE
- uses: angular/dev-infra/github-actions/bazel/configure-remote@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/bazel/configure-remote@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
with:
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
- name: Install node modules
@@ -61,11 +61,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
- uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Setup Bazel
- uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/bazel/setup@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Setup Bazel RBE
- uses: angular/dev-infra/github-actions/bazel/configure-remote@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/bazel/configure-remote@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
with:
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
- name: Install node modules
@@ -84,13 +84,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
- uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Setup Bazel
- uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/bazel/setup@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Setup Bazel RBE
- uses: angular/dev-infra/github-actions/bazel/configure-remote@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/bazel/configure-remote@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
with:
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
- name: Run CLI E2E tests
@@ -100,11 +100,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
- uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Setup Bazel
- uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/bazel/setup@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Setup Bazel RBE
- uses: angular/dev-infra/github-actions/bazel/configure-remote@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/bazel/configure-remote@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
with:
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
- name: Install node modules
@@ -137,7 +137,7 @@ jobs:
runs-on: windows-2025
steps:
- name: Initialize environment
- uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Download built Windows E2E tests
@@ -164,13 +164,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
- uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Setup Bazel
- uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/bazel/setup@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Setup Bazel RBE
- uses: angular/dev-infra/github-actions/bazel/configure-remote@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/bazel/configure-remote@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
with:
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
- name: Run CLI E2E tests
@@ -188,13 +188,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
- uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Setup Bazel
- uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/bazel/setup@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Setup Bazel RBE
- uses: angular/dev-infra/github-actions/bazel/configure-remote@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/bazel/configure-remote@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
with:
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
- name: Run CLI E2E tests
@@ -208,13 +208,13 @@ jobs:
SAUCE_TUNNEL_IDENTIFIER: angular-cli-${{ github.workflow }}-${{ github.run_number }}
steps:
- name: Initialize environment
- uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Setup Bazel
- uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/bazel/setup@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Setup Bazel RBE
- uses: angular/dev-infra/github-actions/bazel/configure-remote@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/bazel/configure-remote@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
with:
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
- name: Start Sauce Connect
@@ -245,11 +245,11 @@ jobs:
CIRCLE_BRANCH: ${{ github.ref_name }}
steps:
- name: Initialize environment
- uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Setup Bazel
- uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/bazel/setup@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- 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 bb05d9b3dbfe..b12b3dc168ea 100644
--- a/.github/workflows/dev-infra.yml
+++ b/.github/workflows/dev-infra.yml
@@ -16,21 +16,21 @@ jobs:
if: github.event_name == 'pull_request_target'
runs-on: ubuntu-latest
steps:
- - uses: angular/dev-infra/github-actions/labeling/pull-request@183403ae13b785698eaf13c819dda55b9fed430b # main
+ - uses: angular/dev-infra/github-actions/labeling/pull-request@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
post_approval_changes:
if: github.event_name == 'pull_request_target'
runs-on: ubuntu-latest
steps:
- - uses: angular/dev-infra/github-actions/post-approval-changes@183403ae13b785698eaf13c819dda55b9fed430b # main
+ - uses: angular/dev-infra/github-actions/post-approval-changes@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
issue_labels:
if: github.event_name == 'issues'
runs-on: ubuntu-latest
steps:
- - uses: angular/dev-infra/github-actions/labeling/issue@183403ae13b785698eaf13c819dda55b9fed430b # main
+ - uses: angular/dev-infra/github-actions/labeling/issue@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
google-generative-ai-key: ${{ secrets.GOOGLE_GENERATIVE_AI_KEY }}
diff --git a/.github/workflows/perf.yml b/.github/workflows/perf.yml
index 504f7e5136ad..b837b722a4f4 100644
--- a/.github/workflows/perf.yml
+++ b/.github/workflows/perf.yml
@@ -22,7 +22,7 @@ jobs:
workflows: ${{ steps.workflows.outputs.workflows }}
steps:
- name: Initialize environment
- uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Install node modules
run: pnpm install --frozen-lockfile
- id: workflows
@@ -40,9 +40,9 @@ jobs:
workflow: ${{ fromJSON(needs.list.outputs.workflows) }}
steps:
- name: Initialize environment
- uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Setup Bazel
- uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/bazel/setup@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Install node modules
run: pnpm install --frozen-lockfile
# We utilize the google-github-actions/auth action to allow us to get an active credential using workflow
diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml
index 8b2baafbd3b4..c62816440333 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@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Setup Bazel
- uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/bazel/setup@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Setup ESLint Caching
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
@@ -66,17 +66,17 @@ jobs:
# it has been merged.
run: pnpm ng-dev format changed --check ${{ github.event.pull_request.base.sha }}
- name: Check Package Licenses
- uses: angular/dev-infra/github-actions/linting/licenses@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/linting/licenses@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
build:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
- uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Setup Bazel
- uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/bazel/setup@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Setup Bazel RBE
- uses: angular/dev-infra/github-actions/bazel/configure-remote@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/bazel/configure-remote@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Build release targets
@@ -93,11 +93,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
- uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Setup Bazel
- uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/bazel/setup@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Setup Bazel RBE
- uses: angular/dev-infra/github-actions/bazel/configure-remote@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/bazel/configure-remote@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Run module and package tests
@@ -114,13 +114,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
- uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Setup Bazel
- uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/bazel/setup@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Setup Bazel RBE
- uses: angular/dev-infra/github-actions/bazel/configure-remote@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/bazel/configure-remote@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Run CLI E2E tests
run: pnpm bazel test --test_env=E2E_SHARD_TOTAL=6 --test_env=E2E_SHARD_INDEX=${{ matrix.shard }} --config=e2e //tests:e2e.${{ matrix.subset }}_node${{ matrix.node }}
@@ -128,11 +128,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
- uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Setup Bazel
- uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/bazel/setup@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Setup Bazel RBE
- uses: angular/dev-infra/github-actions/bazel/configure-remote@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/bazel/configure-remote@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Build E2E tests for Windows on Linux
@@ -156,7 +156,7 @@ jobs:
runs-on: windows-2025
steps:
- name: Initialize environment
- uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Download built Windows E2E tests
@@ -183,13 +183,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
- uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Setup Bazel
- uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/bazel/setup@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Setup Bazel RBE
- uses: angular/dev-infra/github-actions/bazel/configure-remote@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/bazel/configure-remote@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Run CLI E2E tests
run: pnpm bazel test --test_env=E2E_SHARD_TOTAL=3 --test_env=E2E_SHARD_INDEX=${{ matrix.shard }} --config=e2e //tests:e2e.${{ matrix.subset }}_node${{ matrix.node }}
@@ -205,12 +205,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
- uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Setup Bazel
- uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/bazel/setup@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Setup Bazel RBE
- uses: angular/dev-infra/github-actions/bazel/configure-remote@183403ae13b785698eaf13c819dda55b9fed430b # main
+ uses: angular/dev-infra/github-actions/bazel/configure-remote@99d57027286c71dda92d8b91a3607abcf92d8bbd # main
- name: Run CLI E2E tests
run: pnpm bazel test --test_env=E2E_SHARD_TOTAL=6 --test_env=E2E_SHARD_INDEX=${{ matrix.shard }} --config=e2e //tests:e2e.snapshots.${{ matrix.subset }}_node${{ matrix.node }}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f7ae51f6dbe9..a67b3ed682d2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,24 @@
+
+
+# 22.2.0-rc.0 (2026-09-16)
+
+### @angular/cli
+
+| Commit | Type | Description |
+| ---------------------------------------------------------------------------------------------------- | ---- | ----------------------------------------------------------------- |
+| [6e7f809a7d](https://github.com/angular/angular-cli/commit/6e7f809a7d66e4b8d260a1c00297812412a926f1) | perf | skip eager yargs help message formatting during command execution |
+
+### @angular/build
+
+| Commit | Type | Description |
+| ---------------------------------------------------------------------------------------------------- | ---- | -------------------------------------------------------------------------- |
+| [978351af71](https://github.com/angular/angular-cli/commit/978351af71889d4786a18fec9e193eb5b16af7dd) | fix | include inline component stylesheets in referenced watch files |
+| [72dc9677cf](https://github.com/angular/angular-cli/commit/72dc9677cf743243d6cb22d2b81782022f71356e) | fix | pass load cache to compiler plugin and escape extension regex in polyfills |
+| [31c045639e](https://github.com/angular/angular-cli/commit/31c045639e8bfd114cbd552d83cc70a968fe7f81) | fix | prevent stale bundler caching and correctly resolve load cache |
+| [d97c8857c1](https://github.com/angular/angular-cli/commit/d97c8857c18da02fcc6c69a0ea652cc706c5c9b6) | perf | key Sass package resolutions without containing URL qualification |
+
+
+
# 22.2.0-next.7 (2026-09-10)
diff --git a/MODULE.bazel b/MODULE.bazel
index 470c5e258947..595813a5abf1 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -10,30 +10,30 @@ bazel_dep(name = "rules_nodejs", version = "6.7.5")
bazel_dep(name = "aspect_rules_js", version = "3.4.1")
bazel_dep(name = "aspect_rules_ts", version = "3.10.1")
bazel_dep(name = "rules_pkg", version = "1.3.0")
-bazel_dep(name = "rules_cc", version = "0.2.22")
-bazel_dep(name = "jq.bzl", version = "0.6.1")
+bazel_dep(name = "rules_cc", version = "0.2.25")
+bazel_dep(name = "jq.bzl", version = "0.6.2")
bazel_dep(name = "bazel_lib", version = "3.7.2")
bazel_dep(name = "bazel_skylib", version = "1.9.2")
-bazel_dep(name = "aspect_rules_esbuild", version = "0.27.0")
+bazel_dep(name = "aspect_rules_esbuild", version = "0.27.1")
bazel_dep(name = "aspect_rules_jasmine", version = "2.0.4")
bazel_dep(name = "rules_angular")
git_override(
module_name = "rules_angular",
- commit = "5fa856469c642490a6e381a05d7f58f60fad1913",
+ commit = "7fdee07b9989b92b88f24c84471e01d6a0a9bc34",
remote = "https://github.com/angular/rules_angular.git",
)
bazel_dep(name = "devinfra")
git_override(
module_name = "devinfra",
- commit = "183403ae13b785698eaf13c819dda55b9fed430b",
+ commit = "99d57027286c71dda92d8b91a3607abcf92d8bbd",
remote = "https://github.com/angular/dev-infra.git",
)
bazel_dep(name = "rules_browsers")
git_override(
module_name = "rules_browsers",
- commit = "c37398d63f5e990d618d02f4c6fb2fefe40f1a79",
+ commit = "f87a5f7fc587219ff328da2972e06370f297a621",
remote = "https://github.com/angular/rules_browsers.git",
)
diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock
index 43e07b4b4f31..2f5985594341 100644
--- a/MODULE.bazel.lock
+++ b/MODULE.bazel.lock
@@ -10,6 +10,9 @@
"https://bcr.bazel.build/modules/abseil-cpp/20230802.1/MODULE.bazel": "fa92e2eb41a04df73cdabeec37107316f7e5272650f81d6cc096418fe647b915",
"https://bcr.bazel.build/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed",
"https://bcr.bazel.build/modules/abseil-cpp/20240116.1/source.json": "9be551b8d4e3ef76875c0d744b5d6a504a27e3ae67bc6b28f46415fd2d2957da",
+ "https://bcr.bazel.build/modules/apple_support/1.23.1/MODULE.bazel": "53763fed456a968cf919b3240427cf3a9d5481ec5466abc9d5dc51bc70087442",
+ "https://bcr.bazel.build/modules/apple_support/2.8.0/MODULE.bazel": "c45f5176057092afba0be6c48a9ea02101666802f8be536947e099e94757f8ed",
+ "https://bcr.bazel.build/modules/apple_support/2.8.0/source.json": "b7f6d87669c206adaa7d82cc44733349c63e341b80a0e049496ebd91d78c3472",
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.0.0/MODULE.bazel": "e118477db5c49419a88d78ebc7a2c2cea9d49600fe0f490c1903324a2c16ecd9",
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.14.0/MODULE.bazel": "2b31ffcc9bdc8295b2167e07a757dbbc9ac8906e7028e5170a3708cecaac119f",
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.19.2/MODULE.bazel": "30dfabbfae0139b1f0036e01c201dd4c0167da3017f0b7ef3820d78e07622989",
@@ -18,8 +21,8 @@
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.22.5/source.json": "ac2c3213df8f985785f1d0aeb7f0f73d5324e6e67d593d9b9470fb74a25d4a9b",
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.7.7/MODULE.bazel": "491f8681205e31bb57892d67442ce448cda4f472a8e6b3dc062865e29a64f89c",
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.8.1/MODULE.bazel": "812d2dd42f65dca362152101fbec418029cc8fd34cbad1a2fde905383d705838",
- "https://bcr.bazel.build/modules/aspect_rules_esbuild/0.27.0/MODULE.bazel": "877dafc0b925f8af19e8bc2abed04a757bb565c57c1866e8851ac4d15ed5e6d2",
- "https://bcr.bazel.build/modules/aspect_rules_esbuild/0.27.0/source.json": "21f8738b3e62310ef43b7cef4284e1bafd69bd8e4e50251b71b20bbfed4372d8",
+ "https://bcr.bazel.build/modules/aspect_rules_esbuild/0.27.1/MODULE.bazel": "99c3978959edd9892e4b513831b218a13cc84a0215bfe1b972a1e3a771c4670e",
+ "https://bcr.bazel.build/modules/aspect_rules_esbuild/0.27.1/source.json": "20f515102cbcd0835d90bb3976b193eb58c2a0c534093c85cfd3bf6f06dba748",
"https://bcr.bazel.build/modules/aspect_rules_jasmine/2.0.4/MODULE.bazel": "fbb819eb8b7e5d7f67fdd38f7cecb413e287594cd666ce192c72c8828527775a",
"https://bcr.bazel.build/modules/aspect_rules_jasmine/2.0.4/source.json": "81ffb708333cd98ec3c0b4cc004f4d5cf92a16914b5196a2892c45141bba7cff",
"https://bcr.bazel.build/modules/aspect_rules_js/2.0.0/MODULE.bazel": "b45b507574aa60a92796e3e13c195cd5744b3b8aff516a9c0cb5ae6a048161c5",
@@ -40,6 +43,7 @@
"https://bcr.bazel.build/modules/bazel_features/1.18.0/MODULE.bazel": "1be0ae2557ab3a72a57aeb31b29be347bcdc5d2b1eb1e70f39e3851a7e97041a",
"https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58",
"https://bcr.bazel.build/modules/bazel_features/1.21.0/MODULE.bazel": "675642261665d8eea09989aa3b8afb5c37627f1be178382c320d1b46afba5e3b",
+ "https://bcr.bazel.build/modules/bazel_features/1.27.0/MODULE.bazel": "621eeee06c4458a9121d1f104efb80f39d34deff4984e778359c60eaf1a8cb65",
"https://bcr.bazel.build/modules/bazel_features/1.28.0/MODULE.bazel": "4b4200e6cbf8fa335b2c3f43e1d6ef3e240319c33d43d60cc0fbd4b87ece299d",
"https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87",
"https://bcr.bazel.build/modules/bazel_features/1.34.0/MODULE.bazel": "e8475ad7c8965542e0c7aac8af68eb48c4af904be3d614b6aa6274c092c2ea1e",
@@ -85,8 +89,8 @@
"https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f",
"https://bcr.bazel.build/modules/jq.bzl/0.1.0/MODULE.bazel": "2ce69b1af49952cd4121a9c3055faa679e748ce774c7f1fda9657f936cae902f",
"https://bcr.bazel.build/modules/jq.bzl/0.4.0/MODULE.bazel": "a7b39b37589f2b0dad53fd6c1ccaabbdb290330caa920d7ef3e6aad068cd4ab2",
- "https://bcr.bazel.build/modules/jq.bzl/0.6.1/MODULE.bazel": "f30c46e0a08a9f7566a8bf60a43d48abea960cd7f57b315b01e2762f1537eb52",
- "https://bcr.bazel.build/modules/jq.bzl/0.6.1/source.json": "9ca9e2f90baa6a5bb0a49626ed9528554ec83165adf47b39792673ecc7feda22",
+ "https://bcr.bazel.build/modules/jq.bzl/0.6.2/MODULE.bazel": "e9c82f9b1e720d4ab0e232d32c05f0f4d0f92a8e8bb6a0da8f7cd27823b93e05",
+ "https://bcr.bazel.build/modules/jq.bzl/0.6.2/source.json": "e36f8ed173a6ca6e627f9d659ae504733d5a991859805661e4aacd34d8ae0639",
"https://bcr.bazel.build/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075",
"https://bcr.bazel.build/modules/jsoncpp/1.9.5/source.json": "4108ee5085dd2885a341c7fab149429db457b3169b86eb081fa245eadf69169d",
"https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902",
@@ -100,6 +104,7 @@
"https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615",
"https://bcr.bazel.build/modules/platforms/0.0.7/MODULE.bazel": "72fd4a0ede9ee5c021f6a8dd92b503e089f46c227ba2813ff183b71616034814",
"https://bcr.bazel.build/modules/platforms/0.0.8/MODULE.bazel": "9f142c03e348f6d263719f5074b21ef3adf0b139ee4c5133e2aa35664da9eb2d",
+ "https://bcr.bazel.build/modules/platforms/0.0.9/MODULE.bazel": "4a87a60c927b56ddd67db50c89acaa62f4ce2a1d2149ccb63ffd871d5ce29ebc",
"https://bcr.bazel.build/modules/platforms/1.0.0/MODULE.bazel": "f05feb42b48f1b3c225e4ccf351f367be0371411a803198ec34a389fb22aa580",
"https://bcr.bazel.build/modules/platforms/1.1.0/MODULE.bazel": "1c0c09f5bdcf4b3f924720d2478a3711cb39f4977019ca5988685e5b7e18b3d2",
"https://bcr.bazel.build/modules/platforms/1.1.0/source.json": "fcf351c47596c939140ab0d333dfdd08ed1ea6ce33c2fe70c12493a301cf1344",
@@ -131,8 +136,9 @@
"https://bcr.bazel.build/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513",
"https://bcr.bazel.build/modules/rules_cc/0.1.5/MODULE.bazel": "88dfc9361e8b5ae1008ac38f7cdfd45ad738e4fa676a3ad67d19204f045a1fd8",
"https://bcr.bazel.build/modules/rules_cc/0.2.16/MODULE.bazel": "9242fa89f950c6ef7702801ab53922e99c69b02310c39fb6e62b2bd30df2a1d4",
- "https://bcr.bazel.build/modules/rules_cc/0.2.22/MODULE.bazel": "94df4328edef9e44d38de5e73b037cd348e75e7ae55f4e21bf07878c41a31ebb",
- "https://bcr.bazel.build/modules/rules_cc/0.2.22/source.json": "b2d6d6f9c332ce269ad75b89c6f3168d809a66173c9040210fb9bcc733ab42fa",
+ "https://bcr.bazel.build/modules/rules_cc/0.2.20/MODULE.bazel": "f5c07bce5ddcb99be21a0812ff5aadb439e688b7449c6542152363b2fd859c1a",
+ "https://bcr.bazel.build/modules/rules_cc/0.2.25/MODULE.bazel": "4a3d4f3606d3b3190be495dbc497acca552807d1d5540661ef0d1658472882e3",
+ "https://bcr.bazel.build/modules/rules_cc/0.2.25/source.json": "8df28a9a97b878fe45f0a4f4adf019869dda51a60ee2f6e15a0c79656a25058d",
"https://bcr.bazel.build/modules/rules_cc/0.2.4/MODULE.bazel": "1ff1223dfd24f3ecf8f028446d4a27608aa43c3f41e346d22838a4223980b8cc",
"https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6",
"https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8",
@@ -203,8 +209,8 @@
"https://bcr.bazel.build/modules/stardoc/0.7.2/MODULE.bazel": "fc152419aa2ea0f51c29583fab1e8c99ddefd5b3778421845606ee628629e0e5",
"https://bcr.bazel.build/modules/stardoc/0.7.2/source.json": "58b029e5e901d6802967754adf0a9056747e8176f017cfe3607c0851f4d42216",
"https://bcr.bazel.build/modules/tar.bzl/0.10.4/MODULE.bazel": "e8f9ff79199e8d9eaad7f1b0a77ad74b30bb82d794b87d8ca942bead5de83ae9",
- "https://bcr.bazel.build/modules/tar.bzl/0.10.8/MODULE.bazel": "443884cabe241f640cfef256b1de2ccb116752895532900a4be9500e1124323f",
- "https://bcr.bazel.build/modules/tar.bzl/0.10.8/source.json": "4173be64b38e471d92d2eb139a6496311de6de75e710f04edce78c43122c5419",
+ "https://bcr.bazel.build/modules/tar.bzl/0.10.9/MODULE.bazel": "4636f5f6a7c34d3d0653634ed95a34560426d548c5301c6c5924c885ef737b46",
+ "https://bcr.bazel.build/modules/tar.bzl/0.10.9/source.json": "30a5cf0a8281adce53ab1c5a4291e88b2c161e2df1feb6cba4ee50556f1748d7",
"https://bcr.bazel.build/modules/tar.bzl/0.2.1/MODULE.bazel": "52d1c00a80a8cc67acbd01649e83d8dd6a9dc426a6c0b754a04fe8c219c76468",
"https://bcr.bazel.build/modules/tar.bzl/0.5.1/MODULE.bazel": "7c2eb3dcfc53b0f3d6f9acdfd911ca803eaf92aadf54f8ca6e4c1f3aee288351",
"https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43",
@@ -222,8 +228,8 @@
"moduleExtensions": {
"@@aspect_rules_esbuild+//esbuild:extensions.bzl%esbuild": {
"general": {
- "bzlTransitiveDigest": "ivnb7fuZRAghsUscNSqk/cyy9pTpUKwbi8xOKhgxNbo=",
- "usagesDigest": "LZ71sshnfqI8R/HeWwBTwawxIa7KnitayBl2hYrqPo4=",
+ "bzlTransitiveDigest": "bn5qyJpbHTM21Kl9gvkARvwxKe1bH7JC951YjIa5vuk=",
+ "usagesDigest": "ZqBbSnzAi8ZyBL3PXnAGtDMIdC2pcuieJWMY3/94lvA=",
"recordedInputs": [
"REPO_MAPPING:aspect_rules_esbuild+,aspect_rules_js aspect_rules_js+",
"REPO_MAPPING:aspect_rules_esbuild+,aspect_tools_telemetry_report aspect_tools_telemetry++telemetry+aspect_tools_telemetry_report",
@@ -355,7 +361,7 @@
"@@aspect_tools_telemetry+//:extension.bzl%telemetry": {
"general": {
"bzlTransitiveDigest": "zFgbaCUuPzH6bIE7CBHBVCqjWSJ78/WNXisdzXaKSnU=",
- "usagesDigest": "2cLMn8ZcqY/6j/zH70TrPob+ENshTw9h48kUHxn1cUM=",
+ "usagesDigest": "B4C1UNcAOoRiMjmkXh3VFW7fY2YULy8OZA7RjfEM2UQ=",
"recordedInputs": [
"REPO_MAPPING:aspect_tools_telemetry+,bazel_lib bazel_lib+",
"REPO_MAPPING:aspect_tools_telemetry+,bazel_skylib bazel_skylib+",
@@ -368,11 +374,11 @@
"deps": {
"aspect_rules_js": "3.4.1",
"aspect_rules_ts": "3.10.1",
- "aspect_rules_esbuild": "0.27.0",
+ "aspect_rules_esbuild": "0.27.1",
"aspect_rules_jasmine": "2.0.4",
"aspect_tools_telemetry": "0.5.1"
},
- "last_notice": 0
+ "last_notice": 2
}
}
},
@@ -433,16 +439,16 @@
},
"@@rules_browsers+//browsers:extensions.bzl%browsers": {
"general": {
- "bzlTransitiveDigest": "eNfcpVF2YJFb8xQfN64Y9PQgMrXYK0ju3uglArt8IO8=",
+ "bzlTransitiveDigest": "jwGo+QiVY+i/AnJLUkEJ+vaplFYfygil2SJt8C7kM5o=",
"usagesDigest": "FmXYJVoVJlnfUU8x8gObSvu4qWcco/9Faw61aC/wBF0=",
"recordedInputs": [],
"generatedRepoSpecs": {
"rules_browsers_chrome_linux": {
"repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo",
"attributes": {
- "sha256": "ad2e73e1e42c0831930dfd02d4aa8653a5f23f60b0376a5d76ddf8ecd7a41e4b",
+ "sha256": "11d48f3e2ee11fc2a53a230eb364b2becc44c1b67a7aa4683edb7cb8a950b6ab",
"urls": [
- "https://storage.googleapis.com/chrome-for-testing-public/155.0.8043.0/linux64/chrome-headless-shell-linux64.zip"
+ "https://storage.googleapis.com/chrome-for-testing-public/156.0.8060.2/linux64/chrome-headless-shell-linux64.zip"
],
"named_files": {
"CHROME-HEADLESS-SHELL": "chrome-headless-shell-linux64/chrome-headless-shell"
@@ -458,9 +464,9 @@
"rules_browsers_chrome_mac": {
"repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo",
"attributes": {
- "sha256": "d66b730ed9760ae88b83aca1fa89351ac027ede430be4f5283fabd156198762d",
+ "sha256": "42c60eb1793fe76aabf19664b03c299170e5de53baa23ae72578f063dc9b2614",
"urls": [
- "https://storage.googleapis.com/chrome-for-testing-public/155.0.8043.0/mac-x64/chrome-headless-shell-mac-x64.zip"
+ "https://storage.googleapis.com/chrome-for-testing-public/156.0.8060.2/mac-x64/chrome-headless-shell-mac-x64.zip"
],
"named_files": {
"CHROME-HEADLESS-SHELL": "chrome-headless-shell-mac-x64/chrome-headless-shell"
@@ -476,9 +482,9 @@
"rules_browsers_chrome_mac_arm": {
"repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo",
"attributes": {
- "sha256": "44c784c59af60ef62233b6100820ebb8b158e7d105b96c684818f007d6576848",
+ "sha256": "4afc1dbaeea03ad545b434c0f9eba50af326923fc265bc25927b3b4c7d9ca377",
"urls": [
- "https://storage.googleapis.com/chrome-for-testing-public/155.0.8043.0/mac-arm64/chrome-headless-shell-mac-arm64.zip"
+ "https://storage.googleapis.com/chrome-for-testing-public/156.0.8060.2/mac-arm64/chrome-headless-shell-mac-arm64.zip"
],
"named_files": {
"CHROME-HEADLESS-SHELL": "chrome-headless-shell-mac-arm64/chrome-headless-shell"
@@ -494,9 +500,9 @@
"rules_browsers_chrome_win64": {
"repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo",
"attributes": {
- "sha256": "996537a6c3d31bb5c75ef328af5d4827b5f92c4a36ff7ea250e437eadf46eac0",
+ "sha256": "9de8381d4ca9c9f0471fd1cce15e44146328c3e11f55fec43cd667ae1a5bd482",
"urls": [
- "https://storage.googleapis.com/chrome-for-testing-public/155.0.8043.0/win64/chrome-headless-shell-win64.zip"
+ "https://storage.googleapis.com/chrome-for-testing-public/156.0.8060.2/win64/chrome-headless-shell-win64.zip"
],
"named_files": {
"CHROME-HEADLESS-SHELL": "chrome-headless-shell-win64/chrome-headless-shell.exe"
@@ -512,9 +518,9 @@
"rules_browsers_chromedriver_linux": {
"repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo",
"attributes": {
- "sha256": "f1db8caff52251dec46117a2f518a74cd1e4a2abadcbecc0d5bdd49e42175afa",
+ "sha256": "29be812bf4392619d06dfb5716d2bf9d72a278304670a594e3977d4c2cce7502",
"urls": [
- "https://storage.googleapis.com/chrome-for-testing-public/155.0.8043.0/linux64/chromedriver-linux64.zip"
+ "https://storage.googleapis.com/chrome-for-testing-public/156.0.8060.2/linux64/chromedriver-linux64.zip"
],
"named_files": {
"CHROMEDRIVER": "chromedriver-linux64/chromedriver"
@@ -528,9 +534,9 @@
"rules_browsers_chromedriver_mac": {
"repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo",
"attributes": {
- "sha256": "5062675bc868f3f6a1e4850cab72b736a5c17f98f01d610736b8d0a6e009bf54",
+ "sha256": "d4dc141aadd9525e67c92fc78ec65e2926226c436eca7d90e39cdc813726513d",
"urls": [
- "https://storage.googleapis.com/chrome-for-testing-public/155.0.8043.0/mac-x64/chromedriver-mac-x64.zip"
+ "https://storage.googleapis.com/chrome-for-testing-public/156.0.8060.2/mac-x64/chromedriver-mac-x64.zip"
],
"named_files": {
"CHROMEDRIVER": "chromedriver-mac-x64/chromedriver"
@@ -544,9 +550,9 @@
"rules_browsers_chromedriver_mac_arm": {
"repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo",
"attributes": {
- "sha256": "04f07a2a13e969f120dbea61eef291dc4e20617a6d424dec20e90f903da0263b",
+ "sha256": "984056c6d3dd189569cc89affd28564a9de949d58297279fc23a045c8fc53080",
"urls": [
- "https://storage.googleapis.com/chrome-for-testing-public/155.0.8043.0/mac-arm64/chromedriver-mac-arm64.zip"
+ "https://storage.googleapis.com/chrome-for-testing-public/156.0.8060.2/mac-arm64/chromedriver-mac-arm64.zip"
],
"named_files": {
"CHROMEDRIVER": "chromedriver-mac-arm64/chromedriver"
@@ -560,9 +566,9 @@
"rules_browsers_chromedriver_win64": {
"repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo",
"attributes": {
- "sha256": "7701074890112ed3b780af26104cb9f57ce68e229ca3076bfade6a1af93730d7",
+ "sha256": "a5bcbd013b64e6a15acc84c164fe974d0f1a58d4e061fba79d24b31cced64d88",
"urls": [
- "https://storage.googleapis.com/chrome-for-testing-public/155.0.8043.0/win64/chromedriver-win64.zip"
+ "https://storage.googleapis.com/chrome-for-testing-public/156.0.8060.2/win64/chromedriver-win64.zip"
],
"named_files": {
"CHROMEDRIVER": "chromedriver-win64/chromedriver.exe"
@@ -576,9 +582,9 @@
"rules_browsers_firefox_linux": {
"repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo",
"attributes": {
- "sha256": "fd9ec3f5f113d0825ca0bd1ab3c0756fbc40241034eefce6743be953bcca7473",
+ "sha256": "1d44cd02351c307c3e19061ea2a4d18a30f236e6be862b94f2282564afdb0167",
"urls": [
- "https://archive.mozilla.org/pub/firefox/releases/155.0/linux-x86_64/en-US/firefox-155.0.tar.xz"
+ "https://archive.mozilla.org/pub/firefox/releases/156.0/linux-x86_64/en-US/firefox-156.0.tar.xz"
],
"named_files": {
"FIREFOX": "firefox/firefox"
@@ -592,9 +598,9 @@
"rules_browsers_firefox_mac": {
"repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo",
"attributes": {
- "sha256": "3ae135f2023cf0c6cbce3bb757e06564ff8148b1266f682b8eccff0993a1a9f5",
+ "sha256": "fecc46103039ca4a77ecb2b2ef7b03892f922e13db0782f8570c512b2e458515",
"urls": [
- "https://archive.mozilla.org/pub/firefox/releases/155.0/mac/en-US/Firefox%20155.0.dmg"
+ "https://archive.mozilla.org/pub/firefox/releases/156.0/mac/en-US/Firefox%20156.0.dmg"
],
"named_files": {
"FIREFOX": "Firefox.app/Contents/MacOS/firefox"
@@ -608,9 +614,9 @@
"rules_browsers_firefox_mac_arm": {
"repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo",
"attributes": {
- "sha256": "3ae135f2023cf0c6cbce3bb757e06564ff8148b1266f682b8eccff0993a1a9f5",
+ "sha256": "fecc46103039ca4a77ecb2b2ef7b03892f922e13db0782f8570c512b2e458515",
"urls": [
- "https://archive.mozilla.org/pub/firefox/releases/155.0/mac/en-US/Firefox%20155.0.dmg"
+ "https://archive.mozilla.org/pub/firefox/releases/156.0/mac/en-US/Firefox%20156.0.dmg"
],
"named_files": {
"FIREFOX": "Firefox.app/Contents/MacOS/firefox"
@@ -624,9 +630,9 @@
"rules_browsers_firefox_win64": {
"repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo",
"attributes": {
- "sha256": "2718d4cc4b2cf089aa8ac370111652bc763838e1d2ed6d635d465fc17ab6327c",
+ "sha256": "86a027237f7408e8a4d18637c57d487455a704cbb2466a32d0d14c2207d2699a",
"urls": [
- "https://archive.mozilla.org/pub/firefox/releases/155.0/win64/en-US/Firefox%20Setup%20155.0.exe"
+ "https://archive.mozilla.org/pub/firefox/releases/156.0/win64/en-US/Firefox%20Setup%20156.0.exe"
],
"named_files": {
"FIREFOX": "core/firefox.exe"
diff --git a/modules/testing/builder/package.json b/modules/testing/builder/package.json
index 3e0dc743f7ea..d76148352027 100644
--- a/modules/testing/builder/package.json
+++ b/modules/testing/builder/package.json
@@ -8,7 +8,7 @@
"browser-sync": "3.0.4",
"istanbul-lib-instrument": "6.0.3",
"jsdom": "30.0.1",
- "ng-packagr": "22.2.0-next.5",
+ "ng-packagr": "22.2.0-rc.0",
"rxjs": "7.8.2",
"vitest": "5.0.1"
}
diff --git a/modules/testing/builder/src/builder-harness.ts b/modules/testing/builder/src/builder-harness.ts
index 67b5f760d148..570939cecd7f 100644
--- a/modules/testing/builder/src/builder-harness.ts
+++ b/modules/testing/builder/src/builder-harness.ts
@@ -111,7 +111,7 @@ export class BuilderHarness {
}
}
- private resolvePath(path: string): string {
+ resolvePath(path: string): string {
return join(getSystemPath(this.host.root()), path);
}
diff --git a/package.json b/package.json
index 69fd5f4f7f42..331ab53b141f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@angular/devkit-repo",
- "version": "22.2.0-next.7",
+ "version": "22.2.0-rc.0",
"private": true,
"description": "Software Development Kit for Angular",
"keywords": [
@@ -15,6 +15,7 @@
"bazel": "bazelisk",
"test": "bazel test //packages/...",
"build": "pnpm --silent admin build",
+ "benchmark": "node --no-warnings=ExperimentalWarning --experimental-transform-types --expose-gc ./scripts/devkit-admin.mts benchmark",
"build-schema": "bazel build //... --build_tag_filters schema --symlink_prefix dist-schema/",
"lint": "eslint --cache --max-warnings=0",
"templates": "pnpm --silent admin templates",
@@ -42,23 +43,23 @@
},
"homepage": "https://github.com/angular/angular-cli",
"dependencies": {
- "@angular/compiler-cli": "22.2.0-next.5",
+ "@angular/compiler-cli": "22.2.0-rc.0",
"typescript": "6.0.3"
},
"devDependencies": {
- "@angular/animations": "22.2.0-next.5",
- "@angular/cdk": "22.2.0-next.4",
- "@angular/common": "22.2.0-next.5",
- "@angular/compiler": "22.2.0-next.5",
- "@angular/core": "22.2.0-next.5",
- "@angular/forms": "22.2.0-next.5",
- "@angular/localize": "22.2.0-next.5",
- "@angular/material": "22.2.0-next.4",
- "@angular/ng-dev": "https://github.com/angular/dev-infra-private-ng-dev-builds.git#c3d7ed184d71d390d3eacff85f4b2b3f16b82660",
- "@angular/platform-browser": "22.2.0-next.5",
- "@angular/platform-server": "22.2.0-next.5",
- "@angular/router": "22.2.0-next.5",
- "@angular/service-worker": "22.2.0-next.5",
+ "@angular/animations": "22.2.0-rc.0",
+ "@angular/cdk": "22.2.0-rc.0",
+ "@angular/common": "22.2.0-rc.0",
+ "@angular/compiler": "22.2.0-rc.0",
+ "@angular/core": "22.2.0-rc.0",
+ "@angular/forms": "22.2.0-rc.0",
+ "@angular/localize": "22.2.0-rc.0",
+ "@angular/material": "22.2.0-rc.0",
+ "@angular/ng-dev": "https://github.com/angular/dev-infra-private-ng-dev-builds.git#f5de34cabca7e005dc8102d3abb026d01a7a881a",
+ "@angular/platform-browser": "22.2.0-rc.0",
+ "@angular/platform-server": "22.2.0-rc.0",
+ "@angular/router": "22.2.0-rc.0",
+ "@angular/service-worker": "22.2.0-rc.0",
"@babel/core": "8.0.5",
"@bazel/bazelisk": "1.28.1",
"@bazel/buildifier": "8.2.1",
diff --git a/packages/angular/build/package.json b/packages/angular/build/package.json
index 25d640c7619f..9e0b0b7c4ff1 100644
--- a/packages/angular/build/package.json
+++ b/packages/angular/build/package.json
@@ -57,7 +57,7 @@
"istanbul-lib-instrument": "6.0.3",
"jsdom": "30.0.1",
"less": "4.9.1",
- "ng-packagr": "22.2.0-next.5",
+ "ng-packagr": "22.2.0-rc.0",
"postcss": "8.5.28",
"rollup": "4.63.3",
"rxjs": "7.8.2",
diff --git a/packages/angular/build/src/builders/application/build-action.ts b/packages/angular/build/src/builders/application/build-action.ts
index edd0ae7d22f0..299d7cf88ef4 100644
--- a/packages/angular/build/src/builders/application/build-action.ts
+++ b/packages/angular/build/src/builders/application/build-action.ts
@@ -7,8 +7,6 @@
*/
import { BuilderContext } from '@angular-devkit/architect';
-import { existsSync } from 'node:fs';
-import path from 'node:path';
import {
BuildOutputAsset,
ExecutionResult,
@@ -21,10 +19,8 @@ import {
} from '../../tools/esbuild/stylesheets/sass-language';
import { logMessages, withNoProgress, withSpinner } from '../../tools/esbuild/utils';
import { ChangedFiles } from '../../tools/esbuild/watcher';
-import { shouldWatchRoot } from '../../utils/environment-options';
import { initializeHash } from '../../utils/hash';
import { NormalizedCachedOptions } from '../../utils/normalize-cache';
-import { toPosixPath } from '../../utils/path';
import { NormalizedApplicationBuildOptions, NormalizedOutputOptions } from './options';
import {
ComponentUpdateResult,
@@ -35,20 +31,6 @@ import {
ResultMessage,
} from './results';
-// Watch workspace for package manager changes
-const packageWatchFiles = [
- // manifest can affect module resolution
- 'package.json',
- // npm lock file
- 'package-lock.json',
- // pnpm lock file
- 'pnpm-lock.yaml',
- // yarn lock file including Yarn PnP manifest files (https://yarnpkg.com/advanced/pnp-spec/)
- 'yarn.lock',
- '.pnp.cjs',
- '.pnp.data.json',
-];
-
// eslint-disable-next-line max-lines-per-function
export async function* runEsBuildBuildAction(
action: (rebuildState?: RebuildState) => Promise,
@@ -115,55 +97,18 @@ export async function* runEsBuildBuildAction(
logger.info('Watch mode enabled. Watching for file changes...');
}
- const normalizedOutputBase = toPosixPath(outputOptions.base);
- const normalizedCacheBase = toPosixPath(cacheOptions.basePath);
- const ignored: string[] = [
- // Ignore the output and cache paths to avoid infinite rebuild cycles
- normalizedOutputBase,
- `${normalizedOutputBase}/**`,
- normalizedCacheBase,
- `${normalizedCacheBase}/**`,
- `${toPosixPath(workspaceRoot)}/**/.*/**`,
- ];
-
- if (cacheOptions.localBasePath && cacheOptions.localBasePath !== cacheOptions.basePath) {
- const normalizedLocalCacheBase = toPosixPath(cacheOptions.localBasePath);
- ignored.push(normalizedLocalCacheBase, `${normalizedLocalCacheBase}/**`);
- }
-
// Setup a watcher
- const { createWatcher } = await import('../../tools/esbuild/watcher');
- watcher = await createWatcher({
- polling: typeof poll === 'number',
- interval: poll,
- followSymlinks: preserveSymlinks,
- ignored,
- cwd: workspaceRoot,
+ const { setupWatcher } = await import('../../tools/esbuild/watcher');
+ watcher = await setupWatcher({
+ workspaceRoot,
+ projectRoot,
+ outputPath: outputOptions.base,
+ cacheOptions,
+ poll,
+ preserveSymlinks,
+ signal: options.signal,
+ watchFiles: result.watchFiles,
});
-
- // Setup abort support
- options.signal?.addEventListener('abort', () => void watcher?.close());
-
- // Watch the entire project root if 'NG_BUILD_WATCH_ROOT' environment variable is set
- if (shouldWatchRoot) {
- if (!preserveSymlinks) {
- // Ignore all node modules directories to avoid excessive file watchers.
- // Package changes are handled below by watching manifest and lock files.
- // NOTE: this is not enable when preserveSymlinks is true as this would break `npm link` usages.
- ignored.push('**/node_modules/**');
-
- watcher.add(
- packageWatchFiles
- .map((file) => path.join(workspaceRoot, file))
- .filter((file) => existsSync(file)),
- );
- }
-
- watcher.add(projectRoot);
- }
-
- // Watch locations provided by the initial build result
- watcher.add(result.watchFiles);
}
// Output the first build results after setting up the watcher to ensure that any code executed
diff --git a/packages/angular/build/src/builders/application/i18n.ts b/packages/angular/build/src/builders/application/i18n.ts
index d4656c046997..0d6ae77cdea7 100644
--- a/packages/angular/build/src/builders/application/i18n.ts
+++ b/packages/angular/build/src/builders/application/i18n.ts
@@ -17,7 +17,7 @@ import {
} from '../../tools/esbuild/bundler-execution-result';
import { BuildOutputFileType, InitialFileRecord } from '../../tools/esbuild/bundler-files';
import { I18nInliner } from '../../tools/esbuild/i18n-inliner';
-import { maxWorkers } from '../../utils/environment-options';
+import { maxInlinerWorkers } from '../../utils/environment-options';
import { loadTranslations } from '../../utils/i18n-options';
import { createTranslationLoader } from '../../utils/load-translations';
import { createProjectResolver } from '../../utils/resolve-project';
@@ -51,7 +51,9 @@ export async function inlineI18n(
const inliner = new I18nInliner(
{
missingTranslation: i18nOptions.missingTranslationBehavior ?? 'warning',
- maxConcurrency: workerPool ? undefined : maxWorkers,
+ maxConcurrency: workerPool
+ ? Math.min(workerPool.maxThreads, maxInlinerWorkers)
+ : maxInlinerWorkers,
persistentCachePath: cacheOptions.enabled ? cacheOptions.path : undefined,
localizeVersion: i18nOptions.localizeVersion,
},
diff --git a/packages/angular/build/src/builders/application/options.ts b/packages/angular/build/src/builders/application/options.ts
index dc53d55b61f0..785d07371d93 100644
--- a/packages/angular/build/src/builders/application/options.ts
+++ b/packages/angular/build/src/builders/application/options.ts
@@ -9,7 +9,6 @@
import type { BuilderContext } from '@angular-devkit/architect';
import type { Plugin } from 'esbuild';
import { access, constants, readFile } from 'node:fs/promises';
-import { createRequire } from 'node:module';
import path from 'node:path';
import { normalizeAssetPatterns, normalizeOptimization, normalizeSourceMaps } from '../../utils';
import { supportColor } from '../../utils/color';
@@ -19,9 +18,8 @@ import { IndexHtmlTransform } from '../../utils/index-file/index-html-generator'
import { normalizeCacheOptions } from '../../utils/normalize-cache';
import { canonicalizePath } from '../../utils/path';
import {
- SearchDirectory,
- findTailwindConfiguration,
generateSearchDirectories,
+ getTailwindConfig,
loadPostcssConfiguration,
} from '../../utils/postcss-configuration';
import { getProjectRootPaths, normalizeDirectoryPath } from '../../utils/project-metadata';
@@ -280,7 +278,7 @@ export async function normalizeOptions(
// Skip tailwind configuration if postcss is customized
const tailwindConfiguration = postcssConfiguration
? undefined
- : await getTailwindConfig(searchDirectories, workspaceRoot, context);
+ : await getTailwindConfig(searchDirectories, workspaceRoot, context.logger);
let serverEntryPoint: string | undefined;
if (typeof options.server === 'string') {
@@ -538,36 +536,6 @@ export async function normalizeOptions(
};
}
-async function getTailwindConfig(
- searchDirectories: SearchDirectory[],
- workspaceRoot: string,
- context: BuilderContext,
-): Promise<{ file: string; package: string } | undefined> {
- const tailwindConfigurationPath = findTailwindConfiguration(searchDirectories);
-
- if (!tailwindConfigurationPath) {
- return undefined;
- }
-
- // Create a node resolver from the configuration file
- const resolver = createRequire(tailwindConfigurationPath);
- try {
- return {
- file: tailwindConfigurationPath,
- package: resolver.resolve('tailwindcss'),
- };
- } catch {
- const relativeTailwindConfigPath = path.relative(workspaceRoot, tailwindConfigurationPath);
- context.logger.warn(
- `Tailwind CSS configuration file found (${relativeTailwindConfigPath})` +
- ` but the 'tailwindcss' package is not installed.` +
- ` To enable Tailwind CSS, please install the 'tailwindcss' package.`,
- );
- }
-
- return undefined;
-}
-
/**
* Normalize entry point options. To maintain compatibility with the legacy browser builder, we need a single `browser`
* option which defines a single entry point. However, we also want to support multiple entry points as an internal option.
diff --git a/packages/angular/build/src/builders/dev-server/tests/behavior/build-errors_spec.ts b/packages/angular/build/src/builders/dev-server/tests/behavior/build-errors_spec.ts
index a17c0a198e14..9608e0dc976e 100644
--- a/packages/angular/build/src/builders/dev-server/tests/behavior/build-errors_spec.ts
+++ b/packages/angular/build/src/builders/dev-server/tests/behavior/build-errors_spec.ts
@@ -38,8 +38,8 @@ describeServeBuilder(executeDevServer, DEV_SERVER_BUILDER_INFO, (harness, setupT
expectNoLog(logs, 'Unexpected character "EOF"');
},
],
- { outputLogsOnFailure: false, timeout: 60_000 },
+ { outputLogsOnFailure: false },
);
- }, 90_000);
+ });
});
});
diff --git a/packages/angular/build/src/tools/esbuild/persistent-load-result-cache.ts b/packages/angular/build/src/tools/esbuild/persistent-load-result-cache.ts
index 97f7d5cbe1c6..9761ff9aff64 100644
--- a/packages/angular/build/src/tools/esbuild/persistent-load-result-cache.ts
+++ b/packages/angular/build/src/tools/esbuild/persistent-load-result-cache.ts
@@ -31,6 +31,7 @@ import type { Loader, OnLoadResult, PartialMessage } from 'esbuild';
import { readFile, stat } from 'node:fs/promises';
import { isAbsolute } from 'node:path';
import { fileURLToPath } from 'node:url';
+import { mapConcurrent, runConcurrent } from '../../utils/concurrency';
import { calculateHash, createContentHash } from '../../utils/hash';
import type { Cache as PersistentCacheStore } from './cache';
import { LoadResultCache, MemoryLoadResultCache } from './load-result-cache';
@@ -114,29 +115,6 @@ export function extractDiskFilePath(path: string): string | undefined {
/** Maximum number of concurrent file system read/stat operations to prevent OS file descriptor exhaustion. */
const MAX_CONCURRENT_READS = 16;
-/**
- * Maps an array asynchronously with a sliding worker pool to maintain full concurrency saturation.
- */
-async function mapConcurrent(
- items: T[],
- limit: number,
- fn: (item: T) => Promise,
-): Promise {
- const results: R[] = new Array(items.length);
- let index = 0;
-
- const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {
- while (index < items.length) {
- const i = index++;
- results[i] = await fn(items[i]);
- }
- });
-
- await Promise.all(workers);
-
- return results;
-}
-
/**
* Validates that all imported watch files exist on disk and their contents match.
* Performs a fast-path metadata check (mtime + size) first, falling back to content hashing.
@@ -214,7 +192,7 @@ async function computeMetadataForWatchFiles(
): Promise> {
const watchFilesMetadata: Record = {};
- await mapConcurrent(watchFiles, MAX_CONCURRENT_READS, async (filePath) => {
+ await runConcurrent(watchFiles, MAX_CONCURRENT_READS, async (filePath) => {
try {
const knownContent = knownContents?.get(filePath);
const [content, stats] = await Promise.all([
diff --git a/packages/angular/build/src/tools/esbuild/stylesheets/bundle-options.ts b/packages/angular/build/src/tools/esbuild/stylesheets/bundle-options.ts
index 7fa20dde64ae..bb5c945bab7d 100644
--- a/packages/angular/build/src/tools/esbuild/stylesheets/bundle-options.ts
+++ b/packages/angular/build/src/tools/esbuild/stylesheets/bundle-options.ts
@@ -22,6 +22,7 @@ export interface BundleStylesheetOptions {
workspaceRoot: string;
optimization: boolean;
inlineFonts: boolean;
+ dataurl?: boolean;
preserveSymlinks?: boolean;
sourcemap: boolean | 'external' | 'inline' | 'linked';
sourcesContent?: boolean;
@@ -62,7 +63,7 @@ export function createStylesheetBundleOptions(
pluginFactory.create(SassStylesheetLanguage),
pluginFactory.create(LessStylesheetLanguage),
pluginFactory.create(CssStylesheetLanguage),
- createCssResourcePlugin(cache),
+ createCssResourcePlugin(cache, options.dataurl),
];
if (options.inlineFonts) {
diff --git a/packages/angular/build/src/tools/esbuild/stylesheets/css-resource-plugin.ts b/packages/angular/build/src/tools/esbuild/stylesheets/css-resource-plugin.ts
index 7f83e7dc7a8f..ced6422c3fd3 100644
--- a/packages/angular/build/src/tools/esbuild/stylesheets/css-resource-plugin.ts
+++ b/packages/angular/build/src/tools/esbuild/stylesheets/css-resource-plugin.ts
@@ -25,9 +25,11 @@ const CSS_RESOURCE_RESOLUTION = Symbol('CSS_RESOURCE_RESOLUTION');
* and types to be supported without needing to manually specify all extensions
* within the build configuration.
*
+ * @param cache An optional load result cache.
+ * @param dataurl If true, resources will be loaded with the 'dataurl' loader to inline them as base64 data URIs.
* @returns An esbuild {@link Plugin} instance.
*/
-export function createCssResourcePlugin(cache?: LoadResultCache): Plugin {
+export function createCssResourcePlugin(cache?: LoadResultCache, dataurl?: boolean): Plugin {
return {
name: 'angular-css-resource',
setup(build: PluginBuild): void {
@@ -119,7 +121,7 @@ export function createCssResourcePlugin(cache?: LoadResultCache): Plugin {
return {
contents: await readFile(resourcePath),
- loader: 'file',
+ loader: dataurl ? 'dataurl' : 'file',
watchFiles: [resourcePath],
};
}),
diff --git a/packages/angular/build/src/tools/esbuild/watcher.ts b/packages/angular/build/src/tools/esbuild/watcher.ts
index fe41275e479e..e30909fa6813 100644
--- a/packages/angular/build/src/tools/esbuild/watcher.ts
+++ b/packages/angular/build/src/tools/esbuild/watcher.ts
@@ -8,9 +8,11 @@
import type * as ParcelWatcher from '@parcel/watcher';
import type * as Chokidar from 'chokidar';
+import { once } from 'node:events';
import * as fs from 'node:fs';
import * as path from 'node:path';
import picomatch from 'picomatch';
+import { shouldWatchRoot } from '../../utils/environment-options';
import { toPosixPath } from '../../utils/path';
export class ChangedFiles {
@@ -47,6 +49,108 @@ export interface WatcherOptions {
cwd?: string;
}
+// Watch workspace for package manager changes
+const packageWatchFiles = [
+ // manifest can affect module resolution
+ 'package.json',
+ // npm lock file
+ 'package-lock.json',
+ // pnpm lock file
+ 'pnpm-lock.yaml',
+ // yarn lock file including Yarn PnP manifest files (https://yarnpkg.com/advanced/pnp-spec/)
+ 'yarn.lock',
+ '.pnp.cjs',
+ '.pnp.data.json',
+];
+
+export interface SetupWatcherOptions {
+ workspaceRoot: string;
+ projectRoot: string;
+ outputPath: string;
+ cacheOptions: { basePath: string; localBasePath?: string };
+ poll?: number;
+ preserveSymlinks?: boolean;
+ signal?: AbortSignal;
+ watchFiles?: Iterable;
+}
+
+/**
+ * Sets up and initializes a file watcher with proper ignore patterns for build outputs and caches.
+ */
+export async function setupWatcher(options: SetupWatcherOptions): Promise {
+ const {
+ workspaceRoot,
+ projectRoot,
+ outputPath,
+ cacheOptions,
+ poll,
+ preserveSymlinks,
+ signal,
+ watchFiles,
+ } = options;
+
+ const normalizedOutputBase = toPosixPath(outputPath);
+ const normalizedCacheBase = toPosixPath(cacheOptions.basePath);
+ const ignored: string[] = [
+ // Ignore the output and cache paths to avoid infinite rebuild cycles
+ normalizedOutputBase,
+ `${normalizedOutputBase}/**`,
+ normalizedCacheBase,
+ `${normalizedCacheBase}/**`,
+ `${toPosixPath(workspaceRoot)}/**/.*/**`,
+ ];
+
+ if (cacheOptions.localBasePath && cacheOptions.localBasePath !== cacheOptions.basePath) {
+ const normalizedLocalCacheBase = toPosixPath(cacheOptions.localBasePath);
+ ignored.push(normalizedLocalCacheBase, `${normalizedLocalCacheBase}/**`);
+ }
+
+ if (shouldWatchRoot && !preserveSymlinks) {
+ // Ignore all node modules directories to avoid excessive file watchers.
+ // Package changes are handled below by watching manifest and lock files.
+ // NOTE: this is not enabled when preserveSymlinks is true as this would break `npm link` usages.
+ ignored.push('**/node_modules/**');
+ }
+
+ const watcher = await createWatcher({
+ polling: typeof poll === 'number',
+ interval: poll,
+ followSymlinks: preserveSymlinks,
+ ignored,
+ cwd: workspaceRoot,
+ });
+
+ // Setup abort support
+ if (signal) {
+ const onAbort = () => void watcher.close();
+ signal.addEventListener('abort', onAbort, { once: true });
+ const originalClose = watcher.close.bind(watcher);
+ watcher.close = async () => {
+ signal.removeEventListener('abort', onAbort);
+ await originalClose();
+ };
+ }
+
+ // Watch the entire project root if 'NG_BUILD_WATCH_ROOT' environment variable is set
+ if (shouldWatchRoot) {
+ if (!preserveSymlinks) {
+ watcher.add(
+ packageWatchFiles
+ .map((file) => path.join(workspaceRoot, file))
+ .filter((file) => fs.existsSync(file)),
+ );
+ }
+
+ watcher.add(projectRoot);
+ }
+
+ if (watchFiles) {
+ watcher.add(Array.isArray(watchFiles) ? watchFiles : Array.from(watchFiles));
+ }
+
+ return watcher;
+}
+
/**
* Probes the filesystem at the specified target directory to determine whether it is case-sensitive.
*/
@@ -560,8 +664,15 @@ async function createChokidarWatcher(
usePolling: !!options?.polling,
interval: options?.interval,
});
+
const initTime = Date.now();
+ // Wait for the watcher to complete its initial filesystem scan before returning.
+ // With `ignoreInitial: true`, any file visited during the initial scan is treated as the initial baseline
+ // and will not emit 'add' or 'change' events. Awaiting 'ready' ensures that rapid file modifications
+ // made right after watcher setup (e.g. in rebuild tests) are not swallowed as initial files.
+ await once(watcher, 'ready');
+
const handleEvent = (type: 'added' | 'modified' | 'removed', rawPath: string) => {
const posixPath = toPosixPathNormalized(rawPath);
const lookupKey = toLookupKey(posixPath, isCaseSensitive);
diff --git a/packages/angular/build/src/tools/esbuild/watcher_spec.ts b/packages/angular/build/src/tools/esbuild/watcher_spec.ts
index 5c7853012762..e051c44bd3f2 100644
--- a/packages/angular/build/src/tools/esbuild/watcher_spec.ts
+++ b/packages/angular/build/src/tools/esbuild/watcher_spec.ts
@@ -16,6 +16,7 @@ import {
createWatcher,
getDirectoryPath,
isPathInside,
+ setupWatcher,
toPosixPathNormalized,
} from './watcher';
@@ -117,6 +118,56 @@ describe('Watcher', () => {
});
});
+ describe('setupWatcher', () => {
+ let tempDir: string;
+
+ beforeEach(() => {
+ tempDir = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'setup-watcher-spec-')));
+ });
+
+ afterEach(() => {
+ if (fs.existsSync(tempDir)) {
+ fs.rmSync(tempDir, { recursive: true, force: true });
+ }
+ });
+
+ it('should setup watcher with watchFiles and close on abort signal', async () => {
+ const abortController = new AbortController();
+ const testFile = path.join(tempDir, 'main.ts');
+ const watcher = await setupWatcher({
+ workspaceRoot: tempDir,
+ projectRoot: tempDir,
+ outputPath: path.join(tempDir, 'dist'),
+ cacheOptions: { basePath: path.join(tempDir, '.cache') },
+ watchFiles: [testFile],
+ signal: abortController.signal,
+ });
+
+ expect(watcher).toBeDefined();
+
+ const closeSpy = spyOn(watcher, 'close').and.callThrough();
+ abortController.abort();
+
+ expect(closeSpy).toHaveBeenCalled();
+ await watcher.close();
+ });
+
+ it('should remove abort listener when watcher is closed', async () => {
+ const abortController = new AbortController();
+ const removeSpy = spyOn(abortController.signal, 'removeEventListener').and.callThrough();
+ const watcher = await setupWatcher({
+ workspaceRoot: tempDir,
+ projectRoot: tempDir,
+ outputPath: path.join(tempDir, 'dist'),
+ cacheOptions: { basePath: path.join(tempDir, '.cache') },
+ signal: abortController.signal,
+ });
+
+ await watcher.close();
+ expect(removeSpy).toHaveBeenCalledWith('abort', jasmine.any(Function));
+ });
+ });
+
describe('createWatcher', () => {
let tempDir: string;
diff --git a/packages/angular/build/src/utils/concurrency.ts b/packages/angular/build/src/utils/concurrency.ts
new file mode 100644
index 000000000000..a38a9a57fa36
--- /dev/null
+++ b/packages/angular/build/src/utils/concurrency.ts
@@ -0,0 +1,93 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.dev/license
+ */
+
+/**
+ * Executes an asynchronous function for each item in an array concurrently up to a specified limit.
+ *
+ * If any task fails, processing of subsequent items stops and the first encountered error is re-thrown
+ * after all currently in-flight tasks have settled.
+ *
+ * @param items Array of items to process.
+ * @param limit Maximum number of concurrent tasks in flight.
+ * @param fn Async task function.
+ */
+export async function runConcurrent(
+ items: readonly T[],
+ limit: number,
+ fn: (item: T, index: number) => Promise,
+): Promise {
+ if (items.length === 0) {
+ return;
+ }
+
+ let index = 0;
+ let firstError: unknown;
+
+ const concurrency = Math.min(Math.max(1, Math.floor(limit) || 1), items.length);
+ const workers = Array.from({ length: concurrency }, async () => {
+ while (!firstError && index < items.length) {
+ const i = index++;
+ try {
+ await fn(items[i], i);
+ } catch (error) {
+ firstError ??= error;
+ }
+ }
+ });
+
+ await Promise.allSettled(workers);
+
+ if (firstError) {
+ throw firstError;
+ }
+}
+
+/**
+ * Maps an array asynchronously with a sliding worker pool up to a specified concurrency limit.
+ *
+ * If any task fails, processing of subsequent items stops and the first encountered error is re-thrown
+ * after all currently in-flight tasks have settled.
+ *
+ * @param items Array of items to map.
+ * @param limit Maximum number of concurrent tasks in flight.
+ * @param fn Async mapper function.
+ * @returns Array of mapped results in the original item order.
+ */
+export async function mapConcurrent(
+ items: readonly T[],
+ limit: number,
+ fn: (item: T, index: number) => Promise,
+): Promise {
+ if (items.length === 0) {
+ return [];
+ }
+
+ const results: R[] = new Array(items.length);
+ let index = 0;
+ let firstError: unknown;
+
+ const concurrency = Math.min(Math.max(1, Math.floor(limit) || 1), items.length);
+ const workers = Array.from({ length: concurrency }, async () => {
+ while (!firstError && index < items.length) {
+ const i = index++;
+ try {
+ results[i] = await fn(items[i], i);
+ } catch (error) {
+ firstError ??= error;
+ }
+ }
+ });
+
+ await Promise.allSettled(workers);
+
+ if (firstError) {
+ throw firstError;
+ }
+
+ return results;
+}
diff --git a/packages/angular/build/src/utils/concurrency_spec.ts b/packages/angular/build/src/utils/concurrency_spec.ts
new file mode 100644
index 000000000000..ab63054046c5
--- /dev/null
+++ b/packages/angular/build/src/utils/concurrency_spec.ts
@@ -0,0 +1,212 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.dev/license
+ */
+
+import { mapConcurrent, runConcurrent } from './concurrency';
+
+describe('concurrency utilities', () => {
+ describe('runConcurrent', () => {
+ it('should process all items in an array', async () => {
+ const items = [1, 2, 3, 4, 5];
+ const processed: number[] = [];
+
+ await runConcurrent(items, 2, async (item) => {
+ processed.push(item);
+ });
+
+ expect(processed.sort((a, b) => a - b)).toEqual(items);
+ });
+
+ it('should respect the concurrency limit', async () => {
+ const items = [10, 20, 30, 40, 50, 60];
+ const limit = 2;
+ let active = 0;
+ let maxActive = 0;
+
+ await runConcurrent(items, limit, async () => {
+ active++;
+ maxActive = Math.max(maxActive, active);
+ await new Promise((resolve) => setTimeout(resolve, 10));
+ active--;
+ });
+
+ expect(maxActive).toBeLessThanOrEqual(limit);
+ });
+
+ it('should handle non-integer and NaN limits', async () => {
+ const items = [1, 2, 3];
+ const processed: number[] = [];
+
+ await runConcurrent(items, 2.7, async (item) => {
+ processed.push(item);
+ });
+ expect(processed).toEqual(items);
+
+ const processedNaN: number[] = [];
+ await runConcurrent(items, NaN, async (item) => {
+ processedNaN.push(item);
+ });
+ expect(processedNaN).toEqual(items);
+ });
+
+ it('should handle an empty array', async () => {
+ let called = false;
+ await runConcurrent([], 3, async () => {
+ called = true;
+ });
+
+ expect(called).toBe(false);
+ });
+
+ it('should pass item and index to callback', async () => {
+ const items = ['a', 'b', 'c'];
+ const passed: { item: string; index: number }[] = [];
+
+ await runConcurrent(items, 2, async (item, index) => {
+ passed.push({ item, index });
+ });
+
+ expect(passed.sort((a, b) => a.index - b.index)).toEqual([
+ { item: 'a', index: 0 },
+ { item: 'b', index: 1 },
+ { item: 'c', index: 2 },
+ ]);
+ });
+
+ it('should stop processing new items and rethrow the first error', async () => {
+ const items = [1, 2, 3, 4, 5, 6];
+ const executed: number[] = [];
+
+ await expectAsync(
+ runConcurrent(items, 1, async (item) => {
+ executed.push(item);
+ if (item === 2) {
+ throw new Error('Task failed');
+ }
+ }),
+ ).toBeRejectedWithError('Task failed');
+
+ // Subsequent items should not have been executed
+ expect(executed).toEqual([1, 2]);
+ });
+
+ it('should wait for in-flight tasks to settle when an error occurs', async () => {
+ const items = [1, 2, 3, 4];
+ let task2Finished = false;
+
+ await expectAsync(
+ runConcurrent(items, 2, async (item) => {
+ if (item === 1) {
+ throw new Error('Task 1 failed');
+ }
+ if (item === 2) {
+ await new Promise((resolve) => setTimeout(resolve, 20));
+ task2Finished = true;
+ }
+ }),
+ ).toBeRejectedWithError('Task 1 failed');
+
+ expect(task2Finished).toBe(true);
+ });
+ });
+
+ describe('mapConcurrent', () => {
+ it('should map items and return results in original order', async () => {
+ const items = [1, 2, 3, 4, 5];
+
+ const results = await mapConcurrent(items, 2, async (item) => {
+ // Add varying delay so tasks finish out of order
+ await new Promise((resolve) => setTimeout(resolve, (5 - item) * 5));
+
+ return item * 2;
+ });
+
+ expect(results).toEqual([2, 4, 6, 8, 10]);
+ });
+
+ it('should respect the concurrency limit', async () => {
+ const items = [1, 2, 3, 4, 5];
+ const limit = 2;
+ let active = 0;
+ let maxActive = 0;
+
+ await mapConcurrent(items, limit, async (item) => {
+ active++;
+ maxActive = Math.max(maxActive, active);
+ await new Promise((resolve) => setTimeout(resolve, 10));
+ active--;
+
+ return item;
+ });
+
+ expect(maxActive).toBeLessThanOrEqual(limit);
+ });
+
+ it('should handle non-integer and NaN limits', async () => {
+ const items = [1, 2, 3];
+
+ const results = await mapConcurrent(items, 2.7, async (item) => item * 2);
+ expect(results).toEqual([2, 4, 6]);
+
+ const resultsNaN = await mapConcurrent(items, NaN, async (item) => item * 2);
+ expect(resultsNaN).toEqual([2, 4, 6]);
+ });
+
+ it('should handle an empty array', async () => {
+ const results = await mapConcurrent([], 3, async (item) => item);
+
+ expect(results).toEqual([]);
+ });
+
+ it('should pass item and index to mapper function', async () => {
+ const items = ['x', 'y', 'z'];
+
+ const results = await mapConcurrent(items, 2, async (item, index) => `${item}:${index}`);
+
+ expect(results).toEqual(['x:0', 'y:1', 'z:2']);
+ });
+
+ it('should stop processing new items and rethrow the first error', async () => {
+ const items = [1, 2, 3, 4, 5, 6];
+ const executed: number[] = [];
+
+ await expectAsync(
+ mapConcurrent(items, 1, async (item) => {
+ executed.push(item);
+ if (item === 2) {
+ throw new Error('Map failed');
+ }
+
+ return item;
+ }),
+ ).toBeRejectedWithError('Map failed');
+
+ expect(executed).toEqual([1, 2]);
+ });
+
+ it('should wait for in-flight tasks to settle when an error occurs', async () => {
+ const items = [1, 2, 3, 4];
+ let task2Finished = false;
+
+ await expectAsync(
+ mapConcurrent(items, 2, async (item) => {
+ if (item === 1) {
+ throw new Error('Map 1 failed');
+ }
+ if (item === 2) {
+ await new Promise((resolve) => setTimeout(resolve, 20));
+ task2Finished = true;
+ }
+
+ return item;
+ }),
+ ).toBeRejectedWithError('Map 1 failed');
+
+ expect(task2Finished).toBe(true);
+ });
+ });
+});
diff --git a/packages/angular/build/src/utils/environment-options.ts b/packages/angular/build/src/utils/environment-options.ts
index 29faf3e7bbae..d87c104aba65 100644
--- a/packages/angular/build/src/utils/environment-options.ts
+++ b/packages/angular/build/src/utils/environment-options.ts
@@ -141,6 +141,15 @@ export const maxWorkers = customMaxWorkers ?? Math.max(availableParallelism() -
export const maxTransformWorkers =
customMaxWorkers ?? Math.max(1, Math.min(6, Math.floor(availableParallelism() / 4)));
+/**
+ * The maximum number of workers to use for i18n translation inlining.
+ * Translation inlining and sourcemap remapping are CPU- and memory-intensive operations.
+ * To prevent thread oversubscription, memory allocator lock contention, and high-core
+ * performance degradation, concurrency is capped at 8 unless overridden by
+ * `NG_BUILD_MAX_WORKERS`.
+ */
+export const maxInlinerWorkers = customMaxWorkers ?? Math.min(8, availableParallelism());
+
/**
* When `NG_BUILD_PARALLEL_TS` is set to `0` or `false`, parallel TypeScript compilation is disabled.
*/
diff --git a/packages/angular/build/src/utils/environment-options_spec.ts b/packages/angular/build/src/utils/environment-options_spec.ts
index 4146bec8bec1..4ad098a24763 100644
--- a/packages/angular/build/src/utils/environment-options_spec.ts
+++ b/packages/angular/build/src/utils/environment-options_spec.ts
@@ -181,3 +181,67 @@ describe('environment options - maxTransformWorkers', () => {
expect(maxTransformWorkers).toBe(expected);
});
});
+
+describe('environment options - maxInlinerWorkers', () => {
+ const originalEnvValue = process.env['NG_BUILD_MAX_WORKERS'];
+
+ function loadEnvironmentOptions(): typeof import('./environment-options') {
+ delete require.cache[require.resolve('./environment-options')];
+
+ return require('./environment-options');
+ }
+
+ afterEach(() => {
+ if (originalEnvValue !== undefined) {
+ process.env['NG_BUILD_MAX_WORKERS'] = originalEnvValue;
+ } else {
+ delete process.env['NG_BUILD_MAX_WORKERS'];
+ }
+ delete require.cache[require.resolve('./environment-options')];
+ });
+
+ it('defaults maxInlinerWorkers to min(8, availableParallelism()) when NG_BUILD_MAX_WORKERS is unset', () => {
+ delete process.env['NG_BUILD_MAX_WORKERS'];
+ const { maxInlinerWorkers } = loadEnvironmentOptions();
+
+ expect(maxInlinerWorkers).toBe(Math.min(8, availableParallelism()));
+ });
+
+ it('uses configured positive integer when NG_BUILD_MAX_WORKERS is set', () => {
+ process.env['NG_BUILD_MAX_WORKERS'] = '4';
+ const { maxInlinerWorkers } = loadEnvironmentOptions();
+
+ expect(maxInlinerWorkers).toBe(4);
+ });
+
+ it('allows maxInlinerWorkers greater than 8 when explicitly configured', () => {
+ process.env['NG_BUILD_MAX_WORKERS'] = '16';
+ const { maxInlinerWorkers } = loadEnvironmentOptions();
+
+ expect(maxInlinerWorkers).toBe(16);
+ });
+
+ it('supports maxInlinerWorkers set to 1', () => {
+ process.env['NG_BUILD_MAX_WORKERS'] = '1';
+ const { maxInlinerWorkers } = loadEnvironmentOptions();
+
+ expect(maxInlinerWorkers).toBe(1);
+ });
+
+ it('falls back to min(8, availableParallelism()) when NG_BUILD_MAX_WORKERS is 0 or negative', () => {
+ process.env['NG_BUILD_MAX_WORKERS'] = '0';
+ const { maxInlinerWorkers: zeroWorkers } = loadEnvironmentOptions();
+ expect(zeroWorkers).toBe(Math.min(8, availableParallelism()));
+
+ process.env['NG_BUILD_MAX_WORKERS'] = '-4';
+ const { maxInlinerWorkers: negativeWorkers } = loadEnvironmentOptions();
+ expect(negativeWorkers).toBe(Math.min(8, availableParallelism()));
+ });
+
+ it('falls back to min(8, availableParallelism()) when NG_BUILD_MAX_WORKERS is invalid', () => {
+ process.env['NG_BUILD_MAX_WORKERS'] = 'invalid';
+ const { maxInlinerWorkers } = loadEnvironmentOptions();
+
+ expect(maxInlinerWorkers).toBe(Math.min(8, availableParallelism()));
+ });
+});
diff --git a/packages/angular/build/src/utils/index-file/auto-csp.ts b/packages/angular/build/src/utils/index-file/auto-csp.ts
index df33f0766607..5c1899015a7e 100644
--- a/packages/angular/build/src/utils/index-file/auto-csp.ts
+++ b/packages/angular/build/src/utils/index-file/auto-csp.ts
@@ -36,15 +36,40 @@ function getScriptAttributeValue(tag: StartTag, attrName: string): string | unde
return tag.attrs.find((attr) => attr.name === attrName)?.value;
}
+/**
+ * All MIME types associated with JavaScript according to the HTML specification:
+ * https://html.spec.whatwg.org/multipage/scripting.html#javascript-mime-type
+ */
+const JAVASCRIPT_MIME_TYPES = new Set([
+ 'application/ecmascript',
+ 'application/javascript',
+ 'application/x-ecmascript',
+ 'application/x-javascript',
+ 'text/ecmascript',
+ 'text/javascript',
+ 'text/javascript1.0',
+ 'text/javascript1.1',
+ 'text/javascript1.2',
+ 'text/javascript1.3',
+ 'text/javascript1.4',
+ 'text/javascript1.5',
+ 'text/jscript',
+ 'text/livescript',
+ 'text/x-ecmascript',
+ 'text/x-javascript',
+]);
+
/**
* Checks whether a particular string is a MIME type associated with JavaScript, according to
- * https://developer.mozilla.org/en-US/docs/Web/HTTP/MIME_types#textjavascript
+ * https://html.spec.whatwg.org/multipage/scripting.html#javascript-mime-type
*
* @param mimeType a string that may be a MIME type
* @returns whether the string is a MIME type that is associated with JavaScript
*/
-function isJavascriptMimeType(mimeType: string): boolean {
- return mimeType.split(';')[0] === 'text/javascript';
+export function isJavascriptMimeType(mimeType: string): boolean {
+ const [essence] = mimeType.split(';', 1);
+
+ return JAVASCRIPT_MIME_TYPES.has(essence.trim().toLowerCase());
}
/**
@@ -54,7 +79,11 @@ function isJavascriptMimeType(mimeType: string): boolean {
* @returns whether to add the script tag to the dynamically loaded script tag
*/
function shouldDynamicallyLoadScriptTagBasedOnType(scriptType: string | undefined): boolean {
- return !scriptType || scriptType === 'module' || isJavascriptMimeType(scriptType);
+ if (!scriptType) {
+ return true;
+ }
+
+ return scriptType.trim().toLowerCase() === 'module' || isJavascriptMimeType(scriptType);
}
/**
diff --git a/packages/angular/build/src/utils/index-file/auto-csp_spec.ts b/packages/angular/build/src/utils/index-file/auto-csp_spec.ts
index 29e9bff68074..4fe2888efa42 100644
--- a/packages/angular/build/src/utils/index-file/auto-csp_spec.ts
+++ b/packages/angular/build/src/utils/index-file/auto-csp_spec.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.dev/license
*/
-import { autoCsp, hashTextContent } from './auto-csp';
+import { autoCsp, hashTextContent, isJavascriptMimeType } from './auto-csp';
// Utility function to grab the meta tag CSPs from the HTML response.
const getCsps = (html: string) => {
@@ -281,4 +281,121 @@ describe('auto-csp', () => {
`const scripts = [['./main.js', '', false, false, null, "anonymous"]];`,
);
});
+
+ it('should rewrite scripts with application/javascript type', async () => {
+ const result = await autoCsp(`
+
+
+
+
+
+
+ `);
+
+ const csps = getCsps(result);
+ expect(csps).toHaveSize(1);
+ expect(csps[0]).toMatch(CSP_SINGLE_HASH_REGEX);
+ expect(result).toContain(
+ `const scripts = [['./main.js', 'application/javascript', false, false, null, null]];`,
+ );
+ });
+
+ it('should rewrite scripts with case-insensitive type and parameters with whitespace', async () => {
+ const result = await autoCsp(`
+
+
+
+
+
+
+ `);
+
+ const csps = getCsps(result);
+ expect(csps).toHaveSize(1);
+ expect(csps[0]).toMatch(CSP_SINGLE_HASH_REGEX);
+ expect(result).toContain(
+ `const scripts = [['./main.js', 'Text/JavaScript ; charset=utf-8', false, false, null, null]];`,
+ );
+ });
+
+ it('should rewrite scripts with case-insensitive module type', async () => {
+ const result = await autoCsp(`
+
+
+
+
+
+
+ `);
+
+ const csps = getCsps(result);
+ expect(csps).toHaveSize(1);
+ expect(csps[0]).toMatch(CSP_SINGLE_HASH_REGEX);
+ expect(result).toContain(
+ `const scripts = [['./main.js', 'Module', false, false, null, null]];`,
+ );
+ });
+
+ it('should not rewrite non-JavaScript script tags', async () => {
+ const result = await autoCsp(`
+
+
+
+
+
+
+ `);
+
+ // No dynamic loader script is emitted because application/json is not JavaScript.
+ expect(result).toContain('');
+ expect(result).not.toContain('const scripts =');
+ });
+
+ describe('isJavascriptMimeType', () => {
+ it('should identify standard JavaScript MIME types', () => {
+ expect(isJavascriptMimeType('text/javascript')).toBeTrue();
+ expect(isJavascriptMimeType('application/javascript')).toBeTrue();
+ expect(isJavascriptMimeType('application/x-javascript')).toBeTrue();
+ expect(isJavascriptMimeType('text/ecmascript')).toBeTrue();
+ expect(isJavascriptMimeType('application/ecmascript')).toBeTrue();
+ expect(isJavascriptMimeType('text/jscript')).toBeTrue();
+ expect(isJavascriptMimeType('text/livescript')).toBeTrue();
+ expect(isJavascriptMimeType('text/x-ecmascript')).toBeTrue();
+ expect(isJavascriptMimeType('text/x-javascript')).toBeTrue();
+ expect(isJavascriptMimeType('text/javascript1.5')).toBeTrue();
+ });
+
+ it('should ignore parameters when matching MIME type', () => {
+ expect(isJavascriptMimeType('text/javascript; charset=utf-8')).toBeTrue();
+ expect(isJavascriptMimeType('application/javascript;version=1.8')).toBeTrue();
+ });
+
+ it('should handle leading, trailing, and parameter whitespace', () => {
+ expect(isJavascriptMimeType(' text/javascript ')).toBeTrue();
+ expect(isJavascriptMimeType('text/javascript ; charset=utf-8')).toBeTrue();
+ expect(isJavascriptMimeType(' application/javascript ; version=1.0 ')).toBeTrue();
+ });
+
+ it('should be case-insensitive', () => {
+ expect(isJavascriptMimeType('Text/JavaScript')).toBeTrue();
+ expect(isJavascriptMimeType('APPLICATION/JAVASCRIPT')).toBeTrue();
+ expect(isJavascriptMimeType('text/JAVASCRIPT; charset=UTF-8')).toBeTrue();
+ });
+
+ it('should reject non-JavaScript MIME types', () => {
+ expect(isJavascriptMimeType('application/json')).toBeFalse();
+ expect(isJavascriptMimeType('text/html')).toBeFalse();
+ expect(isJavascriptMimeType('text/css')).toBeFalse();
+ expect(isJavascriptMimeType('image/svg+xml')).toBeFalse();
+ expect(isJavascriptMimeType('importmap')).toBeFalse();
+ expect(isJavascriptMimeType('module')).toBeFalse();
+ expect(isJavascriptMimeType('')).toBeFalse();
+ });
+
+ it('should reject invalid MIME types with whitespace inside the essence', () => {
+ expect(isJavascriptMimeType('text / javascript')).toBeFalse();
+ expect(isJavascriptMimeType('application / javascript')).toBeFalse();
+ expect(isJavascriptMimeType('text/java script')).toBeFalse();
+ });
+ });
});
diff --git a/packages/angular/build/src/utils/postcss-configuration.ts b/packages/angular/build/src/utils/postcss-configuration.ts
index 6f3f1f3671f9..abde0e632b30 100644
--- a/packages/angular/build/src/utils/postcss-configuration.ts
+++ b/packages/angular/build/src/utils/postcss-configuration.ts
@@ -7,7 +7,8 @@
*/
import { readFile, readdir } from 'node:fs/promises';
-import { join } from 'node:path';
+import { createRequire } from 'node:module';
+import { join, relative } from 'node:path';
export interface PostcssConfiguration {
plugins: [name: string, options?: object | string][];
@@ -62,6 +63,35 @@ export function findTailwindConfiguration(
return findFile(searchDirectories, tailwindConfigFiles);
}
+export async function getTailwindConfig(
+ searchDirectories: SearchDirectory[],
+ workspaceRoot: string,
+ logger?: { warn(message: string): void },
+): Promise<{ file: string; package: string } | undefined> {
+ const tailwindConfigurationPath = findTailwindConfiguration(searchDirectories);
+ if (!tailwindConfigurationPath) {
+ return undefined;
+ }
+
+ // Create a node resolver from the configuration file
+ const resolver = createRequire(tailwindConfigurationPath);
+ try {
+ return {
+ file: tailwindConfigurationPath,
+ package: resolver.resolve('tailwindcss'),
+ };
+ } catch {
+ const relativeTailwindConfigPath = relative(workspaceRoot, tailwindConfigurationPath);
+ logger?.warn(
+ `Tailwind CSS configuration file found (${relativeTailwindConfigPath})` +
+ ` but the 'tailwindcss' package is not installed.` +
+ ` To enable Tailwind CSS, please install the 'tailwindcss' package.`,
+ );
+ }
+
+ return undefined;
+}
+
async function readPostcssConfiguration(
configurationFile: string,
): Promise {
diff --git a/packages/angular/cli/src/command-builder/command-module.ts b/packages/angular/cli/src/command-builder/command-module.ts
index 35e0d4f8201d..ae0eb82f0cb5 100644
--- a/packages/angular/cli/src/command-builder/command-module.ts
+++ b/packages/angular/cli/src/command-builder/command-module.ts
@@ -7,6 +7,8 @@
*/
import { schema } from '@angular-devkit/core';
+import { readFileSync } from 'node:fs';
+import { join, posix, relative } from 'node:path';
import type { ArgumentsCamelCase, Argv, CommandModule as YargsCommandModule } from 'yargs';
import { Parser as yargsParser } from 'yargs/helpers';
import { getAnalyticsUserId } from '../analytics/analytics';
@@ -18,7 +20,6 @@ import { AngularWorkspace } from '../utilities/config';
import { memoize } from '../utilities/memoize';
import { CommandContext, CommandScope, Options, OtherOptions } from './definitions';
import { Option, addSchemaOptionsToCommand } from './utilities/json-schema';
-import '../utilities/markdown-loader';
export { CommandScope };
export type { CommandContext, Options, OtherOptions };
@@ -30,11 +31,8 @@ export interface CommandModuleImplementation extends Omit<
/** Scope in which the command can be executed in. */
scope: CommandScope;
- /** Long description for the command in JSON help text. */
- longDescription?: string;
-
- /** Relative path to the long description file for the command in JSON help text. */
- longDescriptionRelativePath?: string;
+ /** Path used to load the long description for the command in JSON help text. */
+ longDescriptionPath?: string;
/** Object declaring the options the command accepts, or a function accepting and returning a yargs instance. */
builder(argv: Argv): Promise> | Argv;
@@ -52,8 +50,7 @@ export interface FullDescribe {
export abstract class CommandModule implements CommandModuleImplementation {
abstract readonly command: string;
abstract readonly describe: string | false;
- readonly longDescription?: string;
- readonly longDescriptionRelativePath?: string;
+ abstract readonly longDescriptionPath?: string;
protected readonly shouldReportAnalytics: boolean = true;
readonly scope: CommandScope = CommandScope.Both;
@@ -71,22 +68,23 @@ export abstract class CommandModule implements CommandModuleI
* `false` will result in a hidden command.
*/
public get fullDescribe(): FullDescribe | false {
- if (this.describe === false) {
- return false;
- }
-
- const description: FullDescribe = {
- describe: this.describe,
- };
-
- if (this.longDescription) {
- description.longDescription = this.longDescription.replace(/\r\n/g, '\n');
- description.longDescriptionRelativePath =
- this.longDescriptionRelativePath ??
- `@angular/cli/src/commands/${this.commandName}/long-description.md`;
- }
-
- return description;
+ return this.describe === false
+ ? false
+ : {
+ describe: this.describe,
+ ...(this.longDescriptionPath
+ ? {
+ longDescriptionRelativePath: relative(
+ join(__dirname, '../../../../'),
+ this.longDescriptionPath,
+ ).replace(/\\/g, posix.sep),
+ longDescription: readFileSync(this.longDescriptionPath, 'utf8').replace(
+ /\r\n/g,
+ '\n',
+ ),
+ }
+ : {}),
+ };
}
protected get commandName(): string {
diff --git a/packages/angular/cli/src/commands/add/cli.ts b/packages/angular/cli/src/commands/add/cli.ts
index fb352f70edfd..bc95f33ff8e7 100644
--- a/packages/angular/cli/src/commands/add/cli.ts
+++ b/packages/angular/cli/src/commands/add/cli.ts
@@ -29,9 +29,6 @@ import { NgAddSaveDependency, PackageManifest, PackageMetadata } from '../../pac
import { assertIsError } from '../../utilities/error';
import { isTTY } from '../../utilities/tty';
import { VERSION } from '../../utilities/version';
-// eslint-disable-next-line @typescript-eslint/ban-ts-comment
-// @ts-ignore strict-deps: Markdown files are asset dependencies bundled/loaded at runtime
-import longDescription from './long-description.md';
class CommandError extends Error {}
@@ -103,7 +100,7 @@ export default class AddCommandModule
{
command = 'add ';
describe = 'Adds support for an external library to your project.';
- override longDescription = longDescription;
+ longDescriptionPath = join(__dirname, 'long-description.md');
protected override allowPrivateSchematics = true;
private readonly schematicName = 'ng-add';
private rootRequire = createRequire(this.context.root + '/');
diff --git a/packages/angular/cli/src/commands/analytics/cli.ts b/packages/angular/cli/src/commands/analytics/cli.ts
index 9639cbab50e6..da56a2a00460 100644
--- a/packages/angular/cli/src/commands/analytics/cli.ts
+++ b/packages/angular/cli/src/commands/analytics/cli.ts
@@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.dev/license
*/
+import { join } from 'node:path';
import { Argv } from 'yargs';
import {
CommandModule,
@@ -17,9 +18,6 @@ import {
demandCommandFailureMessage,
} from '../../command-builder/utilities/command';
import { AnalyticsInfoCommandModule } from './info/cli';
-// eslint-disable-next-line @typescript-eslint/ban-ts-comment
-// @ts-ignore strict-deps: Markdown files are asset dependencies bundled/loaded at runtime
-import longDescription from './long-description.md';
import {
AnalyticsDisableModule,
AnalyticsEnableModule,
@@ -32,7 +30,7 @@ export default class AnalyticsCommandModule
{
command = 'analytics';
describe = 'Configures the gathering of Angular CLI usage metrics.';
- override longDescription = longDescription;
+ longDescriptionPath = join(__dirname, 'long-description.md');
builder(localYargs: Argv): Argv {
const subcommands = [
diff --git a/packages/angular/cli/src/commands/analytics/info/cli.ts b/packages/angular/cli/src/commands/analytics/info/cli.ts
index 93b34fd06716..e4434d35baee 100644
--- a/packages/angular/cli/src/commands/analytics/info/cli.ts
+++ b/packages/angular/cli/src/commands/analytics/info/cli.ts
@@ -20,6 +20,7 @@ export class AnalyticsInfoCommandModule
{
command = 'info';
describe = 'Prints analytics gathering and reporting configuration in the console.';
+ longDescriptionPath?: string;
builder(localYargs: Argv): Argv {
return localYargs.strict();
diff --git a/packages/angular/cli/src/commands/analytics/settings/cli.ts b/packages/angular/cli/src/commands/analytics/settings/cli.ts
index 24105de2b060..16f07b353d1a 100644
--- a/packages/angular/cli/src/commands/analytics/settings/cli.ts
+++ b/packages/angular/cli/src/commands/analytics/settings/cli.ts
@@ -26,6 +26,8 @@ abstract class AnalyticsSettingModule
extends CommandModule
implements CommandModuleImplementation
{
+ longDescriptionPath?: string;
+
builder(localYargs: Argv): Argv {
return localYargs
.option('global', {
diff --git a/packages/angular/cli/src/commands/build/cli.ts b/packages/angular/cli/src/commands/build/cli.ts
index 031e31b5140f..365420ca3734 100644
--- a/packages/angular/cli/src/commands/build/cli.ts
+++ b/packages/angular/cli/src/commands/build/cli.ts
@@ -6,12 +6,10 @@
* found in the LICENSE file at https://angular.dev/license
*/
+import { join } from 'node:path';
import { ArchitectCommandModule } from '../../command-builder/architect-command-module';
import { CommandModuleImplementation } from '../../command-builder/command-module';
import { RootCommands } from '../command-config';
-// eslint-disable-next-line @typescript-eslint/ban-ts-comment
-// @ts-ignore strict-deps: Markdown files are asset dependencies bundled/loaded at runtime
-import longDescription from './long-description.md';
export default class BuildCommandModule
extends ArchitectCommandModule
@@ -22,5 +20,5 @@ export default class BuildCommandModule
aliases = RootCommands['build'].aliases;
describe =
'Compiles an Angular application or library into an output directory named dist/ at the given output path.';
- override longDescription = longDescription;
+ longDescriptionPath = join(__dirname, 'long-description.md');
}
diff --git a/packages/angular/cli/src/commands/cache/clean/cli.ts b/packages/angular/cli/src/commands/cache/clean/cli.ts
index 492699271b54..a115b686b7e0 100644
--- a/packages/angular/cli/src/commands/cache/clean/cli.ts
+++ b/packages/angular/cli/src/commands/cache/clean/cli.ts
@@ -18,6 +18,7 @@ import { getCacheConfig } from '../utilities';
export class CacheCleanModule extends CommandModule implements CommandModuleImplementation {
command = 'clean';
describe = 'Deletes persistent disk cache from disk.';
+ longDescriptionPath: string | undefined;
override scope = CommandScope.In;
builder(localYargs: Argv): Argv {
diff --git a/packages/angular/cli/src/commands/cache/cli.ts b/packages/angular/cli/src/commands/cache/cli.ts
index 25140041ae0e..dad144b034b3 100644
--- a/packages/angular/cli/src/commands/cache/cli.ts
+++ b/packages/angular/cli/src/commands/cache/cli.ts
@@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.dev/license
*/
+import { join } from 'node:path';
import { Argv } from 'yargs';
import {
CommandModule,
@@ -19,9 +20,6 @@ import {
} from '../../command-builder/utilities/command';
import { CacheCleanModule } from './clean/cli';
import { CacheInfoCommandModule } from './info/cli';
-// eslint-disable-next-line @typescript-eslint/ban-ts-comment
-// @ts-ignore strict-deps: Markdown files are asset dependencies bundled/loaded at runtime
-import longDescription from './long-description.md';
import { CacheDisableModule, CacheEnableModule } from './settings/cli';
export default class CacheCommandModule
@@ -30,7 +28,7 @@ export default class CacheCommandModule
{
command = 'cache';
describe = 'Configure persistent disk cache and retrieve cache statistics.';
- override longDescription = longDescription;
+ longDescriptionPath = join(__dirname, 'long-description.md');
override scope = CommandScope.In;
builder(localYargs: Argv): Argv {
diff --git a/packages/angular/cli/src/commands/cache/info/cli.ts b/packages/angular/cli/src/commands/cache/info/cli.ts
index 6fd2df0414c7..f4278d52db74 100644
--- a/packages/angular/cli/src/commands/cache/info/cli.ts
+++ b/packages/angular/cli/src/commands/cache/info/cli.ts
@@ -21,6 +21,7 @@ import { getCacheConfig } from '../utilities';
export class CacheInfoCommandModule extends CommandModule implements CommandModuleImplementation {
command = 'info';
describe = 'Prints persistent disk cache configuration and statistics in the console.';
+ longDescriptionPath?: string | undefined;
override scope = CommandScope.In;
builder(localYargs: Argv): Argv {
diff --git a/packages/angular/cli/src/commands/cache/settings/cli.ts b/packages/angular/cli/src/commands/cache/settings/cli.ts
index 5de2d5496475..9a4f654f7ac7 100644
--- a/packages/angular/cli/src/commands/cache/settings/cli.ts
+++ b/packages/angular/cli/src/commands/cache/settings/cli.ts
@@ -18,6 +18,7 @@ export class CacheDisableModule extends CommandModule implements CommandModuleIm
command = 'disable';
aliases = 'off';
describe = 'Disables persistent disk cache for all projects in the workspace.';
+ longDescriptionPath: string | undefined;
override scope = CommandScope.In;
builder(localYargs: Argv): Argv {
@@ -33,6 +34,7 @@ export class CacheEnableModule extends CommandModule implements CommandModuleImp
command = 'enable';
aliases = 'on';
describe = 'Enables disk cache for all projects in the workspace.';
+ longDescriptionPath: string | undefined;
override scope = CommandScope.In;
builder(localYargs: Argv): Argv {
diff --git a/packages/angular/cli/src/commands/completion/cli.ts b/packages/angular/cli/src/commands/completion/cli.ts
index ab81ce5636d8..3fc9dccdc703 100644
--- a/packages/angular/cli/src/commands/completion/cli.ts
+++ b/packages/angular/cli/src/commands/completion/cli.ts
@@ -6,15 +6,13 @@
* found in the LICENSE file at https://angular.dev/license
*/
+import { join } from 'node:path';
import { Argv } from 'yargs';
import { CommandModule, CommandModuleImplementation } from '../../command-builder/command-module';
import { addCommandModuleToYargs } from '../../command-builder/utilities/command';
import { colors } from '../../utilities/color';
import { hasGlobalCliInstall, initializeAutocomplete } from '../../utilities/completion';
import { assertIsError } from '../../utilities/error';
-// eslint-disable-next-line @typescript-eslint/ban-ts-comment
-// @ts-ignore strict-deps: Markdown files are asset dependencies bundled/loaded at runtime
-import longDescription from './long-description.md';
export default class CompletionCommandModule
extends CommandModule
@@ -22,7 +20,7 @@ export default class CompletionCommandModule
{
command = 'completion';
describe = 'Set up Angular CLI autocompletion for your terminal.';
- override longDescription = longDescription;
+ longDescriptionPath = join(__dirname, 'long-description.md');
builder(localYargs: Argv): Argv {
addCommandModuleToYargs(CompletionScriptCommandModule, this.context);
@@ -66,6 +64,7 @@ Appended \`source <(ng completion script)\` to \`${rcFile}\`. Restart your termi
class CompletionScriptCommandModule extends CommandModule implements CommandModuleImplementation {
command = 'script';
describe = 'Generate a bash and zsh real-time type-ahead autocompletion script.';
+ longDescriptionPath = undefined;
builder(localYargs: Argv): Argv {
return localYargs;
diff --git a/packages/angular/cli/src/commands/config/cli.ts b/packages/angular/cli/src/commands/config/cli.ts
index f339fd9bb1ba..06b253b9a42d 100644
--- a/packages/angular/cli/src/commands/config/cli.ts
+++ b/packages/angular/cli/src/commands/config/cli.ts
@@ -8,6 +8,7 @@
import { JsonValue } from '@angular-devkit/core';
import { randomUUID } from 'node:crypto';
+import { join } from 'node:path';
import { Argv } from 'yargs';
import {
CommandModule,
@@ -17,9 +18,6 @@ import {
} from '../../command-builder/command-module';
import { getWorkspaceRaw, validateWorkspace } from '../../utilities/config';
import { JSONFile, parseJson } from '../../utilities/json-file';
-// eslint-disable-next-line @typescript-eslint/ban-ts-comment
-// @ts-ignore strict-deps: Markdown files are asset dependencies bundled/loaded at runtime
-import longDescription from './long-description.md';
interface ConfigCommandArgs {
'json-path'?: string;
@@ -34,7 +32,7 @@ export default class ConfigCommandModule
command = 'config [json-path] [value]';
describe =
'Retrieves or sets Angular configuration values in the angular.json file for the workspace.';
- override longDescription = longDescription;
+ longDescriptionPath = join(__dirname, 'long-description.md');
builder(localYargs: Argv): Argv {
return localYargs
diff --git a/packages/angular/cli/src/commands/deploy/cli.ts b/packages/angular/cli/src/commands/deploy/cli.ts
index 28317cff1ee1..947dc90af2d4 100644
--- a/packages/angular/cli/src/commands/deploy/cli.ts
+++ b/packages/angular/cli/src/commands/deploy/cli.ts
@@ -6,12 +6,10 @@
* found in the LICENSE file at https://angular.dev/license
*/
+import { join } from 'node:path';
import { MissingTargetChoice } from '../../command-builder/architect-base-command-module';
import { ArchitectCommandModule } from '../../command-builder/architect-command-module';
import { CommandModuleImplementation } from '../../command-builder/command-module';
-// eslint-disable-next-line @typescript-eslint/ban-ts-comment
-// @ts-ignore strict-deps: Markdown files are asset dependencies bundled/loaded at runtime
-import longDescription from './long-description.md';
export default class DeployCommandModule
extends ArchitectCommandModule
@@ -39,7 +37,7 @@ export default class DeployCommandModule
multiTarget = false;
command = 'deploy [project]';
- override longDescription = longDescription;
+ longDescriptionPath = join(__dirname, 'long-description.md');
describe =
'Invokes the deploy builder for a specified project or for the default project in the workspace.';
}
diff --git a/packages/angular/cli/src/commands/e2e/cli.ts b/packages/angular/cli/src/commands/e2e/cli.ts
index 9d63a8d96fb4..85d9aab173a0 100644
--- a/packages/angular/cli/src/commands/e2e/cli.ts
+++ b/packages/angular/cli/src/commands/e2e/cli.ts
@@ -42,4 +42,5 @@ export default class E2eCommandModule
command = 'e2e [project]';
aliases = RootCommands['e2e'].aliases;
describe = 'Builds and serves an Angular application, then runs end-to-end tests.';
+ longDescriptionPath?: string;
}
diff --git a/packages/angular/cli/src/commands/extract-i18n/cli.ts b/packages/angular/cli/src/commands/extract-i18n/cli.ts
index 09b3df343403..4f3dea2d8e7e 100644
--- a/packages/angular/cli/src/commands/extract-i18n/cli.ts
+++ b/packages/angular/cli/src/commands/extract-i18n/cli.ts
@@ -19,6 +19,7 @@ export default class ExtractI18nCommandModule
multiTarget = false;
command = 'extract-i18n [project]';
describe = 'Extracts i18n messages from source code.';
+ longDescriptionPath?: string | undefined;
override async findDefaultBuilderName(
project: workspaces.ProjectDefinition,
diff --git a/packages/angular/cli/src/commands/generate/cli.ts b/packages/angular/cli/src/commands/generate/cli.ts
index 8f605e03a18b..4be29c3eaea0 100644
--- a/packages/angular/cli/src/commands/generate/cli.ts
+++ b/packages/angular/cli/src/commands/generate/cli.ts
@@ -38,6 +38,7 @@ export default class GenerateCommandModule
command = 'generate';
aliases = RootCommands['generate'].aliases;
describe = 'Generates and/or modifies files based on a schematic.';
+ longDescriptionPath?: string | undefined;
override async builder(argv: Argv): Promise> {
let localYargs = (await super.builder(argv)).command({
diff --git a/packages/angular/cli/src/commands/lint/cli.ts b/packages/angular/cli/src/commands/lint/cli.ts
index 3fdd38fc169c..9510dd7afe53 100644
--- a/packages/angular/cli/src/commands/lint/cli.ts
+++ b/packages/angular/cli/src/commands/lint/cli.ts
@@ -6,12 +6,10 @@
* found in the LICENSE file at https://angular.dev/license
*/
+import { join } from 'node:path';
import { MissingTargetChoice } from '../../command-builder/architect-base-command-module';
import { ArchitectCommandModule } from '../../command-builder/architect-command-module';
import { CommandModuleImplementation } from '../../command-builder/command-module';
-// eslint-disable-next-line @typescript-eslint/ban-ts-comment
-// @ts-ignore strict-deps: Markdown files are asset dependencies bundled/loaded at runtime
-import longDescription from './long-description.md';
export default class LintCommandModule
extends ArchitectCommandModule
@@ -26,6 +24,6 @@ export default class LintCommandModule
multiTarget = true;
command = 'lint [project]';
- override longDescription = longDescription;
+ longDescriptionPath = join(__dirname, 'long-description.md');
describe = 'Runs linting tools on Angular application code in a given project folder.';
}
diff --git a/packages/angular/cli/src/commands/make-this-awesome/cli.ts b/packages/angular/cli/src/commands/make-this-awesome/cli.ts
index 8e36447f1f3a..6a17c5614b94 100644
--- a/packages/angular/cli/src/commands/make-this-awesome/cli.ts
+++ b/packages/angular/cli/src/commands/make-this-awesome/cli.ts
@@ -17,6 +17,7 @@ export default class AwesomeCommandModule
command = 'make-this-awesome';
describe = false as const;
deprecated = false;
+ longDescriptionPath?: string | undefined;
builder(localYargs: Argv): Argv {
return localYargs;
diff --git a/packages/angular/cli/src/commands/mcp/cli.ts b/packages/angular/cli/src/commands/mcp/cli.ts
index cbc94499a9ca..a379022e8f5e 100644
--- a/packages/angular/cli/src/commands/mcp/cli.ts
+++ b/packages/angular/cli/src/commands/mcp/cli.ts
@@ -35,6 +35,7 @@ For more information and documentation, visit: https://angular.dev/ai/mcp
export default class McpCommandModule extends CommandModule implements CommandModuleImplementation {
command = 'mcp';
describe = false as const;
+ longDescriptionPath = undefined;
builder(localYargs: Argv): Argv {
return localYargs
diff --git a/packages/angular/cli/src/commands/new/cli.ts b/packages/angular/cli/src/commands/new/cli.ts
index c47f3c63153b..6e6545e66421 100644
--- a/packages/angular/cli/src/commands/new/cli.ts
+++ b/packages/angular/cli/src/commands/new/cli.ts
@@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.dev/license
*/
+import { join } from 'node:path';
import { Argv } from 'yargs';
import {
CommandModuleImplementation,
@@ -20,9 +21,6 @@ import {
} from '../../command-builder/schematics-command-module';
import { VERSION } from '../../utilities/version';
import { RootCommands } from '../command-config';
-// eslint-disable-next-line @typescript-eslint/ban-ts-comment
-// @ts-ignore strict-deps: Markdown files are asset dependencies bundled/loaded at runtime
-import longDescription from './long-description.md';
interface NewCommandArgs extends SchematicsCommandArgs {
collection?: string;
@@ -39,7 +37,7 @@ export default class NewCommandModule
command = 'new [name]';
aliases = RootCommands['new'].aliases;
describe = 'Creates a new Angular workspace.';
- override longDescription = longDescription;
+ longDescriptionPath = join(__dirname, 'long-description.md');
override async builder(argv: Argv): Promise> {
const localYargs = (await super.builder(argv)).option('collection', {
diff --git a/packages/angular/cli/src/commands/run/cli.ts b/packages/angular/cli/src/commands/run/cli.ts
index 774f811aa2a5..aa12cd0158f7 100644
--- a/packages/angular/cli/src/commands/run/cli.ts
+++ b/packages/angular/cli/src/commands/run/cli.ts
@@ -7,6 +7,7 @@
*/
import { Target } from '@angular-devkit/architect';
+import { join } from 'node:path';
import { Argv } from 'yargs';
import { ArchitectBaseCommandModule } from '../../command-builder/architect-base-command-module';
import {
@@ -16,9 +17,6 @@ import {
Options,
OtherOptions,
} from '../../command-builder/command-module';
-// eslint-disable-next-line @typescript-eslint/ban-ts-comment
-// @ts-ignore strict-deps: Markdown files are asset dependencies bundled/loaded at runtime
-import longDescription from './long-description.md';
export interface RunCommandArgs {
target: string;
@@ -33,7 +31,7 @@ export default class RunCommandModule
command = 'run ';
describe =
'Runs an Architect target with an optional custom builder configuration defined in your project.';
- override longDescription = longDescription;
+ longDescriptionPath = join(__dirname, 'long-description.md');
async builder(argv: Argv): Promise> {
const { jsonHelp, getYargsCompletions, help } = this.context.args.options;
diff --git a/packages/angular/cli/src/commands/serve/cli.ts b/packages/angular/cli/src/commands/serve/cli.ts
index 6448c48b9484..3b38fa122acd 100644
--- a/packages/angular/cli/src/commands/serve/cli.ts
+++ b/packages/angular/cli/src/commands/serve/cli.ts
@@ -18,4 +18,5 @@ export default class ServeCommandModule
command = 'serve [project]';
aliases = RootCommands['serve'].aliases;
describe = 'Builds and serves your application, rebuilding on file changes.';
+ longDescriptionPath?: string | undefined;
}
diff --git a/packages/angular/cli/src/commands/test/cli.ts b/packages/angular/cli/src/commands/test/cli.ts
index f9dcaaf92c8c..600e9f41f517 100644
--- a/packages/angular/cli/src/commands/test/cli.ts
+++ b/packages/angular/cli/src/commands/test/cli.ts
@@ -6,12 +6,10 @@
* found in the LICENSE file at https://angular.dev/license
*/
+import { join } from 'node:path';
import { ArchitectCommandModule } from '../../command-builder/architect-command-module';
import { CommandModuleImplementation } from '../../command-builder/command-module';
import { RootCommands } from '../command-config';
-// eslint-disable-next-line @typescript-eslint/ban-ts-comment
-// @ts-ignore strict-deps: Markdown files are asset dependencies bundled/loaded at runtime
-import longDescription from './long-description.md';
export default class TestCommandModule
extends ArchitectCommandModule
@@ -21,5 +19,5 @@ export default class TestCommandModule
command = 'test [project]';
aliases = RootCommands['test'].aliases;
describe = 'Runs unit tests in a project.';
- override longDescription = longDescription;
+ longDescriptionPath = join(__dirname, 'long-description.md');
}
diff --git a/packages/angular/cli/src/commands/update/cli.ts b/packages/angular/cli/src/commands/update/cli.ts
index 6418bfc13ce4..005df998b501 100644
--- a/packages/angular/cli/src/commands/update/cli.ts
+++ b/packages/angular/cli/src/commands/update/cli.ts
@@ -24,9 +24,6 @@ import type { InstalledPackage, PackageManager, PackageManifest } from '../../pa
import { colors } from '../../utilities/color';
import { disableVersionCheck } from '../../utilities/environment-options';
import { assertIsError } from '../../utilities/error';
-// eslint-disable-next-line @typescript-eslint/ban-ts-comment
-// @ts-ignore strict-deps: Markdown files are asset dependencies bundled/loaded at runtime
-import longDescription from './long-description.md';
import {
UpdatePlan,
applyUpdatePlan,
@@ -66,7 +63,7 @@ export default class UpdateCommandModule extends CommandModule {
return localYargs
diff --git a/packages/angular/cli/src/commands/version/cli.ts b/packages/angular/cli/src/commands/version/cli.ts
index 1a79b8b76eef..205f0bc7e55e 100644
--- a/packages/angular/cli/src/commands/version/cli.ts
+++ b/packages/angular/cli/src/commands/version/cli.ts
@@ -37,6 +37,7 @@ export default class VersionCommandModule
command = 'version';
aliases = RootCommands['version'].aliases;
describe = 'Outputs Angular CLI version.';
+ longDescriptionPath?: string | undefined;
/**
* Builds the command-line options for the `ng version` command.
diff --git a/packages/angular/cli/src/typings.d.ts b/packages/angular/cli/src/typings.d.ts
deleted file mode 100644
index 12e7de03c204..000000000000
--- a/packages/angular/cli/src/typings.d.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-/**
- * @license
- * Copyright Google LLC All Rights Reserved.
- *
- * Use of this source code is governed by an MIT-style license that can be
- * found in the LICENSE file at https://angular.dev/license
- */
-
-declare module '*/long-description.md' {
- const content: string;
- export default content;
-}
diff --git a/packages/angular/cli/src/utilities/markdown-loader.ts b/packages/angular/cli/src/utilities/markdown-loader.ts
deleted file mode 100644
index 3a70a5eaae1c..000000000000
--- a/packages/angular/cli/src/utilities/markdown-loader.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-/**
- * @license
- * Copyright Google LLC All Rights Reserved.
- *
- * Use of this source code is governed by an MIT-style license that can be
- * found in the LICENSE file at https://angular.dev/license
- */
-
-import { readFileSync } from 'node:fs';
-
-const LONG_DESCRIPTION_REGEXP = /[/\\]long-description\.md$/;
-
-function isCommandLongDescription(filePath: string | undefined): boolean {
- return !!filePath && LONG_DESCRIPTION_REGEXP.test(filePath);
-}
-
-// Register markdown extension hook for CommonJS execution
-if (typeof require !== 'undefined' && require.extensions) {
- const originalMdExtension = require.extensions['.md'];
- require.extensions['.md'] = (module, filename) => {
- if (isCommandLongDescription(filename)) {
- module.exports = readFileSync(filename, 'utf8');
-
- return;
- }
-
- if (originalMdExtension) {
- originalMdExtension(module, filename);
- } else {
- const err = new Error(`Cannot find module '${filename}'`);
- (err as NodeJS.ErrnoException).code = 'MODULE_NOT_FOUND';
- throw err;
- }
- };
-}
diff --git a/packages/angular/ssr/package.json b/packages/angular/ssr/package.json
index 82edb52e27d8..b48e12387746 100644
--- a/packages/angular/ssr/package.json
+++ b/packages/angular/ssr/package.json
@@ -38,12 +38,12 @@
},
"devDependencies": {
"@angular-devkit/schematics": "workspace:*",
- "@angular/common": "22.2.0-next.5",
- "@angular/compiler": "22.2.0-next.5",
- "@angular/core": "22.2.0-next.5",
- "@angular/platform-browser": "22.2.0-next.5",
- "@angular/platform-server": "22.2.0-next.5",
- "@angular/router": "22.2.0-next.5",
+ "@angular/common": "22.2.0-rc.0",
+ "@angular/compiler": "22.2.0-rc.0",
+ "@angular/core": "22.2.0-rc.0",
+ "@angular/platform-browser": "22.2.0-rc.0",
+ "@angular/platform-server": "22.2.0-rc.0",
+ "@angular/router": "22.2.0-rc.0",
"@schematics/angular": "workspace:*"
},
"sideEffects": false,
diff --git a/packages/angular_devkit/build_angular/package.json b/packages/angular_devkit/build_angular/package.json
index d7b95edd650a..4f699d7fbac5 100644
--- a/packages/angular_devkit/build_angular/package.json
+++ b/packages/angular_devkit/build_angular/package.json
@@ -66,7 +66,7 @@
"devDependencies": {
"@angular/ssr": "workspace:*",
"browser-sync": "3.0.4",
- "ng-packagr": "22.2.0-next.5",
+ "ng-packagr": "22.2.0-rc.0",
"sass": "1.104.1",
"undici": "8.10.2"
},
diff --git a/packages/angular_devkit/core/src/json/schema/registry.ts b/packages/angular_devkit/core/src/json/schema/registry.ts
index 77aeab6646a1..928771337bb5 100644
--- a/packages/angular_devkit/core/src/json/schema/registry.ts
+++ b/packages/angular_devkit/core/src/json/schema/registry.ts
@@ -61,19 +61,24 @@ export class SchemaValidationException extends BaseException {
}
const messages = errors.map((err) => {
+ if (err.keyword === 'additionalProperties') {
+ const unknown = err.params?.additionalProperty;
+ // `parentSchema` is the schema that rejected the property, which ajv only attaches when
+ // the validator was created with `verbose: true`. A schema that declares no `properties`
+ // of its own, such as one using only `patternProperties`, has no options to offer.
+ const known = Object.keys(err.parentSchema?.properties ?? {});
+
+ return (
+ `Unknown option "${unknown}"${err.instancePath ? ` at "${err.instancePath}"` : ''}.` +
+ (known.length ? ` Valid options are: ${known.join(', ')}.` : '')
+ );
+ }
+
let message = `Data path ${JSON.stringify(err.instancePath)} ${err.message}`;
- if (err.params) {
- switch (err.keyword) {
- case 'additionalProperties':
- message += `(${err.params.additionalProperty})`;
- break;
-
- case 'enum':
- message += `. Allowed values are: ${(err.params.allowedValues as string[] | undefined)
- ?.map((v) => `"${v}"`)
- .join(', ')}`;
- break;
- }
+ if (err.keyword === 'enum' && err.params) {
+ message += `. Allowed values are: ${(err.params.allowedValues as string[] | undefined)
+ ?.map((v) => `"${v}"`)
+ .join(', ')}`;
}
return message + '.';
@@ -106,6 +111,8 @@ export class CoreSchemaRegistry implements SchemaRegistry {
strict: false,
loadSchema: (uri: string) => this._fetch(uri),
passContext: true,
+ // Needed to list the valid options of the object an unknown option was found in.
+ verbose: true,
});
ajvAddFormats(this._ajv);
diff --git a/packages/angular_devkit/core/src/json/schema/registry_spec.ts b/packages/angular_devkit/core/src/json/schema/registry_spec.ts
index 34e404a63b29..32c601154d16 100644
--- a/packages/angular_devkit/core/src/json/schema/registry_spec.ts
+++ b/packages/angular_devkit/core/src/json/schema/registry_spec.ts
@@ -7,8 +7,10 @@
*/
/* eslint-disable @typescript-eslint/no-explicit-any */
+import { JsonValue } from '../utils';
import { SchemaFormat } from './interface';
import { CoreSchemaRegistry, SchemaValidationException } from './registry';
+import { JsonSchema } from './schema';
import { addUndefinedDefaults } from './transforms';
describe('CoreSchemaRegistry', () => {
@@ -340,4 +342,93 @@ describe('CoreSchemaRegistry', () => {
expect(deprecatedMessages[1]).toBe('Option "bar" is deprecated.');
expect(result.success).toBe(true, result.errors);
});
+
+ describe('error messages', () => {
+ async function messagesFor(schema: JsonSchema, data: JsonValue): Promise {
+ const registry = new CoreSchemaRegistry();
+ const validator = await registry.compile(schema);
+ const result = await validator(data);
+ expect(result.success).toBe(false);
+
+ return SchemaValidationException.createMessages(result.errors);
+ }
+
+ it('names an unknown option and the options that are valid there', async () => {
+ const messages = await messagesFor(
+ {
+ properties: { version: { type: 'number' }, projects: { type: 'object' } },
+ additionalProperties: false,
+ },
+ { allowedCommonJsDependencies: [] },
+ );
+
+ expect(messages).toEqual([
+ 'Unknown option "allowedCommonJsDependencies". Valid options are: version, projects.',
+ ]);
+ });
+
+ it('points at the object an unknown option was found in', async () => {
+ const messages = await messagesFor(
+ {
+ properties: {
+ cli: {
+ type: 'object',
+ properties: { cache: { type: 'object' }, packageManager: { type: 'string' } },
+ additionalProperties: false,
+ },
+ },
+ },
+ { cli: { completion: true } },
+ );
+
+ expect(messages).toEqual([
+ 'Unknown option "completion" at "/cli". Valid options are: cache, packageManager.',
+ ]);
+ });
+
+ it('looks through a $ref for the valid options', async () => {
+ const messages = await messagesFor(
+ {
+ $ref: '#/definitions/global',
+ definitions: {
+ global: {
+ type: 'object',
+ properties: { cli: { type: 'object' }, schematics: { type: 'object' } },
+ additionalProperties: false,
+ },
+ },
+ },
+ { version: 1 },
+ );
+
+ expect(messages).toEqual(['Unknown option "version". Valid options are: cli, schematics.']);
+ });
+
+ it('omits the valid options when the schema does not list any', async () => {
+ const messages = await messagesFor(
+ {
+ $ref: '#/definitions/cli',
+ definitions: {
+ cli: {
+ type: 'object',
+ patternProperties: { '^x-': { type: 'string' } },
+ additionalProperties: false,
+ },
+ },
+ },
+ { completion: true },
+ );
+
+ expect(messages).toEqual(['Unknown option "completion".']);
+ });
+
+ it('leaves an error that is not about an unknown option alone', async () => {
+ const messages = await messagesFor(
+ { properties: { outputPath: { type: 'string' } } },
+ { outputPath: 42 },
+ );
+
+ expect(messages).toEqual(['Data path "/outputPath" must be string.']);
+ });
+ });
});
diff --git a/packages/ngtools/webpack/package.json b/packages/ngtools/webpack/package.json
index aaca4ed7005e..07e9660520f8 100644
--- a/packages/ngtools/webpack/package.json
+++ b/packages/ngtools/webpack/package.json
@@ -17,8 +17,8 @@
},
"devDependencies": {
"@angular-devkit/core": "workspace:0.0.0-PLACEHOLDER",
- "@angular/compiler": "22.2.0-next.5",
- "@angular/compiler-cli": "22.2.0-next.5",
+ "@angular/compiler": "22.2.0-rc.0",
+ "@angular/compiler-cli": "22.2.0-rc.0",
"typescript": "6.0.3",
"webpack": "5.111.0"
}
diff --git a/packages/schematics/angular/utility/latest-versions/package.json b/packages/schematics/angular/utility/latest-versions/package.json
index 111c8296c202..75481eb2b23d 100644
--- a/packages/schematics/angular/utility/latest-versions/package.json
+++ b/packages/schematics/angular/utility/latest-versions/package.json
@@ -5,7 +5,7 @@
"dependencies": {
"@types/express": "^5.0.1",
"@types/jasmine": "~6.0.0",
- "@types/node": "^20.17.19",
+ "@types/node": "^22.12.0",
"browser-sync": "^3.0.0",
"express": "^5.1.0",
"istanbul-lib-instrument": "^6.0.3",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e4cb97b4c448..c0c4d347a5ef 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -172,8 +172,8 @@ importers:
.:
dependencies:
'@angular/compiler-cli':
- specifier: 22.2.0-next.5
- version: 22.2.0-next.5(@angular/compiler@22.2.0-next.5)(typescript@6.0.3)
+ specifier: 22.2.0-rc.0
+ version: 22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(typescript@6.0.3)
typescript:
specifier: 6.0.3
version: 6.0.3
@@ -184,44 +184,44 @@ importers:
built: true
devDependencies:
'@angular/animations':
- specifier: 22.2.0-next.5
- version: 22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))
+ specifier: 22.2.0-rc.0
+ version: 22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))
'@angular/cdk':
- specifier: 22.2.0-next.4
- version: 22.2.0-next.4(164cec07f845c9f2a6f509bb1888cfce)
+ specifier: 22.2.0-rc.0
+ version: 22.2.0-rc.0(32444abcc2be6e3bf2b6c059aa24d737)
'@angular/common':
- specifier: 22.2.0-next.5
- version: 22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2)
+ specifier: 22.2.0-rc.0
+ version: 22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2)
'@angular/compiler':
- specifier: 22.2.0-next.5
- version: 22.2.0-next.5
+ specifier: 22.2.0-rc.0
+ version: 22.2.0-rc.0
'@angular/core':
- specifier: 22.2.0-next.5
- version: 22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3)
+ specifier: 22.2.0-rc.0
+ version: 22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3)
'@angular/forms':
- specifier: 22.2.0-next.5
- version: 22.2.0-next.5(@angular/common@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.0-next.5(@angular/animations@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3)))(@angular/common@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2)
+ specifier: 22.2.0-rc.0
+ version: 22.2.0-rc.0(@angular/common@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.0-rc.0(@angular/animations@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3)))(@angular/common@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2)
'@angular/localize':
- specifier: 22.2.0-next.5
- version: 22.2.0-next.5(@angular/compiler-cli@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(typescript@6.0.3))(@angular/compiler@22.2.0-next.5)
+ specifier: 22.2.0-rc.0
+ version: 22.2.0-rc.0(@angular/compiler-cli@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(typescript@6.0.3))(@angular/compiler@22.2.0-rc.0)
'@angular/material':
- specifier: 22.2.0-next.4
- version: 22.2.0-next.4(95bc9c8a06206acb56b97c62cbcd354e)
+ specifier: 22.2.0-rc.0
+ version: 22.2.0-rc.0(25ddf4063a130bbf19862bd16a6ab5d4)
'@angular/ng-dev':
- specifier: https://github.com/angular/dev-infra-private-ng-dev-builds.git#c3d7ed184d71d390d3eacff85f4b2b3f16b82660
- version: https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/c3d7ed184d71d390d3eacff85f4b2b3f16b82660
+ specifier: https://github.com/angular/dev-infra-private-ng-dev-builds.git#f5de34cabca7e005dc8102d3abb026d01a7a881a
+ version: https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/f5de34cabca7e005dc8102d3abb026d01a7a881a
'@angular/platform-browser':
- specifier: 22.2.0-next.5
- version: 22.2.0-next.5(@angular/animations@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3)))(@angular/common@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))
+ specifier: 22.2.0-rc.0
+ version: 22.2.0-rc.0(@angular/animations@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3)))(@angular/common@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))
'@angular/platform-server':
- specifier: 22.2.0-next.5
- version: 22.2.0-next.5(@angular/common@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/compiler@22.2.0-next.5)(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.0-next.5(@angular/animations@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3)))(@angular/common@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2)
+ specifier: 22.2.0-rc.0
+ version: 22.2.0-rc.0(@angular/common@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/compiler@22.2.0-rc.0)(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.0-rc.0(@angular/animations@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3)))(@angular/common@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2)
'@angular/router':
- specifier: 22.2.0-next.5
- version: 22.2.0-next.5(@angular/common@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.0-next.5(@angular/animations@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3)))(@angular/common@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2)
+ specifier: 22.2.0-rc.0
+ version: 22.2.0-rc.0(@angular/common@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.0-rc.0(@angular/animations@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3)))(@angular/common@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2)
'@angular/service-worker':
- specifier: 22.2.0-next.5
- version: 22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2)
+ specifier: 22.2.0-rc.0
+ version: 22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2)
'@babel/core':
specifier: 8.0.5
version: 8.0.5
@@ -463,8 +463,8 @@ importers:
specifier: 30.0.1
version: 30.0.1
ng-packagr:
- specifier: 22.2.0-next.5
- version: 22.2.0-next.5(@angular/compiler-cli@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(typescript@6.0.3))(supports-color@11.0.0)(tslib@2.8.1)(typescript@6.0.3)
+ specifier: 22.2.0-rc.0
+ version: 22.2.0-rc.0(@angular/compiler-cli@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(typescript@6.0.3))(supports-color@11.0.0)(tslib@2.8.1)(typescript@6.0.3)
rxjs:
specifier: 7.8.2
version: 7.8.2
@@ -578,8 +578,8 @@ importers:
specifier: 4.9.1
version: 4.9.1(supports-color@11.0.0)
ng-packagr:
- specifier: 22.2.0-next.5
- version: 22.2.0-next.5(@angular/compiler-cli@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(typescript@6.0.3))(supports-color@11.0.0)(tslib@2.8.1)(typescript@6.0.3)
+ specifier: 22.2.0-rc.0
+ version: 22.2.0-rc.0(@angular/compiler-cli@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(typescript@6.0.3))(supports-color@11.0.0)(tslib@2.8.1)(typescript@6.0.3)
postcss:
specifier: 8.5.28
version: 8.5.28
@@ -667,23 +667,23 @@ importers:
specifier: workspace:*
version: link:../../angular_devkit/schematics
'@angular/common':
- specifier: 22.2.0-next.5
- version: 22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2)
+ specifier: 22.2.0-rc.0
+ version: 22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2)
'@angular/compiler':
- specifier: 22.2.0-next.5
- version: 22.2.0-next.5
+ specifier: 22.2.0-rc.0
+ version: 22.2.0-rc.0
'@angular/core':
- specifier: 22.2.0-next.5
- version: 22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3)
+ specifier: 22.2.0-rc.0
+ version: 22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3)
'@angular/platform-browser':
- specifier: 22.2.0-next.5
- version: 22.2.0-next.5(@angular/animations@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3)))(@angular/common@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))
+ specifier: 22.2.0-rc.0
+ version: 22.2.0-rc.0(@angular/animations@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3)))(@angular/common@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))
'@angular/platform-server':
- specifier: 22.2.0-next.5
- version: 22.2.0-next.5(@angular/common@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/compiler@22.2.0-next.5)(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.0-next.5(@angular/animations@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3)))(@angular/common@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2)
+ specifier: 22.2.0-rc.0
+ version: 22.2.0-rc.0(@angular/common@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/compiler@22.2.0-rc.0)(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.0-rc.0(@angular/animations@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3)))(@angular/common@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2)
'@angular/router':
- specifier: 22.2.0-next.5
- version: 22.2.0-next.5(@angular/common@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.0-next.5(@angular/animations@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3)))(@angular/common@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2)
+ specifier: 22.2.0-rc.0
+ version: 22.2.0-rc.0(@angular/common@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.0-rc.0(@angular/animations@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3)))(@angular/common@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2)
'@schematics/angular':
specifier: workspace:*
version: link:../../schematics/angular
@@ -866,8 +866,8 @@ importers:
specifier: 3.0.4
version: 3.0.4(bufferutil@4.1.0)(debug@4.4.3(supports-color@11.0.0))(supports-color@11.0.0)(utf-8-validate@6.0.6)
ng-packagr:
- specifier: 22.2.0-next.5
- version: 22.2.0-next.5(@angular/compiler-cli@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(typescript@6.0.3))(supports-color@11.0.0)(tslib@2.8.1)(typescript@6.0.3)
+ specifier: 22.2.0-rc.0
+ version: 22.2.0-rc.0(@angular/compiler-cli@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(typescript@6.0.3))(supports-color@11.0.0)(tslib@2.8.1)(typescript@6.0.3)
sass:
specifier: 1.104.1
version: 1.104.1
@@ -962,11 +962,11 @@ importers:
specifier: workspace:0.0.0-PLACEHOLDER
version: link:../../angular_devkit/core
'@angular/compiler':
- specifier: 22.2.0-next.5
- version: 22.2.0-next.5
+ specifier: 22.2.0-rc.0
+ version: 22.2.0-rc.0
'@angular/compiler-cli':
- specifier: 22.2.0-next.5
- version: 22.2.0-next.5(@angular/compiler@22.2.0-next.5)(typescript@6.0.3)
+ specifier: 22.2.0-rc.0
+ version: 22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(typescript@6.0.3)
typescript:
specifier: 6.0.3
version: 6.0.3
@@ -1026,15 +1026,14 @@ packages:
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
engines: {node: '>=6.0.0'}
- '@angular/animations@22.2.0-next.5':
- resolution: {integrity: sha512-Q2ImjJNM14L+jKh5EvxDtooRf8tshZ+rlomtf24AI6Cxr7FelPXwPOZbYmHkPu0y5hqhey5+WNG5RQLRvKjiLQ==}
+ '@angular/animations@22.2.0-rc.0':
+ resolution: {integrity: sha512-MnkNstor6AH92M9ps7RZfcCyUo24SqBZU71soDgYC3aXFJhSYOtJFPKE1/KgrpVt416lTzbhHe/68kAZ3cH9MQ==}
engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0}
- deprecated: '@angular/animations is deprecated. Use `animate.enter` and `animate.leave` instead. For more information see: https://v22.angular.dev/guide/animations.'
peerDependencies:
- '@angular/core': 22.2.0-next.5
+ '@angular/core': 22.2.0-rc.0
- '@angular/cdk@22.2.0-next.4':
- resolution: {integrity: sha512-ZqORCqi7eGIXgiObGA1Lmf7+l+7VP5gp7/ge3cNHUOcJP6HRJlt4ISjUFT+j7t9FU1lkf6nNBSwEcIG2EI+EGw==}
+ '@angular/cdk@22.2.0-rc.0':
+ resolution: {integrity: sha512-fG3/uCItdRj+PSetdHOIcd72VUekKhdj/uazHuhjjE1mz7gJJx0WBzDk1CcGUYtfvq0WoYlv9Kq6rhnP2gBAdw==}
peerDependencies:
'@angular/common': ^22.0.0-0 || ^22.1.0-0 || ^22.2.0-0 || ^22.3.0-0 || ^23.0.0-0
'@angular/core': ^22.0.0-0 || ^22.1.0-0 || ^22.2.0-0 || ^22.3.0-0 || ^23.0.0-0
@@ -1042,33 +1041,33 @@ packages:
'@angular/platform-browser': ^22.0.0-0 || ^22.1.0-0 || ^22.2.0-0 || ^22.3.0-0 || ^23.0.0-0
rxjs: ^6.5.3 || ^7.4.0
- '@angular/common@22.2.0-next.5':
- resolution: {integrity: sha512-D11bjuSXSuIB+EQ04Ep1NyhbvuJHbZqKBkNzXDK0Mfkkbjfl9rN4YOs7vGNcXOm/pFXWSR4DR5IqQWeiiSlUBQ==}
+ '@angular/common@22.2.0-rc.0':
+ resolution: {integrity: sha512-j80c+FC/yZ2jz0d6w/kPwvEdMhaIol23gt/IcZxOigvElUywpNfm+g36XjM34/UNy6HDhew5ck7HE9DWYVXwsw==}
engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0}
peerDependencies:
- '@angular/core': 22.2.0-next.5
+ '@angular/core': 22.2.0-rc.0
rxjs: ^6.5.3 || ^7.4.0
- '@angular/compiler-cli@22.2.0-next.5':
- resolution: {integrity: sha512-zuAYA4ZAYf1HY82nJRuo7Ft00YwAiRo/aZGuRSU4A+RlHROq57X1W8NyINdMpMPEyL2O14WgRG2/MlGDKnF49Q==}
+ '@angular/compiler-cli@22.2.0-rc.0':
+ resolution: {integrity: sha512-51yfbHuAd09YnH5tTLb9jIxzkqFTB8SAj6Gv2/LqR+xDq7AGaZgFiehQOa2pa9kDJ3wNHYbTROF7dHP+AXXj2w==}
engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0}
hasBin: true
peerDependencies:
- '@angular/compiler': 22.2.0-next.5
+ '@angular/compiler': 22.2.0-rc.0
typescript: '>=6.0 <6.1'
peerDependenciesMeta:
typescript:
optional: true
- '@angular/compiler@22.2.0-next.5':
- resolution: {integrity: sha512-EZ3O1e0aQDsYPX8jffsX7mNoyqWHOMqpzP4ULjt45JZkpAT/H/Ss7p8MFKnox60AFbvmEENm/srcnDJ5s9sTLA==}
+ '@angular/compiler@22.2.0-rc.0':
+ resolution: {integrity: sha512-vWavQPwkNQhie4/KKGycoQrGGPNcjKbn8VkmMiIcfPVCMtvNvhs8ssPgKENYDXaFRyg+UtJ1Y/2HRdYtIQIw8g==}
engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0}
- '@angular/core@22.2.0-next.5':
- resolution: {integrity: sha512-Gh+JycsZiZhVZEODpCBEc20XXl6aIN/w+bB7A9a7/d0bMRdX3IBtYNxCOMor9L7bJVfwD+N3NTWpb+wSv81wMQ==}
+ '@angular/core@22.2.0-rc.0':
+ resolution: {integrity: sha512-HbeWk0EatMmOf5VF71W35WCVZ9nwjHkeXvdhee+1uZfr2cmpnS3OXuFrIgTP2kSrILc6jEh7xuHW/IT1itjHuA==}
engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0}
peerDependencies:
- '@angular/compiler': 22.2.0-next.5
+ '@angular/compiler': 22.2.0-rc.0
rxjs: ^6.5.3 || ^7.4.0
zone.js: ~0.15.0 || ~0.16.0
peerDependenciesMeta:
@@ -1077,74 +1076,74 @@ packages:
zone.js:
optional: true
- '@angular/forms@22.2.0-next.5':
- resolution: {integrity: sha512-eyECVvZ/pJGKzXTWf3SC1d+hSIVWQvJreiCo0kX2e9pDzuGoK5Br8gifP38tgwym2FuR/5THJRn2K998pUZ4Xg==}
+ '@angular/forms@22.2.0-rc.0':
+ resolution: {integrity: sha512-mg+NratvkCH9ihGBplqZBKS2Q8+eLDdZSyl10FWmIzhaTmJEk9GLoZ70n4kEU0rX65hy2oF0M2aTBpr2CRxZ4w==}
engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0}
peerDependencies:
- '@angular/common': 22.2.0-next.5
- '@angular/core': 22.2.0-next.5
- '@angular/platform-browser': 22.2.0-next.5
+ '@angular/common': 22.2.0-rc.0
+ '@angular/core': 22.2.0-rc.0
+ '@angular/platform-browser': 22.2.0-rc.0
rxjs: ^6.5.3 || ^7.4.0
- '@angular/localize@22.2.0-next.5':
- resolution: {integrity: sha512-kquo2tv5g3SF33acN6SQpiOwr0TpP45/iKRLkX/xcWY6XZ7S9bxU4W6CiJ4fZfelfTDKBr6ZhxA8QNnw7Y1Z8w==}
+ '@angular/localize@22.2.0-rc.0':
+ resolution: {integrity: sha512-c0WgdyX0GYrx4hBF9KEPaOXFIcz+j9dIgLDG2hcsxLdjE9r+trmTBFwhR/4/Q7S5GThbnty4/rjVJOrRXpdi7Q==}
engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0}
hasBin: true
peerDependencies:
- '@angular/compiler': 22.2.0-next.5
- '@angular/compiler-cli': 22.2.0-next.5
+ '@angular/compiler': 22.2.0-rc.0
+ '@angular/compiler-cli': 22.2.0-rc.0
- '@angular/material@22.2.0-next.4':
- resolution: {integrity: sha512-BTGnryNFfLvN8jI02msMNbHdNVcOaFL0MMH8S+jpQc8ooULx8bRwQZSWk8LKusayVyZH4JbT3YvV7PW/qw0ndA==}
+ '@angular/material@22.2.0-rc.0':
+ resolution: {integrity: sha512-3IaSPe7LnbhJHzo95xAfY0vB7nA5tDxx+ojn8MSZTa/m6Cw8aCRMZiSiJ783Qw7x1doWz37o3Z7B54Wp8n/2Jw==}
peerDependencies:
- '@angular/cdk': 22.2.0-next.4
+ '@angular/cdk': 22.2.0-rc.0
'@angular/common': ^22.0.0-0 || ^22.1.0-0 || ^22.2.0-0 || ^22.3.0-0 || ^23.0.0-0
'@angular/core': ^22.0.0-0 || ^22.1.0-0 || ^22.2.0-0 || ^22.3.0-0 || ^23.0.0-0
'@angular/forms': ^22.0.0-0 || ^22.1.0-0 || ^22.2.0-0 || ^22.3.0-0 || ^23.0.0-0
'@angular/platform-browser': ^22.0.0-0 || ^22.1.0-0 || ^22.2.0-0 || ^22.3.0-0 || ^23.0.0-0
rxjs: ^6.5.3 || ^7.4.0
- '@angular/ng-dev@https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/c3d7ed184d71d390d3eacff85f4b2b3f16b82660':
- resolution: {gitHosted: true, integrity: sha512-DBHbT5IV4wnEUd5/+wh1cTxmwKNsxGahmN4DJ92T1aY5DcvCxhsoedLtHNX9Wi81hNEc37phIr+qaDsZWQVdQw==, tarball: https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/c3d7ed184d71d390d3eacff85f4b2b3f16b82660}
- version: 0.0.0-f5c817076b8e4da7b6e91783dbcd553c5003a296
+ '@angular/ng-dev@https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/f5de34cabca7e005dc8102d3abb026d01a7a881a':
+ resolution: {gitHosted: true, integrity: sha512-JEXOlRtQ50aV/E2C+PeGKYCmGHWOi6G70kiS0tTjKGAn+qrYTRQQzFSm4YB2VVYnL16s3jg8oAIwZ4pl/t9WgQ==, tarball: https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/f5de34cabca7e005dc8102d3abb026d01a7a881a}
+ version: 0.0.0-71e60c68d9d483b0d763b8e6ed4dd9deb8da3b1a
hasBin: true
- '@angular/platform-browser@22.2.0-next.5':
- resolution: {integrity: sha512-eSTpC4/SqkQ6QDTblFNznT7jRwX9o4X1cO7Hf+F0p07hofbbi8ptDXl0iZs6643C5eSaoyp1Bncd8wJzwoFmPw==}
+ '@angular/platform-browser@22.2.0-rc.0':
+ resolution: {integrity: sha512-poN1dCkFKM1t288v/5M2w+OUzragcUuEt3GQfcQQ87lIWCp5atCpClpXxbFJj0VUeYXPKvb9rUwQtFtTMzYohA==}
engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0}
peerDependencies:
- '@angular/animations': 22.2.0-next.5
- '@angular/common': 22.2.0-next.5
- '@angular/core': 22.2.0-next.5
+ '@angular/animations': 22.2.0-rc.0
+ '@angular/common': 22.2.0-rc.0
+ '@angular/core': 22.2.0-rc.0
peerDependenciesMeta:
'@angular/animations':
optional: true
- '@angular/platform-server@22.2.0-next.5':
- resolution: {integrity: sha512-MrXjF0GI2CSnNBbAhOJXjsXraL5H3P32VaG9Eh6DJNNZpR6pzM9HWOjbXHQ1elrYNBN2rV67HRSiDkRYu46AVw==}
+ '@angular/platform-server@22.2.0-rc.0':
+ resolution: {integrity: sha512-bohsXRaIV/d/RqFqbsdpmNpmcmurSrV/a1I7lo7IEzJJXAVchHBD/JIjJYFOk+NgqLDYZciq6Z8fF8idLfv1YQ==}
engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0}
peerDependencies:
- '@angular/common': 22.2.0-next.5
- '@angular/compiler': 22.2.0-next.5
- '@angular/core': 22.2.0-next.5
- '@angular/platform-browser': 22.2.0-next.5
+ '@angular/common': 22.2.0-rc.0
+ '@angular/compiler': 22.2.0-rc.0
+ '@angular/core': 22.2.0-rc.0
+ '@angular/platform-browser': 22.2.0-rc.0
rxjs: ^6.5.3 || ^7.4.0
- '@angular/router@22.2.0-next.5':
- resolution: {integrity: sha512-40viSK016CMgwVjbW8oLLv0zZSWCNVCFG1bqxOnqujPbP/lW3vlu4ul0Ine9tUlKnkfNZKV+NTCQtHlY1li+5Q==}
+ '@angular/router@22.2.0-rc.0':
+ resolution: {integrity: sha512-bIOOdp5T3KM4vB2xL4L6fpXYQ3vJmkPRB+k2tXSb4xW1HLsJDEyGN9GNj/5prnLbaWIGqcfhGNwnTW/mCVm42A==}
engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0}
peerDependencies:
- '@angular/common': 22.2.0-next.5
- '@angular/core': 22.2.0-next.5
- '@angular/platform-browser': 22.2.0-next.5
+ '@angular/common': 22.2.0-rc.0
+ '@angular/core': 22.2.0-rc.0
+ '@angular/platform-browser': 22.2.0-rc.0
rxjs: ^6.5.3 || ^7.4.0
- '@angular/service-worker@22.2.0-next.5':
- resolution: {integrity: sha512-z8oExlAiu3oKkKQFZ0AXnmk1K9jw/+lLEv9IRhuQM1abPNKfFo9AHE298dPZLoE3wqumg85No2u8AJJX9w/1sw==}
+ '@angular/service-worker@22.2.0-rc.0':
+ resolution: {integrity: sha512-y03a1Ppfyi765n8QlRgVqeL5kF8k5mFOVxvK+ZlxONave1mN2ChG37Yu35gzJ22wiprBDUgxGm3UdLvscU3YkA==}
engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0}
hasBin: true
peerDependencies:
- '@angular/core': 22.2.0-next.5
+ '@angular/core': 22.2.0-rc.0
rxjs: ^6.5.3 || ^7.4.0
'@asamuzakjp/css-color@6.0.7':
@@ -1187,10 +1186,6 @@ packages:
resolution: {integrity: sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==}
engines: {node: '>=6.9.0'}
- '@babel/generator@8.0.0':
- resolution: {integrity: sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g==}
- engines: {node: ^22.18.0 || >=24.11.0}
-
'@babel/generator@8.0.5':
resolution: {integrity: sha512-f/TuhuMAxJqhwxEGNsJrswuG9VHmh0oNFoQoo6TbpgtFAz9wYZXcTAcWZMHfp7ljesr0RG04bp3Aos9GI59L7w==}
engines: {node: ^22.18.0 || >=24.11.0}
@@ -3270,26 +3265,6 @@ packages:
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
- '@pnpm/crypto.hash@1000.2.2':
- resolution: {integrity: sha512-W8pLZvXWLlGG5p0Z2nCvtBhlM6uuTcbAbsS15wlGS31jBBJKJW2udLoFeM7qfWPo7E2PqRPGxca7APpVYAjJhw==}
- engines: {node: '>=18.12'}
-
- '@pnpm/crypto.polyfill@1000.1.0':
- resolution: {integrity: sha512-tNe7a6U4rCpxLMBaR0SIYTdjxGdL0Vwb3G1zY8++sPtHSvy7qd54u8CIB0Z+Y6t5tc9pNYMYCMwhE/wdSY7ltg==}
- engines: {node: '>=18.12'}
-
- '@pnpm/dependency-path@1001.1.10':
- resolution: {integrity: sha512-PNImtV2SmNTDpLi4HdN86tJPmsOeIxm4VhmxgBVsMrJPEBfkNEWFcflR3wU6XVn/26g9qWdvlNHaawtCjeB93Q==}
- engines: {node: '>=18.12'}
-
- '@pnpm/graceful-fs@1000.1.0':
- resolution: {integrity: sha512-EsMX4slK0qJN2AR0/AYohY5m0HQNYGMNe+jhN74O994zp22/WbX+PbkIKyw3UQn39yQm2+z6SgwklDxbeapsmQ==}
- engines: {node: '>=18.12'}
-
- '@pnpm/types@1001.3.0':
- resolution: {integrity: sha512-NLTXheat/u7OEGg5M5vF6Z85zx8uKUZE0+whtX/sbFV2XL48RdnOWGPTKYuVVkv8M+launaLUTgGEXNs/ess2w==}
- engines: {node: '>=18.12'}
-
'@protobufjs/aspromise@1.1.2':
resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==}
@@ -6768,8 +6743,8 @@ packages:
neo-async@2.6.2:
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
- ng-packagr@22.2.0-next.5:
- resolution: {integrity: sha512-fZixAT5MGDlxZnPISy1ml22DeOFVMwt3Ym/6l/VwzuAlj1K0YVJNgxxRQs+iX/qqDzcXjyVD+CQAAwAq5pQo3Q==}
+ ng-packagr@22.2.0-rc.0:
+ resolution: {integrity: sha512-l91WqbCFBKpVmDwW/rttDNe6kpRjsIlHnUf55YTH5JqN0Q7uVGBy6QbRUV2S+4/StyFqc+keOAtVPJUdO2bvxw==}
engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0}
hasBin: true
peerDependencies:
@@ -7946,10 +7921,6 @@ packages:
engines: {node: '>=0.10.0'}
hasBin: true
- ssri@10.0.5:
- resolution: {integrity: sha512-bSf16tAFkGeRlUNDjXu8FzaMQt6g2HZJrun7mtMbIPOddxt3GLMSz5VWUWcqTJUPfLEaDIepGxv+bYQW49596A==}
- engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
-
stack-trace@0.0.10:
resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==}
@@ -8794,9 +8765,6 @@ packages:
zod@3.25.76:
resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
- zod@4.5.4:
- resolution: {integrity: sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA==}
-
zod@4.6.5:
resolution: {integrity: sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==}
@@ -8826,30 +8794,30 @@ snapshots:
'@jridgewell/gen-mapping': 0.3.13
'@jridgewell/trace-mapping': 0.3.31
- '@angular/animations@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))':
+ '@angular/animations@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))':
dependencies:
- '@angular/core': 22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3)
+ '@angular/core': 22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3)
tslib: 2.8.1
- '@angular/cdk@22.2.0-next.4(164cec07f845c9f2a6f509bb1888cfce)':
+ '@angular/cdk@22.2.0-rc.0(32444abcc2be6e3bf2b6c059aa24d737)':
dependencies:
- '@angular/common': 22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2)
- '@angular/core': 22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3)
- '@angular/forms': 22.2.0-next.5(@angular/common@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.0-next.5(@angular/animations@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3)))(@angular/common@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2)
- '@angular/platform-browser': 22.2.0-next.5(@angular/animations@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3)))(@angular/common@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))
+ '@angular/common': 22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2)
+ '@angular/core': 22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3)
+ '@angular/forms': 22.2.0-rc.0(@angular/common@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.0-rc.0(@angular/animations@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3)))(@angular/common@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2)
+ '@angular/platform-browser': 22.2.0-rc.0(@angular/animations@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3)))(@angular/common@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))
parse5: 8.0.1
rxjs: 7.8.2
tslib: 2.8.1
- '@angular/common@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2)':
+ '@angular/common@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2)':
dependencies:
- '@angular/core': 22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3)
+ '@angular/core': 22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3)
rxjs: 7.8.2
tslib: 2.8.1
- '@angular/compiler-cli@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(typescript@6.0.3)':
+ '@angular/compiler-cli@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(typescript@6.0.3)':
dependencies:
- '@angular/compiler': 22.2.0-next.5
+ '@angular/compiler': 22.2.0-rc.0
'@babel/core': 8.0.1
'@jridgewell/sourcemap-codec': 1.6.0
chokidar: 5.0.0
@@ -8861,47 +8829,47 @@ snapshots:
optionalDependencies:
typescript: 6.0.3
- '@angular/compiler@22.2.0-next.5':
+ '@angular/compiler@22.2.0-rc.0':
dependencies:
tslib: 2.8.1
- '@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3)':
+ '@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3)':
dependencies:
rxjs: 7.8.2
tslib: 2.8.1
optionalDependencies:
- '@angular/compiler': 22.2.0-next.5
+ '@angular/compiler': 22.2.0-rc.0
zone.js: 0.16.3
- '@angular/forms@22.2.0-next.5(@angular/common@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.0-next.5(@angular/animations@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3)))(@angular/common@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2)':
+ '@angular/forms@22.2.0-rc.0(@angular/common@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.0-rc.0(@angular/animations@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3)))(@angular/common@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2)':
dependencies:
- '@angular/common': 22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2)
- '@angular/core': 22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3)
- '@angular/platform-browser': 22.2.0-next.5(@angular/animations@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3)))(@angular/common@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))
+ '@angular/common': 22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2)
+ '@angular/core': 22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3)
+ '@angular/platform-browser': 22.2.0-rc.0(@angular/animations@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3)))(@angular/common@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))
'@standard-schema/spec': 1.1.0
rxjs: 7.8.2
tslib: 2.8.1
- zod: 4.5.4
+ zod: 4.6.5
- '@angular/localize@22.2.0-next.5(@angular/compiler-cli@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(typescript@6.0.3))(@angular/compiler@22.2.0-next.5)':
+ '@angular/localize@22.2.0-rc.0(@angular/compiler-cli@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(typescript@6.0.3))(@angular/compiler@22.2.0-rc.0)':
dependencies:
- '@angular/compiler': 22.2.0-next.5
- '@angular/compiler-cli': 22.2.0-next.5(@angular/compiler@22.2.0-next.5)(typescript@6.0.3)
+ '@angular/compiler': 22.2.0-rc.0
+ '@angular/compiler-cli': 22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(typescript@6.0.3)
'@babel/core': 8.0.1
tinyglobby: 0.2.17
yargs: 18.1.0
- '@angular/material@22.2.0-next.4(95bc9c8a06206acb56b97c62cbcd354e)':
+ '@angular/material@22.2.0-rc.0(25ddf4063a130bbf19862bd16a6ab5d4)':
dependencies:
- '@angular/cdk': 22.2.0-next.4(164cec07f845c9f2a6f509bb1888cfce)
- '@angular/common': 22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2)
- '@angular/core': 22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3)
- '@angular/forms': 22.2.0-next.5(@angular/common@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.0-next.5(@angular/animations@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3)))(@angular/common@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2)
- '@angular/platform-browser': 22.2.0-next.5(@angular/animations@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3)))(@angular/common@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))
+ '@angular/cdk': 22.2.0-rc.0(32444abcc2be6e3bf2b6c059aa24d737)
+ '@angular/common': 22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2)
+ '@angular/core': 22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3)
+ '@angular/forms': 22.2.0-rc.0(@angular/common@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.0-rc.0(@angular/animations@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3)))(@angular/common@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2)
+ '@angular/platform-browser': 22.2.0-rc.0(@angular/animations@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3)))(@angular/common@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))
rxjs: 7.8.2
tslib: 2.8.1
- '@angular/ng-dev@https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/c3d7ed184d71d390d3eacff85f4b2b3f16b82660':
+ '@angular/ng-dev@https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/f5de34cabca7e005dc8102d3abb026d01a7a881a':
dependencies:
'@actions/core': 3.0.1
'@conventional-changelog/git-client': 3.1.2(conventional-commits-filter@6.0.1)(conventional-commits-parser@7.1.2)
@@ -8919,7 +8887,6 @@ snapshots:
'@octokit/request-error': 7.1.2
'@octokit/rest': 22.0.1
'@octokit/types': 17.0.0
- '@pnpm/dependency-path': 1001.1.10
'@types/cli-progress': 3.11.6
'@types/ejs': 3.1.5
'@types/events': 3.0.3
@@ -8956,40 +8923,40 @@ snapshots:
which: 7.0.0
yaml: 2.9.0
yargs: 18.1.0
- zod: 4.5.4
+ zod: 4.6.5
transitivePeerDependencies:
- '@modelcontextprotocol/sdk'
- '@react-native-async-storage/async-storage'
- '@angular/platform-browser@22.2.0-next.5(@angular/animations@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3)))(@angular/common@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))':
+ '@angular/platform-browser@22.2.0-rc.0(@angular/animations@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3)))(@angular/common@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))':
dependencies:
- '@angular/common': 22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2)
- '@angular/core': 22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3)
+ '@angular/common': 22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2)
+ '@angular/core': 22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3)
tslib: 2.8.1
optionalDependencies:
- '@angular/animations': 22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))
+ '@angular/animations': 22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))
- '@angular/platform-server@22.2.0-next.5(@angular/common@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/compiler@22.2.0-next.5)(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.0-next.5(@angular/animations@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3)))(@angular/common@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2)':
+ '@angular/platform-server@22.2.0-rc.0(@angular/common@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/compiler@22.2.0-rc.0)(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.0-rc.0(@angular/animations@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3)))(@angular/common@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2)':
dependencies:
- '@angular/common': 22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2)
- '@angular/compiler': 22.2.0-next.5
- '@angular/core': 22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3)
- '@angular/platform-browser': 22.2.0-next.5(@angular/animations@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3)))(@angular/common@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))
+ '@angular/common': 22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2)
+ '@angular/compiler': 22.2.0-rc.0
+ '@angular/core': 22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3)
+ '@angular/platform-browser': 22.2.0-rc.0(@angular/animations@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3)))(@angular/common@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))
rxjs: 7.8.2
tslib: 2.8.1
xhr2: 0.2.1
- '@angular/router@22.2.0-next.5(@angular/common@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.0-next.5(@angular/animations@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3)))(@angular/common@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2)':
+ '@angular/router@22.2.0-rc.0(@angular/common@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(@angular/platform-browser@22.2.0-rc.0(@angular/animations@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3)))(@angular/common@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3)))(rxjs@7.8.2)':
dependencies:
- '@angular/common': 22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2)
- '@angular/core': 22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3)
- '@angular/platform-browser': 22.2.0-next.5(@angular/animations@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3)))(@angular/common@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))
+ '@angular/common': 22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2)
+ '@angular/core': 22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3)
+ '@angular/platform-browser': 22.2.0-rc.0(@angular/animations@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3)))(@angular/common@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2))(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))
rxjs: 7.8.2
tslib: 2.8.1
- '@angular/service-worker@22.2.0-next.5(@angular/core@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2)':
+ '@angular/service-worker@22.2.0-rc.0(@angular/core@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3))(rxjs@7.8.2)':
dependencies:
- '@angular/core': 22.2.0-next.5(@angular/compiler@22.2.0-next.5)(rxjs@7.8.2)(zone.js@0.16.3)
+ '@angular/core': 22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(rxjs@7.8.2)(zone.js@0.16.3)
rxjs: 7.8.2
tslib: 2.8.1
@@ -9046,7 +9013,7 @@ snapshots:
'@babel/core@8.0.1':
dependencies:
'@babel/code-frame': 8.0.0
- '@babel/generator': 8.0.0
+ '@babel/generator': 8.0.5
'@babel/helper-compilation-targets': 8.0.5
'@babel/helpers': 8.0.5
'@babel/parser': 8.0.5
@@ -9089,15 +9056,6 @@ snapshots:
'@jridgewell/trace-mapping': 0.3.31
jsesc: 3.1.0
- '@babel/generator@8.0.0':
- dependencies:
- '@babel/parser': 8.0.5
- '@babel/types': 8.0.5
- '@jridgewell/gen-mapping': 0.3.13
- '@jridgewell/trace-mapping': 0.3.31
- '@types/jsesc': 2.5.1
- jsesc: 3.1.0
-
'@babel/generator@8.0.5':
dependencies:
'@babel/parser': 8.0.5
@@ -11304,26 +11262,6 @@ snapshots:
'@pkgjs/parseargs@0.11.0':
optional: true
- '@pnpm/crypto.hash@1000.2.2':
- dependencies:
- '@pnpm/crypto.polyfill': 1000.1.0
- '@pnpm/graceful-fs': 1000.1.0
- ssri: 10.0.5
-
- '@pnpm/crypto.polyfill@1000.1.0': {}
-
- '@pnpm/dependency-path@1001.1.10':
- dependencies:
- '@pnpm/crypto.hash': 1000.2.2
- '@pnpm/types': 1001.3.0
- semver: 7.8.5
-
- '@pnpm/graceful-fs@1000.1.0':
- dependencies:
- graceful-fs: 4.2.11
-
- '@pnpm/types@1001.3.0': {}
-
'@protobufjs/aspromise@1.1.2': {}
'@protobufjs/base64@1.1.2': {}
@@ -15129,10 +15067,10 @@ snapshots:
neo-async@2.6.2: {}
- ng-packagr@22.2.0-next.5(@angular/compiler-cli@22.2.0-next.5(@angular/compiler@22.2.0-next.5)(typescript@6.0.3))(supports-color@11.0.0)(tslib@2.8.1)(typescript@6.0.3):
+ ng-packagr@22.2.0-rc.0(@angular/compiler-cli@22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(typescript@6.0.3))(supports-color@11.0.0)(tslib@2.8.1)(typescript@6.0.3):
dependencies:
'@ampproject/remapping': 2.3.0
- '@angular/compiler-cli': 22.2.0-next.5(@angular/compiler@22.2.0-next.5)(typescript@6.0.3)
+ '@angular/compiler-cli': 22.2.0-rc.0(@angular/compiler@22.2.0-rc.0)(typescript@6.0.3)
ajv: 8.20.0
browserslist: 4.28.9
chokidar: 5.0.0
@@ -15148,8 +15086,8 @@ snapshots:
rolldown: 1.2.8
rolldown-plugin-dts: 0.28.5(rolldown@1.2.8)(typescript@6.0.3)
rxjs: 7.8.2
- sass: 1.104.0
- sass-embedded: 1.104.0
+ sass: 1.104.1
+ sass-embedded: 1.104.1
tinyglobby: 0.2.17
tslib: 2.8.1
typescript: 6.0.3
@@ -16158,6 +16096,7 @@ snapshots:
sass-embedded-unknown-all: 1.104.0
sass-embedded-win32-arm64: 1.104.0
sass-embedded-win32-x64: 1.104.0
+ optional: true
sass-embedded@1.104.1:
dependencies:
@@ -16201,6 +16140,7 @@ snapshots:
source-map-js: 1.2.1
optionalDependencies:
'@parcel/watcher': 2.6.0
+ optional: true
sass@1.104.1:
dependencies:
@@ -16467,10 +16407,6 @@ snapshots:
safer-buffer: 2.1.2
tweetnacl: 0.14.5
- ssri@10.0.5:
- dependencies:
- minipass: 7.1.3
-
stack-trace@0.0.10: {}
stackback@0.0.2: {}
@@ -17482,8 +17418,6 @@ snapshots:
zod@3.25.76: {}
- zod@4.5.4: {}
-
zod@4.6.5: {}
zone.js@0.16.3: {}
diff --git a/scripts/benchmark.mts b/scripts/benchmark.mts
new file mode 100644
index 000000000000..bf35b97808e3
--- /dev/null
+++ b/scripts/benchmark.mts
@@ -0,0 +1,150 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.dev/license
+ */
+
+import fs from 'node:fs';
+import path from 'node:path';
+import { type BenchmarkCliOptions, runI18nBenchmarks } from './benchmarks/i18n/index.mts';
+
+function checkBuildStatus(logger: Console): boolean {
+ const distFile = 'dist/@angular/build/src/tools/esbuild/i18n-inliner.js';
+ const srcFile = 'packages/angular/build/src/tools/esbuild/i18n-inliner.ts';
+
+ if (!fs.existsSync(distFile)) {
+ logger.error(
+ 'Error: @angular/build has not been built yet.\nPlease run "pnpm build" before benchmarking.',
+ );
+
+ return false;
+ }
+
+ if (fs.existsSync(srcFile)) {
+ const srcMtime = fs.statSync(srcFile).mtimeMs;
+ const distMtime = fs.statSync(distFile).mtimeMs;
+ if (srcMtime > distMtime) {
+ logger.warn(
+ 'Warning: Source files in packages/angular/build are newer than dist/.\n' +
+ 'Run "pnpm build" to ensure your benchmark reflects your latest local edits.\n',
+ );
+ }
+ }
+
+ return true;
+}
+
+export default async function (
+ options: {
+ _?: string[];
+ scenario?: string;
+ iterations?: string | number;
+ warmup?: string | number;
+ concurrency?: string | number;
+ build?: boolean;
+ json?: boolean;
+ inProcess?: boolean;
+ 'in-process'?: boolean;
+ saveBaseline?: string;
+ 'save-baseline'?: string;
+ compareBaseline?: string;
+ 'compare-baseline'?: string;
+ help?: boolean;
+ [key: string]: unknown;
+ },
+ _cwd: string,
+): Promise {
+ const positionals = options._ ?? [];
+ const targetSubsystem = positionals[0] ?? 'i18n';
+
+ if (options.help || targetSubsystem === 'help') {
+ // eslint-disable-next-line no-console
+ console.log(`
+Angular CLI Performance Benchmark Runner
+
+Usage:
+ pnpm admin benchmark [subsystem] [options]
+
+Subsystems:
+ i18n Run i18n inliner performance benchmarks (default)
+
+Options:
+ --scenario= Run a specific scenario (e.g. standard-app, enterprise-multilingual)
+ --iterations= Number of measured iterations (default: 5)
+ --warmup= Number of warmup iterations (default: 2)
+ --concurrency= Override worker thread pool concurrency
+ --in-process Run all scenarios in a single process (useful for debugging)
+ --build Automatically build packages before benchmarking
+ --json Output results in machine-readable JSON
+ --save-baseline= Save run results to a baseline JSON file
+ --compare-baseline= Compare run results against an existing baseline JSON file
+ --help Show this help message
+`);
+
+ return 0;
+ }
+
+ if (targetSubsystem !== 'i18n') {
+ // eslint-disable-next-line no-console
+ console.error(`Unknown benchmark subsystem: "${targetSubsystem}". Supported subsystems: i18n`);
+
+ return 1;
+ }
+
+ if (options.build) {
+ const buildModule = await import('./build.mts');
+ await buildModule.default({ local: true });
+ }
+
+ // eslint-disable-next-line no-console
+ if (!checkBuildStatus(console)) {
+ return 1;
+ }
+
+ const rawSaveBaseline = options.saveBaseline ?? options['save-baseline'];
+ const rawCompareBaseline = options.compareBaseline ?? options['compare-baseline'];
+
+ const iterations = options.iterations !== undefined ? Number(options.iterations) : undefined;
+ const warmup = options.warmup !== undefined ? Number(options.warmup) : undefined;
+ const concurrency = options.concurrency !== undefined ? Number(options.concurrency) : undefined;
+
+ if (iterations !== undefined && (!Number.isInteger(iterations) || iterations < 1)) {
+ // eslint-disable-next-line no-console
+ console.error('Error: --iterations must be a positive integer.');
+
+ return 1;
+ }
+
+ if (warmup !== undefined && (!Number.isInteger(warmup) || warmup < 0)) {
+ // eslint-disable-next-line no-console
+ console.error('Error: --warmup must be a non-negative integer.');
+
+ return 1;
+ }
+
+ if (concurrency !== undefined && (!Number.isInteger(concurrency) || concurrency < 1)) {
+ // eslint-disable-next-line no-console
+ console.error('Error: --concurrency must be a positive integer.');
+
+ return 1;
+ }
+
+ const cliOptions: BenchmarkCliOptions = {
+ scenario: options.scenario,
+ iterations,
+ warmup,
+ concurrency,
+ json: Boolean(options.json),
+ inProcess: Boolean(options.inProcess ?? options['in-process']),
+ saveBaseline: rawSaveBaseline ? path.resolve(_cwd, String(rawSaveBaseline)) : undefined,
+ compareBaseline: rawCompareBaseline
+ ? path.resolve(_cwd, String(rawCompareBaseline))
+ : undefined,
+ };
+
+ const { exitCode } = await runI18nBenchmarks(cliOptions);
+
+ return exitCode;
+}
diff --git a/scripts/benchmarks/i18n/fixtures.mts b/scripts/benchmarks/i18n/fixtures.mts
new file mode 100644
index 000000000000..3d53c3dbff5b
--- /dev/null
+++ b/scripts/benchmarks/i18n/fixtures.mts
@@ -0,0 +1,165 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.dev/license
+ */
+
+import './init-env.mts';
+import type { ɵParsedTranslation } from '@angular/localize';
+import { transformSync } from 'esbuild';
+import { createRequire } from 'node:module';
+
+import path from 'node:path';
+
+import type { BuildOutputFile } from '../../../packages/angular/build/src/tools/esbuild/bundler-files.js';
+import type { LocaleInlineOptions } from '../../../packages/angular/build/src/tools/esbuild/i18n-inliner.js';
+
+// Setup module paths to resolve dependencies from packages/angular/build
+const requireFromBuild = createRequire(
+ path.resolve(import.meta.dirname, '../../../packages/angular/build/package.json'),
+);
+
+const { BuildOutputFileType, createOutputFile } = requireFromBuild(
+ '../../../dist/@angular/build/src/tools/esbuild/bundler-files.js',
+) as typeof import('../../../packages/angular/build/src/tools/esbuild/bundler-files.js');
+
+const { calculateHash, initializeHash } = requireFromBuild(
+ '../../../dist/@angular/build/src/utils/hash.js',
+) as typeof import('../../../packages/angular/build/src/utils/hash.js');
+
+let isHashInitialized = false;
+
+export async function initializeFixtures(): Promise {
+ if (!isHashInitialized) {
+ await initializeHash();
+ isHashInitialized = true;
+ }
+}
+
+export function parsedTranslation(
+ parts: string[],
+ placeholderNames: string[] = [],
+ text?: string,
+): ɵParsedTranslation {
+ return {
+ messageParts: Object.assign([...parts], { raw: [...parts] }),
+ placeholderNames,
+ text: text ?? parts.join(''),
+ };
+}
+
+export function generateTranslations(
+ locales: string[],
+ messageCount: number,
+): LocaleInlineOptions[] {
+ return locales.map((locale) => {
+ const translation: Record = {};
+
+ for (let i = 0; i < messageCount; i++) {
+ const msgId = `msg_${i}`;
+ translation[msgId] = parsedTranslation(
+ [`[${locale}] Order #`, ` was confirmed for customer `, `. Thank you!`],
+ ['orderId', 'customerName'],
+ `[${locale}] Order #${i} was confirmed for customer Doe. Thank you!`,
+ );
+ }
+
+ const translationIntegrity = calculateHash(JSON.stringify(translation));
+
+ return {
+ locale,
+ translation,
+ translationIntegrity,
+ };
+ });
+}
+
+export interface SyntheticBundleOptions {
+ filename: string;
+ targetByteSize: number;
+ messageCount: number;
+ withSourceMap?: boolean;
+ messageIdOffset?: number;
+}
+
+export function generateSyntheticBundle(options: SyntheticBundleOptions): {
+ codeFile: BuildOutputFile;
+ mapFile?: BuildOutputFile;
+} {
+ const {
+ filename,
+ targetByteSize,
+ messageCount,
+ withSourceMap = true,
+ messageIdOffset = 0,
+ } = options;
+
+ const parts: string[] = [
+ '// Synthetic test bundle generated for i18n-inliner benchmark\n',
+ 'export const BUNDLE_META = { generated: true, timestamp: Date.now() };\n',
+ ];
+
+ // Generate functions with $localize call sites
+ for (let i = 0; i < messageCount; i++) {
+ const msgId = `msg_${messageIdOffset + i}`;
+ parts.push(
+ `export function renderMessage_${i}(orderId, customerName) {\n`,
+ ` return $localize\`:@@${msgId}:Order #\${orderId}:orderId: ` +
+ `was confirmed for customer \${customerName}:customerName:. Thank you!\`;\n`,
+ `}\n`,
+ );
+ }
+
+ // Calculate current approximate size and pad with realistic JS functions if needed
+ let currentSize = parts.reduce((acc, str) => acc + str.length, 0);
+ let classIndex = 0;
+
+ while (currentSize < targetByteSize) {
+ const filler =
+ `export class DataProcessor_${classIndex} {\n` +
+ ` constructor(id, options = {}) {\n` +
+ ` this.id = id;\n` +
+ ` this.options = Object.assign({ enabled: true, retries: 3 }, options);\n` +
+ ` this.history = [];\n` +
+ ` }\n` +
+ ` process(batch) {\n` +
+ ` if (!Array.isArray(batch)) return [];\n` +
+ ` const result = batch.map((item, idx) => ({\n` +
+ ` id: this.id + '_' + idx,\n` +
+ ` source: item,\n` +
+ ` timestamp: Date.now(),\n` +
+ ` active: true\n` +
+ ` }));\n` +
+ ` this.history.push(...result);\n` +
+ ` return result;\n` +
+ ` }\n` +
+ `}\n`;
+
+ parts.push(filler);
+ currentSize += filler.length;
+ classIndex++;
+ }
+
+ const rawCode = parts.join('');
+
+ let finalCode = rawCode;
+ let finalMap: string | undefined;
+
+ if (withSourceMap) {
+ const result = transformSync(rawCode, {
+ sourcemap: true,
+ sourcefile: filename.replace(/\.js$/, '.ts'),
+ });
+ finalCode = result.code;
+ finalMap = result.map;
+ }
+
+ const codeFile = createOutputFile(filename, finalCode, BuildOutputFileType.Browser);
+ const mapFile = finalMap
+ ? createOutputFile(filename + '.map', finalMap, BuildOutputFileType.Browser)
+ : undefined;
+
+ return { codeFile, mapFile };
+}
diff --git a/scripts/benchmarks/i18n/harness.mts b/scripts/benchmarks/i18n/harness.mts
new file mode 100644
index 000000000000..caf7f0753d24
--- /dev/null
+++ b/scripts/benchmarks/i18n/harness.mts
@@ -0,0 +1,155 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.dev/license
+ */
+
+import './init-env.mts';
+
+export interface BenchmarkScenario {
+ name: string;
+ description: string;
+ inputSizeBytes: number;
+ localeCount: number;
+ run(iteration: number): Promise;
+ setup?(): Promise;
+ teardown?(): Promise;
+}
+
+export interface ScenarioResult {
+ name: string;
+ description: string;
+ inputSizeBytes: number;
+ localeCount: number;
+ iterations: number;
+ warmup: number;
+ durationsMs: number[];
+ minMs: number;
+ maxMs: number;
+ meanMs: number;
+ medianMs: number;
+ p95Ms: number;
+ stdDevMs: number;
+ throughputMBps: number;
+ peakRssBytes: number;
+ peakHeapBytes: number;
+ rssDeltaBytes: number;
+ heapUsedDeltaBytes: number;
+}
+
+export interface BenchmarkRunOptions {
+ warmup?: number;
+ iterations?: number;
+}
+
+function calculatePercentile(sortedValues: number[], percentile: number): number {
+ if (sortedValues.length === 0) {
+ return 0;
+ }
+ const index = (percentile / 100) * (sortedValues.length - 1);
+ const lower = Math.floor(index);
+ const upper = Math.ceil(index);
+ const weight = index - lower;
+
+ return sortedValues[lower] * (1 - weight) + sortedValues[upper] * weight;
+}
+
+export async function runScenario(
+ scenario: BenchmarkScenario,
+ options: BenchmarkRunOptions = {},
+): Promise {
+ const warmup = options.warmup ?? 2;
+ const iterations = options.iterations ?? 5;
+
+ try {
+ await scenario.setup?.();
+
+ // Warmup phase
+ for (let w = 0; w < warmup; w++) {
+ // Force GC if available between warmups
+ global.gc?.();
+ await scenario.run(-(w + 1));
+ }
+
+ // Measurement phase
+ const durationsMs: number[] = [];
+ let peakRssBytes = 0;
+ let peakHeapBytes = 0;
+ let maxRssDeltaBytes = 0;
+ let initialHeap = 0;
+ let finalHeap = 0;
+
+ for (let i = 0; i < iterations; i++) {
+ global.gc?.();
+
+ const memBefore = process.memoryUsage();
+ if (i === 0) {
+ initialHeap = memBefore.heapUsed;
+ }
+
+ const start = performance.now();
+ await scenario.run(i);
+ const duration = performance.now() - start;
+
+ durationsMs.push(duration);
+
+ const memAfter = process.memoryUsage();
+ if (memAfter.rss > peakRssBytes) {
+ peakRssBytes = memAfter.rss;
+ }
+ if (memAfter.heapUsed > peakHeapBytes) {
+ peakHeapBytes = memAfter.heapUsed;
+ }
+ const rssDelta = Math.max(0, memAfter.rss - memBefore.rss);
+ if (rssDelta > maxRssDeltaBytes) {
+ maxRssDeltaBytes = rssDelta;
+ }
+
+ // Force GC immediately after iteration to clean main thread isolate
+ global.gc?.();
+ finalHeap = process.memoryUsage().heapUsed;
+ }
+
+ // Sort ascending for percentile computation
+ const sorted = [...durationsMs].sort((a, b) => a - b);
+ const minMs = sorted[0];
+ const maxMs = sorted[sorted.length - 1];
+ const meanMs = durationsMs.reduce((sum, d) => sum + d, 0) / durationsMs.length;
+ const medianMs = calculatePercentile(sorted, 50);
+ const p95Ms = calculatePercentile(sorted, 95);
+
+ const variance =
+ durationsMs.reduce((sum, d) => sum + Math.pow(d - meanMs, 2), 0) / durationsMs.length;
+ const stdDevMs = Math.sqrt(variance);
+
+ // Throughput: total effective processed code volume in MB / mean seconds
+ const totalProcessedMb = (scenario.inputSizeBytes * scenario.localeCount) / (1024 * 1024);
+ const meanSeconds = meanMs / 1000;
+ const throughputMBps = meanSeconds > 0 ? totalProcessedMb / meanSeconds : 0;
+
+ return {
+ name: scenario.name,
+ description: scenario.description,
+ inputSizeBytes: scenario.inputSizeBytes,
+ localeCount: scenario.localeCount,
+ iterations,
+ warmup,
+ durationsMs,
+ minMs,
+ maxMs,
+ meanMs,
+ medianMs,
+ p95Ms,
+ stdDevMs,
+ throughputMBps,
+ peakRssBytes,
+ peakHeapBytes,
+ rssDeltaBytes: maxRssDeltaBytes,
+ heapUsedDeltaBytes: Math.max(0, finalHeap - initialHeap),
+ };
+ } finally {
+ await scenario.teardown?.();
+ }
+}
diff --git a/scripts/benchmarks/i18n/index.mts b/scripts/benchmarks/i18n/index.mts
new file mode 100644
index 000000000000..276671c193e0
--- /dev/null
+++ b/scripts/benchmarks/i18n/index.mts
@@ -0,0 +1,167 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.dev/license
+ */
+
+import './init-env.mts';
+
+import { spawnSync } from 'node:child_process';
+import fs from 'node:fs/promises';
+import path from 'node:path';
+import { type ScenarioResult, runScenario } from './harness.mts';
+import {
+ buildReportData,
+ formatComparisonTable,
+ formatConsoleTable,
+ formatJsonReport,
+} from './reporters.mts';
+import { type ScenarioFactoryOptions, getAllScenarios, getScenarioByName } from './scenarios.mts';
+
+export interface BenchmarkCliOptions extends ScenarioFactoryOptions {
+ scenario?: string;
+ iterations?: number;
+ warmup?: number;
+ verbose?: boolean;
+ json?: boolean;
+ inProcess?: boolean;
+ saveBaseline?: string;
+ compareBaseline?: string;
+}
+
+export async function runI18nBenchmarks(
+ options: BenchmarkCliOptions = {},
+): Promise<{ results: ScenarioResult[]; exitCode: number }> {
+ const warmup = options.warmup ?? 2;
+ const iterations = options.iterations ?? 5;
+
+ let scenariosToRun = getAllScenarios({ concurrency: options.concurrency });
+
+ if (options.scenario) {
+ const single = getScenarioByName(options.scenario, { concurrency: options.concurrency });
+ if (!single) {
+ // eslint-disable-next-line no-console
+ console.error(
+ `Unknown scenario: "${options.scenario}".\nAvailable scenarios: ${scenariosToRun.map((s) => s.name).join(', ')}`,
+ );
+
+ return { results: [], exitCode: 1 };
+ }
+ scenariosToRun = [single];
+ }
+
+ const results: ScenarioResult[] = [];
+
+ // When running multiple scenarios in a suite, isolate each scenario into its own child process
+ // so operating system memory and thread caches are not accumulated across scenarios.
+ if (scenariosToRun.length > 1 && !options.inProcess) {
+ for (const scenario of scenariosToRun) {
+ if (!options.json) {
+ // eslint-disable-next-line no-console
+ console.log(
+ `Running scenario: ${scenario.name} (${warmup} warmups, ${iterations} iterations)...`,
+ );
+ }
+
+ const args = [
+ '--no-warnings=ExperimentalWarning',
+ '--experimental-transform-types',
+ '--expose-gc',
+ path.resolve(import.meta.dirname, '../../devkit-admin.mts'),
+ 'benchmark',
+ 'i18n',
+ `--scenario=${scenario.name}`,
+ `--warmup=${warmup}`,
+ `--iterations=${iterations}`,
+ '--json',
+ ];
+ if (options.concurrency !== undefined) {
+ args.push(`--concurrency=${options.concurrency}`);
+ }
+
+ const proc = spawnSync(process.execPath, args, { encoding: 'utf-8' });
+ if (proc.status !== 0 || proc.error) {
+ // eslint-disable-next-line no-console
+ console.error(
+ `Error running scenario ${scenario.name}:\n` +
+ (proc.error?.message ?? (proc.stderr || proc.stdout)),
+ );
+
+ return { results, exitCode: 1 };
+ }
+
+ try {
+ const parsed = JSON.parse(proc.stdout);
+ const scenarioResult: ScenarioResult | undefined = Array.isArray(parsed)
+ ? parsed[0]
+ : parsed.results?.[0];
+ if (scenarioResult) {
+ results.push(scenarioResult);
+ }
+ } catch {
+ // eslint-disable-next-line no-console
+ console.error(`Failed to parse result for scenario ${scenario.name}:\n${proc.stdout}`);
+
+ return { results, exitCode: 1 };
+ }
+ }
+ } else {
+ for (const scenario of scenariosToRun) {
+ if (!options.json) {
+ // eslint-disable-next-line no-console
+ console.log(
+ `Running scenario: ${scenario.name} (${warmup} warmups, ${iterations} iterations)...`,
+ );
+ }
+
+ try {
+ const result = await runScenario(scenario, { warmup, iterations });
+ results.push(result);
+ } catch (error) {
+ // eslint-disable-next-line no-console
+ console.error(`Error running scenario ${scenario.name}:`, error);
+
+ return { results, exitCode: 1 };
+ }
+ }
+ }
+
+ if (options.json) {
+ // eslint-disable-next-line no-console
+ console.log(formatJsonReport(results));
+ } else {
+ // eslint-disable-next-line no-console
+ console.log('\n' + formatConsoleTable(results));
+ }
+
+ if (options.saveBaseline) {
+ const reportData = buildReportData(results);
+ await fs.writeFile(options.saveBaseline, JSON.stringify(reportData, null, 2), 'utf-8');
+ if (!options.json) {
+ // eslint-disable-next-line no-console
+ console.log(`Saved baseline to: ${options.saveBaseline}`);
+ }
+ }
+
+ if (options.compareBaseline) {
+ try {
+ const baselineContent = await fs.readFile(options.compareBaseline, 'utf-8');
+ const baselineData = JSON.parse(baselineContent);
+ const baselineResults: ScenarioResult[] = Array.isArray(baselineData)
+ ? baselineData
+ : (baselineData.results ?? []);
+
+ if (!options.json) {
+ // eslint-disable-next-line no-console
+ console.log(formatComparisonTable(results, baselineResults));
+ }
+ } catch (error) {
+ // eslint-disable-next-line no-console
+ console.error(`Failed to load baseline from ${options.compareBaseline}:`, error);
+ }
+ }
+
+ return { results, exitCode: 0 };
+}
diff --git a/scripts/benchmarks/i18n/init-env.mts b/scripts/benchmarks/i18n/init-env.mts
new file mode 100644
index 000000000000..a1690cf576c8
--- /dev/null
+++ b/scripts/benchmarks/i18n/init-env.mts
@@ -0,0 +1,26 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.dev/license
+ */
+
+import Module from 'node:module';
+import path from 'node:path';
+
+// Resolve dependencies from packages/angular/build/node_modules for runtime resolution in dist/
+const buildNodeModules = path.resolve(
+ import.meta.dirname,
+ '../../../packages/angular/build/node_modules',
+);
+
+const currentPath = process.env.NODE_PATH ?? '';
+if (!currentPath.includes(buildNodeModules)) {
+ process.env.NODE_PATH = currentPath
+ ? `${buildNodeModules}${path.delimiter}${currentPath}`
+ : buildNodeModules;
+ // Initialize internal search paths for Node CommonJS loader
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ (Module as any)._initPaths?.();
+}
diff --git a/scripts/benchmarks/i18n/reporters.mts b/scripts/benchmarks/i18n/reporters.mts
new file mode 100644
index 000000000000..d5153552a2b3
--- /dev/null
+++ b/scripts/benchmarks/i18n/reporters.mts
@@ -0,0 +1,159 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.dev/license
+ */
+
+import os from 'node:os';
+import type { ScenarioResult } from './harness.mts';
+
+export interface BenchmarkReportData {
+ timestamp: string;
+ system: {
+ nodeVersion: string;
+ platform: string;
+ arch: string;
+ cpus: number;
+ cpuModel: string;
+ totalMemoryMb: number;
+ };
+ results: ScenarioResult[];
+}
+
+export function buildReportData(results: ScenarioResult[]): BenchmarkReportData {
+ const cpus = os.cpus();
+
+ return {
+ timestamp: new Date().toISOString(),
+ system: {
+ nodeVersion: process.version,
+ platform: os.platform(),
+ arch: os.arch(),
+ cpus: cpus.length,
+ cpuModel: cpus[0]?.model ?? 'unknown',
+ totalMemoryMb: Math.round(os.totalmem() / (1024 * 1024)),
+ },
+ results,
+ };
+}
+
+export function formatJsonReport(results: ScenarioResult[]): string {
+ return JSON.stringify(buildReportData(results), null, 2);
+}
+
+function formatBytes(bytes: number): string {
+ if (bytes < 1024) {
+ return `${bytes} B`;
+ }
+ if (bytes < 1024 * 1024) {
+ return `${(bytes / 1024).toFixed(1)} KB`;
+ }
+
+ return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
+}
+
+function padRight(str: string, len: number): string {
+ return str.length >= len ? str : str + ' '.repeat(len - str.length);
+}
+
+function padLeft(str: string, len: number): string {
+ return str.length >= len ? str : ' '.repeat(len - str.length) + str;
+}
+
+export function formatConsoleTable(results: ScenarioResult[]): string {
+ const cpus = os.cpus();
+ const gcStatus = typeof global.gc === 'function' ? 'active' : 'inactive (run with --expose-gc)';
+ const header =
+ `================================================================================================================\n` +
+ `i18n Inliner Performance Benchmarks (Node ${process.version}, ${cpus.length} CPUs: ${cpus[0]?.model ?? ''} | GC: ${gcStatus})\n` +
+ `================================================================================================================\n`;
+
+ const columns = [
+ { name: 'Scenario', width: 25 },
+ { name: 'Input Size', width: 11 },
+ { name: 'Locales', width: 8 },
+ { name: 'Mean Latency', width: 13 },
+ { name: 'p50 / p95', width: 18 },
+ { name: 'Throughput', width: 12 },
+ { name: 'Peak Heap', width: 11 },
+ { name: 'Peak RSS', width: 11 },
+ ];
+
+ const colHeader = columns.map((col) => padRight(col.name, col.width)).join(' ');
+ const separator = columns.map((col) => '-'.repeat(col.width)).join(' ');
+
+ const rows = results.map((r) => {
+ const inputFormatted = formatBytes(r.inputSizeBytes);
+ const meanFormatted = `${r.meanMs.toFixed(1)} ms`;
+ const p50p95Formatted = `${r.medianMs.toFixed(0)} ms / ${r.p95Ms.toFixed(0)} ms`;
+ const throughputFormatted = `${r.throughputMBps.toFixed(1)} MB/s`;
+ const peakHeapFormatted = formatBytes(r.peakHeapBytes ?? 0);
+ const peakRssFormatted = formatBytes(r.peakRssBytes);
+
+ return [
+ padRight(r.name, columns[0].width),
+ padLeft(inputFormatted, columns[1].width),
+ padLeft(r.localeCount.toString(), columns[2].width),
+ padLeft(meanFormatted, columns[3].width),
+ padLeft(p50p95Formatted, columns[4].width),
+ padLeft(throughputFormatted, columns[5].width),
+ padLeft(peakHeapFormatted, columns[6].width),
+ padLeft(peakRssFormatted, columns[7].width),
+ ].join(' ');
+ });
+
+ return `${header}\n${colHeader}\n${separator}\n${rows.join('\n')}\n${'='.repeat(separator.length)}\n`;
+}
+
+export function formatComparisonTable(
+ currentResults: ScenarioResult[],
+ baselineResults: ScenarioResult[],
+): string {
+ const header =
+ `====================================================================================================\n` +
+ `i18n Inliner Benchmark Comparison (Current vs Baseline)\n` +
+ `====================================================================================================\n`;
+
+ const columns = [
+ { name: 'Scenario', width: 26 },
+ { name: 'Baseline Mean', width: 14 },
+ { name: 'Current Mean', width: 14 },
+ { name: 'Latency Diff', width: 14 },
+ { name: 'Baseline MB/s', width: 14 },
+ { name: 'Current MB/s', width: 14 },
+ ];
+
+ const colHeader = columns.map((col) => padRight(col.name, col.width)).join(' ');
+ const separator = columns.map((col) => '-'.repeat(col.width)).join(' ');
+
+ const rows = currentResults.map((curr) => {
+ const base = baselineResults.find((b) => b.name === curr.name);
+ if (!base) {
+ return [
+ padRight(curr.name, columns[0].width),
+ padLeft('N/A', columns[1].width),
+ padLeft(`${curr.meanMs.toFixed(1)} ms`, columns[2].width),
+ padLeft('NEW', columns[3].width),
+ padLeft('N/A', columns[4].width),
+ padLeft(`${curr.throughputMBps.toFixed(1)} MB/s`, columns[5].width),
+ ].join(' ');
+ }
+
+ const diffPercent = ((curr.meanMs - base.meanMs) / base.meanMs) * 100;
+ const diffSign = diffPercent > 0 ? '+' : '';
+ const diffText = `${diffSign}${diffPercent.toFixed(1)}% ${diffPercent > 1 ? '(slower)' : diffPercent < -1 ? '(faster)' : '(same)'}`;
+
+ return [
+ padRight(curr.name, columns[0].width),
+ padLeft(`${base.meanMs.toFixed(1)} ms`, columns[1].width),
+ padLeft(`${curr.meanMs.toFixed(1)} ms`, columns[2].width),
+ padLeft(diffText, columns[3].width),
+ padLeft(`${base.throughputMBps.toFixed(1)} MB/s`, columns[4].width),
+ padLeft(`${curr.throughputMBps.toFixed(1)} MB/s`, columns[5].width),
+ ].join(' ');
+ });
+
+ return `${header}\n${colHeader}\n${separator}\n${rows.join('\n')}\n${'='.repeat(separator.length)}\n`;
+}
diff --git a/scripts/benchmarks/i18n/scenarios.mts b/scripts/benchmarks/i18n/scenarios.mts
new file mode 100644
index 000000000000..c00f34ded8a2
--- /dev/null
+++ b/scripts/benchmarks/i18n/scenarios.mts
@@ -0,0 +1,476 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.dev/license
+ */
+
+import './init-env.mts';
+
+import { spawnSync } from 'node:child_process';
+import fs from 'node:fs/promises';
+import { createRequire } from 'node:module';
+import os from 'node:os';
+import path from 'node:path';
+import { pathToFileURL } from 'node:url';
+
+import type { BuildOutputFile } from '../../../packages/angular/build/src/tools/esbuild/bundler-files.js';
+import type { LocaleInlineOptions } from '../../../packages/angular/build/src/tools/esbuild/i18n-inliner.js';
+import { generateSyntheticBundle, generateTranslations, initializeFixtures } from './fixtures.mts';
+import type { BenchmarkScenario } from './harness.mts';
+
+const requireFromBuild = createRequire(
+ path.resolve(import.meta.dirname, '../../../packages/angular/build/package.json'),
+);
+
+const { I18nInliner } = requireFromBuild(
+ '../../../dist/@angular/build/src/tools/esbuild/i18n-inliner.js',
+) as typeof import('../../../packages/angular/build/src/tools/esbuild/i18n-inliner.js');
+
+export interface ScenarioFactoryOptions {
+ concurrency?: number;
+}
+
+const DEFAULT_LOCALES_8 = ['fr', 'de', 'es', 'ja', 'zh', 'it', 'pt', 'ko'];
+const DEFAULT_LOCALES_32 = [
+ 'fr',
+ 'de',
+ 'es',
+ 'ja',
+ 'zh',
+ 'it',
+ 'pt',
+ 'ko',
+ 'ru',
+ 'pl',
+ 'nl',
+ 'tr',
+ 'ar',
+ 'hi',
+ 'sv',
+ 'da',
+ 'fi',
+ 'no',
+ 'cs',
+ 'el',
+ 'he',
+ 'hu',
+ 'id',
+ 'ms',
+ 'ro',
+ 'sk',
+ 'th',
+ 'uk',
+ 'vi',
+ 'bg',
+ 'hr',
+ 'sr',
+];
+
+interface GeneratedWorkload {
+ files: BuildOutputFile[];
+ locales: LocaleInlineOptions[];
+ totalInputSizeBytes: number;
+}
+
+function createBundleSet(
+ mainSizeBytes: number,
+ chunkCount: number,
+ chunkSizeBytes: number,
+ messageCount: number,
+ withSourceMap: boolean,
+): BuildOutputFile[] {
+ const files: BuildOutputFile[] = [];
+
+ const mainMessages = Math.floor(messageCount * 0.4);
+ const { codeFile: mainCode, mapFile: mainMap } = generateSyntheticBundle({
+ filename: 'main.js',
+ targetByteSize: mainSizeBytes,
+ messageCount: mainMessages,
+ withSourceMap,
+ messageIdOffset: 0,
+ });
+
+ files.push(mainCode);
+ if (mainMap) {
+ files.push(mainMap);
+ }
+
+ const remainingMessages = messageCount - mainMessages;
+ const messagesPerChunk = Math.max(1, Math.floor(remainingMessages / chunkCount));
+
+ for (let i = 0; i < chunkCount; i++) {
+ const { codeFile, mapFile } = generateSyntheticBundle({
+ filename: `chunk_${i}.js`,
+ targetByteSize: chunkSizeBytes,
+ messageCount: messagesPerChunk,
+ withSourceMap,
+ messageIdOffset: mainMessages + i * messagesPerChunk,
+ });
+
+ files.push(codeFile);
+ if (mapFile) {
+ files.push(mapFile);
+ }
+ }
+
+ return files;
+}
+
+function calculateInputSizeBytes(files: BuildOutputFile[]): number {
+ return files.reduce((total, f) => {
+ // Only count JS code size towards raw input volume (maps are auxiliary)
+ return f.path.endsWith('.js') ? total + f.size : total;
+ }, 0);
+}
+
+/**
+ * Note on Worker Pool Lifecycle:
+ * Each scenario's run() method instantiates and closes an I18nInliner per iteration.
+ * This is designed as a macro benchmark to reflect the cold-start behavior of single-shot
+ * CLI build invocations (including worker thread pool initialization, task dispatch,
+ * inlining transformations, and thread pool shutdown). Warmup iterations warm up the
+ * main-thread V8 isolate and runtime paths, while worker threads are initialized per iteration.
+ */
+
+/**
+ * 1. Standard App Scenario:
+ * 1 main bundle (1 MB) + 20 route chunks (50 KB) = ~2 MB input JS, 8 locales, maps enabled.
+ */
+export function createStandardAppScenario(options: ScenarioFactoryOptions = {}): BenchmarkScenario {
+ let workload: GeneratedWorkload | undefined;
+
+ return {
+ name: 'standard-app',
+ description: 'Standard App: 1 main bundle (1 MB) + 20 chunks (50 KB), 8 locales, sourcemaps ON',
+ get inputSizeBytes() {
+ return workload?.totalInputSizeBytes ?? 0;
+ },
+ get localeCount() {
+ return DEFAULT_LOCALES_8.length;
+ },
+ async setup() {
+ await initializeFixtures();
+ const files = createBundleSet(1024 * 1024, 20, 50 * 1024, 1000, true);
+ const locales = generateTranslations(DEFAULT_LOCALES_8, 1000);
+ workload = {
+ files,
+ locales,
+ totalInputSizeBytes: calculateInputSizeBytes(files),
+ };
+ },
+ async run() {
+ if (!workload) {
+ return;
+ }
+ const inliner = new I18nInliner({
+ missingTranslation: 'warning',
+ maxConcurrency: options.concurrency,
+ });
+ try {
+ await inliner.inlineAll(workload.files, workload.locales);
+ } finally {
+ await inliner.close();
+ }
+ },
+ };
+}
+
+/**
+ * 2. Standard App without Sourcemaps:
+ * Evaluates inlining without source map generation and remapping.
+ */
+export function createStandardAppNoMapsScenario(
+ options: ScenarioFactoryOptions = {},
+): BenchmarkScenario {
+ let workload: GeneratedWorkload | undefined;
+
+ return {
+ name: 'standard-app-no-maps',
+ description:
+ 'Standard App (No Maps): 1 main bundle (1 MB) + 20 chunks (50 KB), 8 locales, sourcemaps OFF',
+ get inputSizeBytes() {
+ return workload?.totalInputSizeBytes ?? 0;
+ },
+ get localeCount() {
+ return DEFAULT_LOCALES_8.length;
+ },
+ async setup() {
+ await initializeFixtures();
+ const files = createBundleSet(1024 * 1024, 20, 50 * 1024, 1000, false);
+ const locales = generateTranslations(DEFAULT_LOCALES_8, 1000);
+ workload = {
+ files,
+ locales,
+ totalInputSizeBytes: calculateInputSizeBytes(files),
+ };
+ },
+ async run() {
+ if (!workload) {
+ return;
+ }
+ const inliner = new I18nInliner({
+ missingTranslation: 'warning',
+ maxConcurrency: options.concurrency,
+ });
+ try {
+ await inliner.inlineAll(workload.files, workload.locales);
+ } finally {
+ await inliner.close();
+ }
+ },
+ };
+}
+
+/**
+ * 3. Enterprise Multilingual Scenario:
+ * 1 main bundle (2 MB) + 40 chunks (60 KB), 32 locales, 3,000 translations, sourcemaps ON.
+ * Exercises sliding window batching (4 windows of 8 locales) and memory scaling.
+ */
+export function createEnterpriseScenario(options: ScenarioFactoryOptions = {}): BenchmarkScenario {
+ let workload: GeneratedWorkload | undefined;
+
+ return {
+ name: 'enterprise-multilingual',
+ description: 'Enterprise: 1 main (2 MB) + 40 chunks (60 KB), 32 locales, sourcemaps ON',
+ get inputSizeBytes() {
+ return workload?.totalInputSizeBytes ?? 0;
+ },
+ get localeCount() {
+ return DEFAULT_LOCALES_32.length;
+ },
+ async setup() {
+ await initializeFixtures();
+ const files = createBundleSet(2 * 1024 * 1024, 40, 60 * 1024, 3000, true);
+ const locales = generateTranslations(DEFAULT_LOCALES_32, 3000);
+ workload = {
+ files,
+ locales,
+ totalInputSizeBytes: calculateInputSizeBytes(files),
+ };
+ },
+ async run() {
+ if (!workload) {
+ return;
+ }
+ const inliner = new I18nInliner({
+ missingTranslation: 'warning',
+ maxConcurrency: options.concurrency,
+ });
+ try {
+ await inliner.inlineAll(workload.files, workload.locales);
+ } finally {
+ await inliner.close();
+ }
+ },
+ };
+}
+
+/**
+ * 4. Monolithic Dominant Scenario:
+ * 1 dominant bundle (6 MB) + 5 small runtime chunks (30 KB), 8 locales, sourcemaps ON.
+ * Evaluates whether LPT + DOMINANT_FILE_RATIO sharding saturates workers efficiently.
+ */
+export function createMonolithicScenario(options: ScenarioFactoryOptions = {}): BenchmarkScenario {
+ let workload: GeneratedWorkload | undefined;
+
+ return {
+ name: 'monolithic-dominant',
+ description: 'Monolithic: 1 dominant bundle (6 MB) + 5 small chunks (30 KB), 8 locales',
+ get inputSizeBytes() {
+ return workload?.totalInputSizeBytes ?? 0;
+ },
+ get localeCount() {
+ return DEFAULT_LOCALES_8.length;
+ },
+ async setup() {
+ await initializeFixtures();
+ const files = createBundleSet(6 * 1024 * 1024, 5, 30 * 1024, 2000, true);
+ const locales = generateTranslations(DEFAULT_LOCALES_8, 2000);
+ workload = {
+ files,
+ locales,
+ totalInputSizeBytes: calculateInputSizeBytes(files),
+ };
+ },
+ async run() {
+ if (!workload) {
+ return;
+ }
+ const inliner = new I18nInliner({
+ missingTranslation: 'warning',
+ maxConcurrency: options.concurrency,
+ });
+ try {
+ await inliner.inlineAll(workload.files, workload.locales);
+ } finally {
+ await inliner.close();
+ }
+ },
+ };
+}
+
+/**
+ * Helper to prime the persistent cache in an isolated process.
+ */
+export async function primeCache(cacheDir: string, concurrency?: number): Promise {
+ await initializeFixtures();
+ const files = createBundleSet(1024 * 1024, 15, 50 * 1024, 1000, true);
+ const locales = generateTranslations(DEFAULT_LOCALES_8, 1000);
+ const primer = new I18nInliner({
+ missingTranslation: 'warning',
+ persistentCachePath: cacheDir,
+ maxConcurrency: concurrency,
+ });
+ await primer.inlineAll(files, locales);
+ await primer.close();
+}
+
+/**
+ * 5. Persistent Cache Warm Scenario:
+ * Evaluates throughput when 100% of transformed files and translations are pre-cached in LMDB.
+ */
+export function createPersistentCacheWarmScenario(
+ options: ScenarioFactoryOptions = {},
+): BenchmarkScenario {
+ let workload: GeneratedWorkload | undefined;
+ let cacheDir: string | undefined;
+
+ return {
+ name: 'persistent-cache-warm',
+ description:
+ 'Persistent Cache (Warm): 100% LMDB cache hits for transformed files & translations',
+ get inputSizeBytes() {
+ return workload?.totalInputSizeBytes ?? 0;
+ },
+ get localeCount() {
+ return DEFAULT_LOCALES_8.length;
+ },
+ async setup() {
+ await initializeFixtures();
+ cacheDir = await fs.mkdtemp(path.join(os.tmpdir(), 'angular-i18n-bench-cache-'));
+ const files = createBundleSet(1024 * 1024, 15, 50 * 1024, 1000, true);
+ const locales = generateTranslations(DEFAULT_LOCALES_8, 1000);
+ workload = {
+ files,
+ locales,
+ totalInputSizeBytes: calculateInputSizeBytes(files),
+ };
+
+ // Prime the persistent cache out-of-process so cold worker thread allocations
+ // do not inflate this process's RSS metrics.
+ const scenariosUrl = pathToFileURL(path.resolve(import.meta.dirname, './scenarios.mts')).href;
+ const primerCode =
+ `import { primeCache } from ${JSON.stringify(scenariosUrl)};\n` +
+ `await primeCache(${JSON.stringify(cacheDir)}, ${options.concurrency ?? 'undefined'});\n`;
+
+ const primerProc = spawnSync(
+ process.execPath,
+ [
+ '--no-warnings=ExperimentalWarning',
+ '--experimental-transform-types',
+ '--input-type=module',
+ '-e',
+ primerCode,
+ ],
+ { stdio: 'inherit' },
+ );
+
+ if (primerProc.status !== 0 || primerProc.error) {
+ throw new Error(
+ `Failed to prime cache for persistent-cache-warm scenario: ${primerProc.error?.message ?? primerProc.status}`,
+ );
+ }
+ },
+
+ async run() {
+ if (!workload || !cacheDir) {
+ return;
+ }
+ const inliner = new I18nInliner({
+ missingTranslation: 'warning',
+ persistentCachePath: cacheDir,
+ maxConcurrency: options.concurrency,
+ });
+ try {
+ await inliner.inlineAll(workload.files, workload.locales);
+ } finally {
+ await inliner.close();
+ }
+ },
+ async teardown() {
+ if (cacheDir) {
+ await fs.rm(cacheDir, { recursive: true, force: true }).catch(() => {});
+ }
+ },
+ };
+}
+
+/**
+ * 6. Large Enterprise (10k translations) Scenario:
+ * 1 main bundle (3 MB) + 100 chunks (50 KB), 32 locales, 10,000 translations, sourcemaps ON.
+ * Maximum scale stress test for binary translation tables, memory retention, and multi-locale windows.
+ */
+export function createLargeEnterpriseScenario(
+ options: ScenarioFactoryOptions = {},
+): BenchmarkScenario {
+ let workload: GeneratedWorkload | undefined;
+
+ return {
+ name: 'large-enterprise-10k',
+ description:
+ 'Large Enterprise (10k msgs): 1 main (3 MB) + 100 chunks (50 KB), 32 locales, 10,000 translations',
+ get inputSizeBytes() {
+ return workload?.totalInputSizeBytes ?? 0;
+ },
+ get localeCount() {
+ return DEFAULT_LOCALES_32.length;
+ },
+ async setup() {
+ await initializeFixtures();
+ const files = createBundleSet(3 * 1024 * 1024, 100, 50 * 1024, 10000, true);
+ const locales = generateTranslations(DEFAULT_LOCALES_32, 10000);
+
+ workload = {
+ files,
+ locales,
+ totalInputSizeBytes: calculateInputSizeBytes(files),
+ };
+ },
+ async run() {
+ if (!workload) {
+ return;
+ }
+ const inliner = new I18nInliner({
+ missingTranslation: 'warning',
+ maxConcurrency: options.concurrency,
+ });
+ try {
+ await inliner.inlineAll(workload.files, workload.locales);
+ } finally {
+ await inliner.close();
+ }
+ },
+ };
+}
+
+export function getAllScenarios(options: ScenarioFactoryOptions = {}): BenchmarkScenario[] {
+ return [
+ createStandardAppScenario(options),
+ createStandardAppNoMapsScenario(options),
+ createEnterpriseScenario(options),
+ createLargeEnterpriseScenario(options),
+ createMonolithicScenario(options),
+ createPersistentCacheWarmScenario(options),
+ ];
+}
+
+export function getScenarioByName(
+ name: string,
+ options: ScenarioFactoryOptions = {},
+): BenchmarkScenario | undefined {
+ const all = getAllScenarios(options);
+
+ return all.find((s) => s.name.toLowerCase() === name.toLowerCase());
+}
diff --git a/tests/e2e/tests/commands/config/config-global-validation.ts b/tests/e2e/tests/commands/config/config-global-validation.ts
index 7be29130dca0..39fa611075a9 100644
--- a/tests/e2e/tests/commands/config/config-global-validation.ts
+++ b/tests/e2e/tests/commands/config/config-global-validation.ts
@@ -9,21 +9,15 @@ export default async function () {
let ngError: Error;
ngError = await expectToFail(() => silentNg('config', 'cli.completion.prompted', 'true'));
- assert.match(
- ngError.message,
- /Data path "\/cli" must NOT have additional properties\(completion\)\./,
- );
+ assert.match(ngError.message, /Unknown option "completion" at "\/cli"\./);
ngError = await expectToFail(() =>
silentNg('config', '--global', 'cli.completion.invalid', 'true'),
);
- assert.match(
- ngError.message,
- /Data path "\/cli\/completion" must NOT have additional properties\(invalid\)\./,
- );
+ assert.match(ngError.message, /Unknown option "invalid" at "\/cli\/completion"\./);
ngError = await expectToFail(() => silentNg('config', '--global', 'cli.cache.enabled', 'true'));
- assert.match(ngError.message, /Data path "\/cli" must NOT have additional properties\(cache\)\./);
+ assert.match(ngError.message, /Unknown option "cache" at "\/cli"\./);
ngError = await expectToFail(() => silentNg('config', 'cli.completion.prompted'));
assert.match(ngError.message, /Value cannot be found\./);
diff --git a/tests/e2e/tests/commands/config/config-set.ts b/tests/e2e/tests/commands/config/config-set.ts
index 2152e573132e..05fa364bcb37 100644
--- a/tests/e2e/tests/commands/config/config-set.ts
+++ b/tests/e2e/tests/commands/config/config-set.ts
@@ -6,10 +6,7 @@ export default async function () {
let ngError: Error;
ngError = await expectToFail(() => silentNg('config', 'cli.warnings.zzzz', 'true'));
- assert.match(
- ngError.message,
- /Data path "\/cli\/warnings" must NOT have additional properties\(zzzz\)\./,
- );
+ assert.match(ngError.message, /Unknown option "zzzz" at "\/cli\/warnings"\./);
ngError = await expectToFail(() => silentNg('config', 'cli.warnings.zzzz'));
assert.match(ngError.message, /Value cannot be found\./);