From 0035d82ac95ebbfec1a097b2845415d6374f231e Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Wed, 16 Sep 2026 12:02:29 +0000 Subject: [PATCH 01/13] release: cut the v22.2.0-rc.0 release --- CHANGELOG.md | 21 +++++++++++++++++++++ package.json | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) 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/package.json b/package.json index 69fd5f4f7f42..771cab189583 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": [ From deed5d892492c10f1bfd39883362787c84c4c431 Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Wed, 16 Sep 2026 14:55:35 -0400 Subject: [PATCH 02/13] test(@angular/build): add performance benchmark suite for i18n inliner Introduces an automated macro performance benchmark suite for the i18n inlining subsystem in @angular/build, executable via the benchmark command. The suite generates synthetic in-memory bundles, source maps, and translation catalogs to evaluate realistic workloads without checking large test fixtures into the repository. Benchmark scenarios cover standard applications with and without source maps, enterprise multilingual applications across 32 locales, monolithic bundles to verify 2D task sharding, and warm persistent cache throughput. Each scenario is executed in an isolated child process, and persistent cache priming is performed out-of-process to avoid cross-scenario memory contamination from the OS memory allocator. The harness collects high-precision timing, peak heap, peak RSS, and memory deltas with explicit garbage collection support. The runner is integrated into devkit-admin with support for baseline comparison, machine-readable JSON output, stale build detection, and automatic rebuilds. (cherry picked from commit 2a636a71efb4473f9b9ef27a16784a73b03c6eca) --- package.json | 1 + scripts/benchmark.mts | 115 ++++++++ scripts/benchmarks/i18n/fixtures.mts | 165 +++++++++++ scripts/benchmarks/i18n/harness.mts | 155 ++++++++++ scripts/benchmarks/i18n/index.mts | 164 +++++++++++ scripts/benchmarks/i18n/init-env.mts | 24 ++ scripts/benchmarks/i18n/reporters.mts | 159 ++++++++++ scripts/benchmarks/i18n/scenarios.mts | 404 ++++++++++++++++++++++++++ 8 files changed, 1187 insertions(+) create mode 100644 scripts/benchmark.mts create mode 100644 scripts/benchmarks/i18n/fixtures.mts create mode 100644 scripts/benchmarks/i18n/harness.mts create mode 100644 scripts/benchmarks/i18n/index.mts create mode 100644 scripts/benchmarks/i18n/init-env.mts create mode 100644 scripts/benchmarks/i18n/reporters.mts create mode 100644 scripts/benchmarks/i18n/scenarios.mts diff --git a/package.json b/package.json index 771cab189583..1cd8f7ceaac3 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/scripts/benchmark.mts b/scripts/benchmark.mts new file mode 100644 index 000000000000..2f1d13b8f13b --- /dev/null +++ b/scripts/benchmark.mts @@ -0,0 +1,115 @@ +/** + * @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 { 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; + 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 + --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 cliOptions: BenchmarkCliOptions = { + scenario: options.scenario, + iterations: options.iterations !== undefined ? Number(options.iterations) : undefined, + warmup: options.warmup !== undefined ? Number(options.warmup) : undefined, + concurrency: options.concurrency !== undefined ? Number(options.concurrency) : undefined, + json: Boolean(options.json), + saveBaseline: options.saveBaseline ?? options['save-baseline'], + compareBaseline: options.compareBaseline ?? options['compare-baseline'], + }; + + 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..16305b766bec --- /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 '../../../dist/@angular/build/src/tools/esbuild/bundler-files.d.ts'; +import type { LocaleInlineOptions } from '../../../dist/@angular/build/src/tools/esbuild/i18n-inliner.d.ts'; + +// 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..700199c0af0b --- /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; + + await scenario.setup?.(); + + try { + // 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; + } + finalHeap = memAfter.heapUsed; + + // Force GC immediately after iteration to clean main thread isolate + global.gc?.(); + } + + // 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..8b6503d60e83 --- /dev/null +++ b/scripts/benchmarks/i18n/index.mts @@ -0,0 +1,164 @@ +/** + * @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) { + // eslint-disable-next-line no-console + console.error(`Error running scenario ${scenario.name}:\n${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..679281e4dc18 --- /dev/null +++ b/scripts/benchmarks/i18n/init-env.mts @@ -0,0 +1,24 @@ +/** + * @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}:${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..f862e28c04f6 --- /dev/null +++ b/scripts/benchmarks/i18n/scenarios.mts @@ -0,0 +1,404 @@ +/** + * @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 type { BuildOutputFile } from '../../../dist/@angular/build/src/tools/esbuild/bundler-files.d.ts'; +import type { LocaleInlineOptions } from '../../../dist/@angular/build/src/tools/esbuild/i18n-inliner.d.ts'; +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('../../../dist/@angular/build/src/tools/esbuild/i18n-inliner.d.ts'); + +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); +} + +/** + * 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 primerCode = + `import { primeCache } from ${JSON.stringify(path.resolve(import.meta.dirname, './scenarios.mts'))};\n` + + `await primeCache(${JSON.stringify(cacheDir)}, ${options.concurrency ?? 'undefined'});\n`; + + spawnSync( + process.execPath, + ['--no-warnings=ExperimentalWarning', '--experimental-transform-types', '-e', primerCode], + { stdio: 'inherit' }, + ); + }, + + 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(() => {}); + } + }, + }; +} + +export function getAllScenarios(options: ScenarioFactoryOptions = {}): BenchmarkScenario[] { + return [ + createStandardAppScenario(options), + createStandardAppNoMapsScenario(options), + createEnterpriseScenario(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()); +} From 33e6d5feae386b76261117196ca8e3b6676c0dbc Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Wed, 16 Sep 2026 15:15:17 -0400 Subject: [PATCH 03/13] test(@angular/build): add large-enterprise-10k benchmark scenario Adds a large-scale stress test scenario to the i18n inliner benchmark suite with 10,000 translation messages across 32 locales. The scenario generates a 3 MB main bundle and 100 route chunks with source maps, evaluating binary translation catalog encoding, small file batching, and memory scaling under maximum enterprise workloads. (cherry picked from commit d285bd85214cedaa31d0bf949381dc5c06f4b164) --- scripts/benchmark.mts | 45 ++++++++++++-- scripts/benchmarks/i18n/fixtures.mts | 4 +- scripts/benchmarks/i18n/harness.mts | 6 +- scripts/benchmarks/i18n/index.mts | 7 ++- scripts/benchmarks/i18n/init-env.mts | 4 +- scripts/benchmarks/i18n/scenarios.mts | 84 +++++++++++++++++++++++++-- 6 files changed, 131 insertions(+), 19 deletions(-) diff --git a/scripts/benchmark.mts b/scripts/benchmark.mts index 2f1d13b8f13b..bf35b97808e3 100644 --- a/scripts/benchmark.mts +++ b/scripts/benchmark.mts @@ -7,6 +7,7 @@ */ import fs from 'node:fs'; +import path from 'node:path'; import { type BenchmarkCliOptions, runI18nBenchmarks } from './benchmarks/i18n/index.mts'; function checkBuildStatus(logger: Console): boolean { @@ -44,6 +45,8 @@ export default async function ( concurrency?: string | number; build?: boolean; json?: boolean; + inProcess?: boolean; + 'in-process'?: boolean; saveBaseline?: string; 'save-baseline'?: string; compareBaseline?: string; @@ -72,6 +75,7 @@ Options: --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 @@ -99,14 +103,45 @@ Options: 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: options.iterations !== undefined ? Number(options.iterations) : undefined, - warmup: options.warmup !== undefined ? Number(options.warmup) : undefined, - concurrency: options.concurrency !== undefined ? Number(options.concurrency) : undefined, + iterations, + warmup, + concurrency, json: Boolean(options.json), - saveBaseline: options.saveBaseline ?? options['save-baseline'], - compareBaseline: options.compareBaseline ?? options['compare-baseline'], + 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); diff --git a/scripts/benchmarks/i18n/fixtures.mts b/scripts/benchmarks/i18n/fixtures.mts index 16305b766bec..3d53c3dbff5b 100644 --- a/scripts/benchmarks/i18n/fixtures.mts +++ b/scripts/benchmarks/i18n/fixtures.mts @@ -13,8 +13,8 @@ import { createRequire } from 'node:module'; import path from 'node:path'; -import type { BuildOutputFile } from '../../../dist/@angular/build/src/tools/esbuild/bundler-files.d.ts'; -import type { LocaleInlineOptions } from '../../../dist/@angular/build/src/tools/esbuild/i18n-inliner.d.ts'; +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( diff --git a/scripts/benchmarks/i18n/harness.mts b/scripts/benchmarks/i18n/harness.mts index 700199c0af0b..caf7f0753d24 100644 --- a/scripts/benchmarks/i18n/harness.mts +++ b/scripts/benchmarks/i18n/harness.mts @@ -63,9 +63,9 @@ export async function runScenario( const warmup = options.warmup ?? 2; const iterations = options.iterations ?? 5; - await scenario.setup?.(); - try { + await scenario.setup?.(); + // Warmup phase for (let w = 0; w < warmup; w++) { // Force GC if available between warmups @@ -106,10 +106,10 @@ export async function runScenario( if (rssDelta > maxRssDeltaBytes) { maxRssDeltaBytes = rssDelta; } - finalHeap = memAfter.heapUsed; // Force GC immediately after iteration to clean main thread isolate global.gc?.(); + finalHeap = process.memoryUsage().heapUsed; } // Sort ascending for percentile computation diff --git a/scripts/benchmarks/i18n/index.mts b/scripts/benchmarks/i18n/index.mts index 8b6503d60e83..276671c193e0 100644 --- a/scripts/benchmarks/i18n/index.mts +++ b/scripts/benchmarks/i18n/index.mts @@ -82,9 +82,12 @@ export async function runI18nBenchmarks( } const proc = spawnSync(process.execPath, args, { encoding: 'utf-8' }); - if (proc.status !== 0) { + if (proc.status !== 0 || proc.error) { // eslint-disable-next-line no-console - console.error(`Error running scenario ${scenario.name}:\n${proc.stderr || proc.stdout}`); + console.error( + `Error running scenario ${scenario.name}:\n` + + (proc.error?.message ?? (proc.stderr || proc.stdout)), + ); return { results, exitCode: 1 }; } diff --git a/scripts/benchmarks/i18n/init-env.mts b/scripts/benchmarks/i18n/init-env.mts index 679281e4dc18..a1690cf576c8 100644 --- a/scripts/benchmarks/i18n/init-env.mts +++ b/scripts/benchmarks/i18n/init-env.mts @@ -17,7 +17,9 @@ const buildNodeModules = path.resolve( const currentPath = process.env.NODE_PATH ?? ''; if (!currentPath.includes(buildNodeModules)) { - process.env.NODE_PATH = currentPath ? `${buildNodeModules}:${currentPath}` : 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/scenarios.mts b/scripts/benchmarks/i18n/scenarios.mts index f862e28c04f6..c00f34ded8a2 100644 --- a/scripts/benchmarks/i18n/scenarios.mts +++ b/scripts/benchmarks/i18n/scenarios.mts @@ -13,9 +13,10 @@ 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 '../../../dist/@angular/build/src/tools/esbuild/bundler-files.d.ts'; -import type { LocaleInlineOptions } from '../../../dist/@angular/build/src/tools/esbuild/i18n-inliner.d.ts'; +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'; @@ -25,7 +26,7 @@ const requireFromBuild = createRequire( const { I18nInliner } = requireFromBuild( '../../../dist/@angular/build/src/tools/esbuild/i18n-inliner.js', -) as typeof import('../../../dist/@angular/build/src/tools/esbuild/i18n-inliner.d.ts'); +) as typeof import('../../../packages/angular/build/src/tools/esbuild/i18n-inliner.js'); export interface ScenarioFactoryOptions { concurrency?: number; @@ -124,6 +125,15 @@ function calculateInputSizeBytes(files: BuildOutputFile[]): number { }, 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. @@ -350,15 +360,28 @@ export function createPersistentCacheWarmScenario( // 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(path.resolve(import.meta.dirname, './scenarios.mts'))};\n` + + `import { primeCache } from ${JSON.stringify(scenariosUrl)};\n` + `await primeCache(${JSON.stringify(cacheDir)}, ${options.concurrency ?? 'undefined'});\n`; - spawnSync( + const primerProc = spawnSync( process.execPath, - ['--no-warnings=ExperimentalWarning', '--experimental-transform-types', '-e', primerCode], + [ + '--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() { @@ -384,11 +407,60 @@ export function createPersistentCacheWarmScenario( }; } +/** + * 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), ]; From 24dc029343b9034a7c550fb437dfba0a2e19bce6 Mon Sep 17 00:00:00 2001 From: Angular Robot Date: Wed, 16 Sep 2026 19:27:50 +0000 Subject: [PATCH 04/13] build: update cross-repo angular dependencies See associated pull request for more information. --- .../assistant-to-the-branch-manager.yml | 2 +- .github/workflows/ci.yml | 52 +-- .github/workflows/dev-infra.yml | 6 +- .github/workflows/perf.yml | 6 +- .github/workflows/pr.yml | 44 +-- MODULE.bazel | 6 +- MODULE.bazel.lock | 50 +-- modules/testing/builder/package.json | 2 +- package.json | 28 +- packages/angular/build/package.json | 2 +- packages/angular/ssr/package.json | 12 +- .../angular_devkit/build_angular/package.json | 2 +- packages/ngtools/webpack/package.json | 4 +- pnpm-lock.yaml | 325 +++++++++--------- 14 files changed, 262 insertions(+), 279 deletions(-) diff --git a/.github/workflows/assistant-to-the-branch-manager.yml b/.github/workflows/assistant-to-the-branch-manager.yml index 30585fa707fd..c767c09e4f07 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@837e71330341e19050ebc3808e02d7fb2d112be5 # main with: angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5fad471f1244..80027a577866 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@837e71330341e19050ebc3808e02d7fb2d112be5 # main - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main + uses: angular/dev-infra/github-actions/bazel/setup@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # main - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main + uses: angular/dev-infra/github-actions/bazel/setup@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # main - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main + uses: angular/dev-infra/github-actions/bazel/setup@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # main - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main + uses: angular/dev-infra/github-actions/bazel/setup@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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..4dc1c8378728 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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..84b5b8cfb21d 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # main - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main + uses: angular/dev-infra/github-actions/bazel/setup@837e71330341e19050ebc3808e02d7fb2d112be5 # 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..400910007dda 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@837e71330341e19050ebc3808e02d7fb2d112be5 # main - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main + uses: angular/dev-infra/github-actions/bazel/setup@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # main - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main + uses: angular/dev-infra/github-actions/bazel/setup@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # main - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main + uses: angular/dev-infra/github-actions/bazel/setup@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # main - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@183403ae13b785698eaf13c819dda55b9fed430b # main + uses: angular/dev-infra/github-actions/bazel/setup@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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/MODULE.bazel b/MODULE.bazel index 470c5e258947..d5d7e9df8670 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -19,21 +19,21 @@ bazel_dep(name = "aspect_rules_jasmine", version = "2.0.4") bazel_dep(name = "rules_angular") git_override( module_name = "rules_angular", - commit = "5fa856469c642490a6e381a05d7f58f60fad1913", + commit = "3f933b9dde5139e67575b6c2437c854986ecd10a", remote = "https://github.com/angular/rules_angular.git", ) bazel_dep(name = "devinfra") git_override( module_name = "devinfra", - commit = "183403ae13b785698eaf13c819dda55b9fed430b", + commit = "837e71330341e19050ebc3808e02d7fb2d112be5", 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..27af19eb5cb6 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -433,16 +433,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 +458,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 +476,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 +494,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 +512,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 +528,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 +544,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 +560,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 +576,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 +592,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 +608,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 +624,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/package.json b/package.json index 1cd8f7ceaac3..49ef7f8b1128 100644 --- a/package.json +++ b/package.json @@ -43,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#f166178ac5c37cd3aadab7b8cb7212f34d71b2a6", + "@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/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/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/pnpm-lock.yaml b/pnpm-lock.yaml index e4cb97b4c448..11feaf7d1151 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#f166178ac5c37cd3aadab7b8cb7212f34d71b2a6 + version: https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/f166178ac5c37cd3aadab7b8cb7212f34d71b2a6 '@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/f166178ac5c37cd3aadab7b8cb7212f34d71b2a6': + resolution: {gitHosted: true, integrity: sha512-5NrBeSt7Q//Qrxhy1OVNDNuGiZLdFOaIPMLtCgjypDsJc214fEI4xRKqN7zIpAQfMlZDoIFMh7vodM/uB9oq3A==, tarball: https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/f166178ac5c37cd3aadab7b8cb7212f34d71b2a6} + version: 0.0.0-cde7ad16c16f5c7dbd57b62e8b930443813484ec 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} @@ -6768,8 +6763,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: @@ -8794,9 +8789,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 +8818,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 +8853,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/f166178ac5c37cd3aadab7b8cb7212f34d71b2a6': 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) @@ -8956,40 +8948,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 +9038,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 +9081,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 @@ -15129,10 +15112,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 +15131,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 +16141,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 +16185,7 @@ snapshots: source-map-js: 1.2.1 optionalDependencies: '@parcel/watcher': 2.6.0 + optional: true sass@1.104.1: dependencies: @@ -17482,8 +17467,6 @@ snapshots: zod@3.25.76: {} - zod@4.5.4: {} - zod@4.6.5: {} zone.js@0.16.3: {} From 6ffd26791199ccd61ac55fdbd34a1149a1af9eed Mon Sep 17 00:00:00 2001 From: Angular Robot Date: Thu, 17 Sep 2026 06:47:34 +0000 Subject: [PATCH 05/13] build: update bazel dependencies See associated pull request for more information. --- MODULE.bazel | 6 +++--- MODULE.bazel.lock | 26 +++++++++++++++++--------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/MODULE.bazel b/MODULE.bazel index d5d7e9df8670..9c3fd0ab9019 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -10,11 +10,11 @@ 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( diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 27af19eb5cb6..31e442ccf216 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", @@ -19,7 +22,8 @@ "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 +44,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", @@ -86,7 +91,8 @@ "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 +106,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 +138,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", @@ -222,8 +230,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 +363,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 +376,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 } } }, From c76b2d58a9113f9a38ce6fb2f448b6dc61e4a965 Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Thu, 17 Sep 2026 10:19:13 -0400 Subject: [PATCH 06/13] refactor(@angular/build): cap default i18n inlining concurrency to 8 Translation inlining and sourcemap remapping are CPU- and memory-intensive operations. When Piscina is initialized without explicit maxThreads, it defaults to floor(cpus * 1.5), spawning 18 to 48 worker threads on high-core machines. This excessive concurrency causes severe thread oversubscription, memory allocator lock contention, and high-core performance degradation under Promise.all barriers. This change introduces maxInlinerWorkers in environment options defaulting to min(8, availableParallelism()) while preserving explicit overrides via NG_BUILD_MAX_WORKERS. Concurrency passed to I18nInliner in the application builder is bounded to maxInlinerWorkers, capping standalone workers and shared worker pools while honoring shared pool thread limits when lower. (cherry picked from commit 93c118edcc97e9179f1ad01bf9bac09eaffed3b5) --- .../build/src/builders/application/i18n.ts | 6 +- .../build/src/utils/environment-options.ts | 9 +++ .../src/utils/environment-options_spec.ts | 64 +++++++++++++++++++ 3 files changed, 77 insertions(+), 2 deletions(-) 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/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())); + }); +}); From 9c6037af8b16f6c44d16324e76c13356173a7a55 Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Thu, 17 Sep 2026 16:29:53 +0000 Subject: [PATCH 07/13] Revert "refactor(@angular/cli): import markdown files directly for command descriptions" This reverts commit 3d7f081dbc6cc0c25740bab70d49f21c7d178c3c. --- .../cli/src/command-builder/command-module.ts | 46 +++++++++---------- packages/angular/cli/src/commands/add/cli.ts | 5 +- .../angular/cli/src/commands/analytics/cli.ts | 6 +-- .../cli/src/commands/analytics/info/cli.ts | 1 + .../src/commands/analytics/settings/cli.ts | 2 + .../angular/cli/src/commands/build/cli.ts | 6 +-- .../cli/src/commands/cache/clean/cli.ts | 1 + .../angular/cli/src/commands/cache/cli.ts | 6 +-- .../cli/src/commands/cache/info/cli.ts | 1 + .../cli/src/commands/cache/settings/cli.ts | 2 + .../cli/src/commands/completion/cli.ts | 7 ++- .../angular/cli/src/commands/config/cli.ts | 6 +-- .../angular/cli/src/commands/deploy/cli.ts | 6 +-- packages/angular/cli/src/commands/e2e/cli.ts | 1 + .../cli/src/commands/extract-i18n/cli.ts | 1 + .../angular/cli/src/commands/generate/cli.ts | 1 + packages/angular/cli/src/commands/lint/cli.ts | 6 +-- .../cli/src/commands/make-this-awesome/cli.ts | 1 + packages/angular/cli/src/commands/mcp/cli.ts | 1 + packages/angular/cli/src/commands/new/cli.ts | 6 +-- packages/angular/cli/src/commands/run/cli.ts | 6 +-- .../angular/cli/src/commands/serve/cli.ts | 1 + packages/angular/cli/src/commands/test/cli.ts | 6 +-- .../angular/cli/src/commands/update/cli.ts | 5 +- .../angular/cli/src/commands/version/cli.ts | 1 + packages/angular/cli/src/typings.d.ts | 12 ----- .../cli/src/utilities/markdown-loader.ts | 35 -------------- 27 files changed, 59 insertions(+), 119 deletions(-) delete mode 100644 packages/angular/cli/src/typings.d.ts delete mode 100644 packages/angular/cli/src/utilities/markdown-loader.ts 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; - } - }; -} From cf21837ca0f35ff8e1866df5fc35437807e78d77 Mon Sep 17 00:00:00 2001 From: Angular Robot Date: Thu, 17 Sep 2026 15:30:26 +0000 Subject: [PATCH 08/13] build: update cross-repo angular dependencies See associated pull request for more information. --- .../assistant-to-the-branch-manager.yml | 2 +- .github/workflows/ci.yml | 52 ++++++++-------- .github/workflows/dev-infra.yml | 6 +- .github/workflows/perf.yml | 6 +- .github/workflows/pr.yml | 44 ++++++------- MODULE.bazel | 4 +- MODULE.bazel.lock | 6 +- package.json | 2 +- pnpm-lock.yaml | 61 ++----------------- 9 files changed, 66 insertions(+), 117 deletions(-) diff --git a/.github/workflows/assistant-to-the-branch-manager.yml b/.github/workflows/assistant-to-the-branch-manager.yml index c767c09e4f07..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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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 80027a577866..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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # main + uses: angular/dev-infra/github-actions/bazel/setup@99d57027286c71dda92d8b91a3607abcf92d8bbd # main - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # main + uses: angular/dev-infra/github-actions/bazel/setup@99d57027286c71dda92d8b91a3607abcf92d8bbd # main - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # main + uses: angular/dev-infra/github-actions/bazel/setup@99d57027286c71dda92d8b91a3607abcf92d8bbd # main - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # main + uses: angular/dev-infra/github-actions/bazel/setup@99d57027286c71dda92d8b91a3607abcf92d8bbd # main - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # main + uses: angular/dev-infra/github-actions/bazel/setup@99d57027286c71dda92d8b91a3607abcf92d8bbd # main - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # main + uses: angular/dev-infra/github-actions/bazel/setup@99d57027286c71dda92d8b91a3607abcf92d8bbd # main - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # main + uses: angular/dev-infra/github-actions/bazel/setup@99d57027286c71dda92d8b91a3607abcf92d8bbd # main - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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 4dc1c8378728..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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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 84b5b8cfb21d..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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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 400910007dda..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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # main + uses: angular/dev-infra/github-actions/bazel/setup@99d57027286c71dda92d8b91a3607abcf92d8bbd # main - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # main + uses: angular/dev-infra/github-actions/bazel/setup@99d57027286c71dda92d8b91a3607abcf92d8bbd # main - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # main + uses: angular/dev-infra/github-actions/bazel/setup@99d57027286c71dda92d8b91a3607abcf92d8bbd # main - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # main + uses: angular/dev-infra/github-actions/bazel/setup@99d57027286c71dda92d8b91a3607abcf92d8bbd # main - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # main + uses: angular/dev-infra/github-actions/bazel/setup@99d57027286c71dda92d8b91a3607abcf92d8bbd # main - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # 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@837e71330341e19050ebc3808e02d7fb2d112be5 # main + uses: angular/dev-infra/github-actions/bazel/setup@99d57027286c71dda92d8b91a3607abcf92d8bbd # main - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@837e71330341e19050ebc3808e02d7fb2d112be5 # 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/MODULE.bazel b/MODULE.bazel index 9c3fd0ab9019..595813a5abf1 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -19,14 +19,14 @@ bazel_dep(name = "aspect_rules_jasmine", version = "2.0.4") bazel_dep(name = "rules_angular") git_override( module_name = "rules_angular", - commit = "3f933b9dde5139e67575b6c2437c854986ecd10a", + commit = "7fdee07b9989b92b88f24c84471e01d6a0a9bc34", remote = "https://github.com/angular/rules_angular.git", ) bazel_dep(name = "devinfra") git_override( module_name = "devinfra", - commit = "837e71330341e19050ebc3808e02d7fb2d112be5", + commit = "99d57027286c71dda92d8b91a3607abcf92d8bbd", remote = "https://github.com/angular/dev-infra.git", ) diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 31e442ccf216..2f5985594341 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -21,7 +21,6 @@ "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.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", @@ -90,7 +89,6 @@ "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.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", @@ -211,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", diff --git a/package.json b/package.json index 49ef7f8b1128..331ab53b141f 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "@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#f166178ac5c37cd3aadab7b8cb7212f34d71b2a6", + "@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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 11feaf7d1151..c0c4d347a5ef 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -208,8 +208,8 @@ importers: 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#f166178ac5c37cd3aadab7b8cb7212f34d71b2a6 - version: https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/f166178ac5c37cd3aadab7b8cb7212f34d71b2a6 + 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-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)) @@ -1103,9 +1103,9 @@ 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/ng-dev@https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/f166178ac5c37cd3aadab7b8cb7212f34d71b2a6': - resolution: {gitHosted: true, integrity: sha512-5NrBeSt7Q//Qrxhy1OVNDNuGiZLdFOaIPMLtCgjypDsJc214fEI4xRKqN7zIpAQfMlZDoIFMh7vodM/uB9oq3A==, tarball: https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/f166178ac5c37cd3aadab7b8cb7212f34d71b2a6} - version: 0.0.0-cde7ad16c16f5c7dbd57b62e8b930443813484ec + '@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-rc.0': @@ -3265,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==} @@ -7941,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==} @@ -8893,7 +8869,7 @@ snapshots: rxjs: 7.8.2 tslib: 2.8.1 - '@angular/ng-dev@https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/f166178ac5c37cd3aadab7b8cb7212f34d71b2a6': + '@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) @@ -8911,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 @@ -11287,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': {} @@ -16452,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: {} From 96832f13ba9497df185a42a8b6d8d3de6b28b76a Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Thu, 17 Sep 2026 16:48:36 +0000 Subject: [PATCH 09/13] refactor(@angular/build): extract shared concurrency, watcher, and styling utilities (cherry picked from commit d0eb6b656ccb51565adb532370eada2e00182668) --- .../testing/builder/src/builder-harness.ts | 2 +- .../src/builders/application/build-action.ts | 75 +------ .../build/src/builders/application/options.ts | 36 +-- .../tests/behavior/build-errors_spec.ts | 4 +- .../esbuild/persistent-load-result-cache.ts | 26 +-- .../esbuild/stylesheets/bundle-options.ts | 3 +- .../stylesheets/css-resource-plugin.ts | 6 +- .../build/src/tools/esbuild/watcher.ts | 103 +++++++++ .../build/src/tools/esbuild/watcher_spec.ts | 51 +++++ .../angular/build/src/utils/concurrency.ts | 93 ++++++++ .../build/src/utils/concurrency_spec.ts | 212 ++++++++++++++++++ .../build/src/utils/postcss-configuration.ts | 32 ++- 12 files changed, 513 insertions(+), 130 deletions(-) create mode 100644 packages/angular/build/src/utils/concurrency.ts create mode 100644 packages/angular/build/src/utils/concurrency_spec.ts 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/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/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..424c520eab2d 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, timeout: 90_000 }, ); - }, 90_000); + }, 120_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..9923f08fea7f 100644 --- a/packages/angular/build/src/tools/esbuild/watcher.ts +++ b/packages/angular/build/src/tools/esbuild/watcher.ts @@ -11,6 +11,7 @@ import type * as Chokidar from 'chokidar'; 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 +48,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. */ 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/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 { From e31bd66f67fbb55d17997920634a10a51a4a7886 Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Tue, 15 Sep 2026 12:09:11 -0400 Subject: [PATCH 10/13] fix(@angular/build): support standard JavaScript MIME types and case insensitivity in auto-CSP Previously, isJavascriptMimeType() in auto-csp.ts only performed a case-sensitive check against 'text/javascript' on the slice prior to the first semicolon. This caused several issues: 1. Valid and common JavaScript MIME types specified in the HTML Living Standard such as application/javascript and text/ecmascript were not recognized as JavaScript. 2. Case differences (e.g. type="text/JavaScript" or type="Module") were not matched, despite HTML attribute matching and MIME types being ASCII case-insensitive. 3. Whitespace around the essence (e.g. type="text/javascript ; charset=utf-8" or type=" text/javascript") caused the strict equality check to fail. When a script tag with one of these valid types was not recognized, auto-csp bypassed dynamic script rewriting and emitted the script element as-is into index.html. Under the generated strict CSP, the browser would then block the script from executing. This change introduces JAVASCRIPT_MIME_TYPES containing all HTML-standard JavaScript MIME types, normalizes the essence (stripping parameters, trimming surrounding whitespace, and lowercasing), and updates shouldDynamicallyLoadScriptTagBasedOnType to support case-insensitive module types. (cherry picked from commit 8e1b20253a06ea6614bb27f70ea252a6e0e67e1a) --- .../build/src/utils/index-file/auto-csp.ts | 37 +++++- .../src/utils/index-file/auto-csp_spec.ts | 119 +++++++++++++++++- 2 files changed, 151 insertions(+), 5 deletions(-) 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(); + }); + }); }); From c0e6b3cf684b458c8ef1e0b64197303234e0492f Mon Sep 17 00:00:00 2001 From: rk Date: Fri, 18 Sep 2026 00:13:00 +0500 Subject: [PATCH 11/13] fix(@angular-devkit/core): name the unknown option in a schema validation error An unknown property in `angular.json` or in a builder's options reported `Data path "" must NOT have additional properties(allowedCommonJsDependencies).`, which names neither the object the property was found in nor what is valid there. It now reads `Unknown option "allowedCommonJsDependencies". Valid options are: assets, browser, ...`, with `at "/cli"` where the instance path is not empty. Errors that are not about an unknown option are unchanged. Listing the valid options needs ajv's `verbose` option, which is what puts `parentSchema` on an error. That is the schema the property was rejected by, so a `$ref` and a `oneOf` branch each list their own options; a schema that declares no `properties` at all stops after the option name rather than offering an empty list. (cherry picked from commit 8c438891c9c8be4e66a82148908343befbb462a2) --- .../core/src/json/schema/registry.ts | 31 ++++--- .../core/src/json/schema/registry_spec.ts | 91 +++++++++++++++++++ .../config/config-global-validation.ts | 12 +-- tests/e2e/tests/commands/config/config-set.ts | 5 +- 4 files changed, 114 insertions(+), 25 deletions(-) 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/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\./); From 4ebaa667bbcb569151434bebe3f27af94ed17c95 Mon Sep 17 00:00:00 2001 From: rk Date: Fri, 18 Sep 2026 15:09:17 +0500 Subject: [PATCH 12/13] fix(@schematics/angular): update @types/node to a version vitest 5 accepts `ng new --ssr` and `ng add @angular/ssr` fail to install their dependencies: the server schematic adds `@types/node@^20.17.19`, and vitest 5, which the same schematics now add, has `@types/node@^22.0.0 || >=24.0.0` as a peer. npm refuses the tree and the workspace is left without `node_modules`. `^22.12.0` is what this repository uses for `@types/node` itself, and Node 20 is already outside the `engines.node` range of the generated project. (cherry picked from commit 1627ccbc1f61cd0a9ff093bb23cefc9d90b338ba) --- .../schematics/angular/utility/latest-versions/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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", From ee3041325cd2400c26e239e64e14a0e314ab6031 Mon Sep 17 00:00:00 2001 From: Alan Agius Date: Fri, 18 Sep 2026 18:36:24 +0200 Subject: [PATCH 13/13] fix(@angular/build): ensure chokidar watcher is ready before returning When initializing chokidar.watch with ignoreInitial: true, files visited during the initial scan are treated as the initial baseline and do not emit change events. If createChokidarWatcher returns before the initial scan completes, subsequent file modifications made shortly after setup can be visited for the first time during the initial scan, causing the change event to be dropped. (cherry picked from commit dc79270a8d56a59b79cc2a44653885d5111b9ba7) --- .../dev-server/tests/behavior/build-errors_spec.ts | 4 ++-- packages/angular/build/src/tools/esbuild/watcher.ts | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) 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 424c520eab2d..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: 90_000 }, + { outputLogsOnFailure: false }, ); - }, 120_000); + }); }); }); diff --git a/packages/angular/build/src/tools/esbuild/watcher.ts b/packages/angular/build/src/tools/esbuild/watcher.ts index 9923f08fea7f..e30909fa6813 100644 --- a/packages/angular/build/src/tools/esbuild/watcher.ts +++ b/packages/angular/build/src/tools/esbuild/watcher.ts @@ -8,6 +8,7 @@ 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'; @@ -663,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);