From 0234edc5e8a764c175db27970bde69efce5c3ed3 Mon Sep 17 00:00:00 2001 From: Alessio Pelliccione Date: Sat, 8 Nov 2025 16:56:19 +0100 Subject: [PATCH] refactor(cdk/schematics): remove TODO by centralizing stylesheet discovery --- .../ng-update/devkit-migration-rule.ts | 8 +- .../schematics/ng-update/find-stylesheets.ts | 117 ++++++++++++++++-- 2 files changed, 112 insertions(+), 13 deletions(-) diff --git a/src/cdk/schematics/ng-update/devkit-migration-rule.ts b/src/cdk/schematics/ng-update/devkit-migration-rule.ts index cfe30a630827..161941e3970d 100644 --- a/src/cdk/schematics/ng-update/devkit-migration-rule.ts +++ b/src/cdk/schematics/ng-update/devkit-migration-rule.ts @@ -100,11 +100,9 @@ export function createMigrationSchematicRule( } // In some applications, developers will have global stylesheets which are not - // specified in any Angular component. Therefore we glob up all CSS and SCSS files - // in the project and migrate them if needed. - // TODO: rework this to collect global stylesheets from the workspace config. - // TODO: https://github.com/angular/components/issues/24032. - const additionalStylesheetPaths = findStylesheetFiles(tree, project.root); + // specified in any Angular component. We consult the workspace config for each + // project and migrate only the explicitly configured global styles. + const additionalStylesheetPaths = findStylesheetFiles(tree, project); if (buildTsconfigPath !== null) { runMigrations(project, projectName, buildTsconfigPath, additionalStylesheetPaths, false); diff --git a/src/cdk/schematics/ng-update/find-stylesheets.ts b/src/cdk/schematics/ng-update/find-stylesheets.ts index c06f738c192a..a2dd8a5cd079 100644 --- a/src/cdk/schematics/ng-update/find-stylesheets.ts +++ b/src/cdk/schematics/ng-update/find-stylesheets.ts @@ -6,20 +6,111 @@ * found in the LICENSE file at https://angular.dev/license */ -import {join, Path} from '@angular-devkit/core'; +import {join, normalize, Path} from '@angular-devkit/core'; import {Tree} from '@angular-devkit/schematics'; +import {ProjectDefinition} from '@schematics/angular/utility'; /** Regular expression that matches stylesheet paths */ const STYLESHEET_REGEX = /.*\.(css|scss)$/; /** - * Finds stylesheets in the given directory from within the specified tree. - * @param tree Devkit tree where stylesheet files can be found in. - * @param startDirectory Optional start directory where stylesheets should be searched in. - * This can be useful if only stylesheets within a given folder are relevant (to avoid - * unnecessary iterations). + * Finds stylesheets referenced by the workspace configuration. Falls back to scanning a + * directory tree when a specific project definition is not provided. + * + * @param tree Virtual file system tree used by schematics. + * @param projectOrDirectory Project definition to inspect or a path to scan recursively. + */ +export function findStylesheetFiles( + tree: Tree, + projectOrDirectory: ProjectDefinition | string = '/', +): string[] { + if (isProjectDefinition(projectOrDirectory)) { + return findFromWorkspaceProject(tree, projectOrDirectory); + } + + return findByTraversal(tree, projectOrDirectory); +} + +/** + * Collects all styles declared inside the workspace configuration for a given project. + * + * @param tree Virtual file system tree used for file existence checks. + * @param project Angular workspace project whose styles should be gathered. + */ +function findFromWorkspaceProject(tree: Tree, project: ProjectDefinition): string[] { + const styles = new Set(); + + project.targets?.forEach(target => { + collectFromTarget(target?.options, tree, styles); + + if (target?.configurations) { + Object.values(target.configurations).forEach(config => + collectFromTarget(config, tree, styles), + ); + } + }); + + return Array.from(styles); +} + +/** + * Extracts stylesheet entries from a target/options object and adds them to the accumulator. + * + * @param options Architect options object that may contain a `styles` array. + * @param tree Virtual file system tree used to validate style paths. + * @param styles Accumulator set that stores normalized stylesheet paths. + */ +function collectFromTarget( + options: Record | undefined, + tree: Tree, + styles: Set, +) { + const optionStyles = options?.['styles']; + + if (!Array.isArray(optionStyles)) { + return; + } + + optionStyles.forEach(entry => { + const stylesheetPath = coerceStylesheetPath(entry); + + if (!stylesheetPath || !STYLESHEET_REGEX.test(stylesheetPath)) { + return; + } + + const normalizedPath = normalize(stylesheetPath); + + if (tree.exists(normalizedPath)) { + styles.add(normalizedPath); + } + }); +} + +/** + * Normalizes a `styles` entry into a plain path string. + * + * @param value Value from the `styles` array (string or object with `input`). */ -export function findStylesheetFiles(tree: Tree, startDirectory: string = '/'): string[] { +function coerceStylesheetPath(value: unknown): string | null { + if (typeof value === 'string') { + return value; + } + + if (value && typeof value === 'object') { + const input = (value as Record)['input']; + return typeof input === 'string' ? input : null; + } + + return null; +} + +/** + * Traverses directories starting from the provided path and gathers stylesheet files. + * + * @param tree Virtual file system tree used for directory traversal. + * @param startDirectory Directory path whose subtree should be searched. + */ +function findByTraversal(tree: Tree, startDirectory: string): string[] { const result: string[] = []; const visitDir = (dirPath: Path) => { const {subfiles, subdirs} = tree.getDir(dirPath); @@ -38,6 +129,16 @@ export function findStylesheetFiles(tree: Tree, startDirectory: string = '/'): s } }); }; - visitDir(startDirectory as Path); + + visitDir((startDirectory || '/') as Path); return result; } + +/** + * Type guard that checks whether the provided value is a `ProjectDefinition`. + * + * @param value Unknown value to test. + */ +function isProjectDefinition(value: unknown): value is ProjectDefinition { + return !!value && typeof value === 'object' && 'targets' in (value as ProjectDefinition); +}