diff --git a/packages/schematics/angular/migrations/use-application-builder/migration.ts b/packages/schematics/angular/migrations/use-application-builder/migration.ts index 28fd2c3797bc..c66908231a05 100644 --- a/packages/schematics/angular/migrations/use-application-builder/migration.ts +++ b/packages/schematics/angular/migrations/use-application-builder/migration.ts @@ -245,17 +245,58 @@ function updateProjects(tree: Tree, context: SchematicContext) { rules.push( addDependency('@angular/build', latestVersions.DevkitBuildAngular, { type: DependencyType.Dev, + // Always is set here since removePackageJsonDependency below does not automatically + // trigger the package manager execution. install: InstallBehavior.Always, existing: ExistingBehavior.Replace, }), ); removePackageJsonDependency(tree, '@angular-devkit/build-angular'); + + // Add less dependency if any projects contain a Less stylesheet file. + // This check does not consider Node.js packages due to the performance + // cost of searching such a large directory structure. A build time error + // will provide instructions to install the package in this case. + if (hasLessStylesheets(tree)) { + rules.push( + addDependency('less', latestVersions['less'], { + type: DependencyType.Dev, + existing: ExistingBehavior.Skip, + }), + ); + } } return chain(rules); }); } +/** + * Searches the schematic tree for files that have a `.less` extension. + * + * @param tree A Schematics tree instance to search + * @returns true if Less stylesheet files are found; otherwise, false + */ +function hasLessStylesheets(tree: Tree) { + const directories = [tree.getDir('/')]; + + let current; + while ((current = directories.pop())) { + for (const path of current.subfiles) { + if (path.endsWith('.less')) { + return true; + } + } + + for (const path of current.subdirs) { + if (path === 'node_modules' || path.startsWith('.')) { + continue; + } + directories.push(current.dir(path)); + } + } +} + function* visit( directory: DirEntry, ): IterableIterator<[fileName: string, contents: string, sass: boolean]> { diff --git a/packages/schematics/angular/migrations/use-application-builder/migration_spec.ts b/packages/schematics/angular/migrations/use-application-builder/migration_spec.ts index ff0a9b6e541e..d0d20348e600 100644 --- a/packages/schematics/angular/migrations/use-application-builder/migration_spec.ts +++ b/packages/schematics/angular/migrations/use-application-builder/migration_spec.ts @@ -267,4 +267,22 @@ describe(`Migration to use the application builder`, () => { const { stylePreprocessorOptions } = app.architect['build'].options; expect(stylePreprocessorOptions).toEqual({ includePaths: ['.'] }); }); + + it('should add "less" dependency when converting to "@angular/build" and a ".less" file is present', async () => { + tree.create('/project/app/src/styles.less', ''); + + const newTree = await schematicRunner.runSchematic(schematicName, {}, tree); + + const { devDependencies } = JSON.parse(newTree.readContent('/package.json')); + + expect(devDependencies['less']).toBeDefined(); + }); + + it('should not add "less" dependency when converting to "@angular/build" and a ".less" file is present', async () => { + const newTree = await schematicRunner.runSchematic(schematicName, {}, tree); + + const { devDependencies } = JSON.parse(newTree.readContent('/package.json')); + + expect(devDependencies['less']).toBeUndefined(); + }); }); diff --git a/packages/schematics/angular/utility/latest-versions/package.json b/packages/schematics/angular/utility/latest-versions/package.json index de3e2acf0070..f9b29642dce8 100644 --- a/packages/schematics/angular/utility/latest-versions/package.json +++ b/packages/schematics/angular/utility/latest-versions/package.json @@ -16,6 +16,7 @@ "karma-jasmine-html-reporter": "~2.1.0", "karma-jasmine": "~5.1.0", "karma": "~6.4.0", + "less": "^4.2.0", "ng-packagr": "^18.0.0-next.0", "protractor": "~7.0.0", "rxjs": "~7.8.0",