diff --git a/packages/core/schematics/migrations/strict-safe-navigation-narrow/index.ts b/packages/core/schematics/migrations/strict-safe-navigation-narrow/index.ts index bf930593190..e351f70612d 100644 --- a/packages/core/schematics/migrations/strict-safe-navigation-narrow/index.ts +++ b/packages/core/schematics/migrations/strict-safe-navigation-narrow/index.ts @@ -12,7 +12,9 @@ import {JSONFile} from '@schematics/angular/utility/json-file'; export function migrate(): Rule { return async (tree: Tree) => { - const {buildPaths, testPaths} = await getProjectTsConfigPaths(tree); + const {buildPaths, testPaths} = await getProjectTsConfigPaths(tree, { + angularBuildersOnly: true, + }); const allPaths = [...new Set([...buildPaths, ...testPaths])]; for (const tsconfigPath of allPaths) { diff --git a/packages/core/schematics/migrations/strict-safe-navigation-narrow/migration.spec.ts b/packages/core/schematics/migrations/strict-safe-navigation-narrow/migration.spec.ts index 7218b2e024d..cb250b4e95f 100644 --- a/packages/core/schematics/migrations/strict-safe-navigation-narrow/migration.spec.ts +++ b/packages/core/schematics/migrations/strict-safe-navigation-narrow/migration.spec.ts @@ -28,6 +28,7 @@ describe('strict-safe-navigation-narrow migration', () => { root: '', architect: { build: { + builder: '@angular/build:application', options: { tsConfig: './tsconfig.json', }, @@ -138,4 +139,44 @@ describe('strict-safe-navigation-narrow migration', () => { tsconfig.angularCompilerOptions.extendedDiagnostics.checks.nullishCoalescingNotNullable, ).toBe('suppress'); }); + + it('should not migrate tsconfig files of projects that do not use an Angular builder', async () => { + tree.overwrite( + '/angular.json', + JSON.stringify({ + version: 1, + projects: { + 'angular-app': { + root: '', + architect: { + build: { + builder: '@angular/build:application', + options: {tsConfig: './tsconfig.app.json'}, + }, + }, + }, + 'node-lib': { + root: '', + architect: { + build: { + builder: '@nx/js:tsc', + options: {tsConfig: './tsconfig.lib.json'}, + }, + }, + }, + }, + }), + ); + tree.create('/tsconfig.app.json', JSON.stringify({compilerOptions: {target: 'es2020'}})); + tree.create('/tsconfig.lib.json', JSON.stringify({compilerOptions: {target: 'es2020'}})); + + const runMigration = migrate(); + await runMigration(tree, {} as any); + + const appTsconfig = parseConfig(tree, '/tsconfig.app.json'); + expect( + appTsconfig.angularCompilerOptions.extendedDiagnostics.checks.nullishCoalescingNotNullable, + ).toBe('suppress'); + expect(parseConfig(tree, '/tsconfig.lib.json').angularCompilerOptions).toBeUndefined(); + }); }); diff --git a/packages/core/schematics/migrations/strict-templates-default/index.ts b/packages/core/schematics/migrations/strict-templates-default/index.ts index 31a3ede31ef..1ceb8ee9248 100644 --- a/packages/core/schematics/migrations/strict-templates-default/index.ts +++ b/packages/core/schematics/migrations/strict-templates-default/index.ts @@ -44,7 +44,9 @@ function getResolvedAngularCompilerOptions(tree: any, tsconfigPath: string): Rec */ export function migrate(): Rule { return async (tree) => { - const {buildPaths, testPaths} = await getProjectTsConfigPaths(tree); + const {buildPaths, testPaths} = await getProjectTsConfigPaths(tree, { + angularBuildersOnly: true, + }); const allPaths = [...new Set([...buildPaths, ...testPaths])]; for (const tsconfigPath of allPaths) { diff --git a/packages/core/schematics/migrations/strict-templates-default/migration.spec.ts b/packages/core/schematics/migrations/strict-templates-default/migration.spec.ts index 6a942a24e9d..95210d2bae2 100644 --- a/packages/core/schematics/migrations/strict-templates-default/migration.spec.ts +++ b/packages/core/schematics/migrations/strict-templates-default/migration.spec.ts @@ -28,6 +28,7 @@ describe('strict-templates-default migration', () => { root: '', architect: { build: { + builder: '@angular/build:application', options: { tsConfig: './tsconfig.json', }, @@ -197,4 +198,41 @@ describe('strict-templates-default migration', () => { expect(baseTsconfig.angularCompilerOptions).toBeDefined(); expect(appTsconfig.angularCompilerOptions).toBeUndefined(); }); + + it('should not migrate tsconfig files of projects that do not use an Angular builder', async () => { + tree.overwrite( + '/angular.json', + JSON.stringify({ + version: 1, + projects: { + 'angular-app': { + root: '', + architect: { + build: { + builder: '@angular/build:application', + options: {tsConfig: './tsconfig.app.json'}, + }, + }, + }, + 'node-lib': { + root: '', + architect: { + build: { + builder: '@nx/js:tsc', + options: {tsConfig: './tsconfig.lib.json'}, + }, + }, + }, + }, + }), + ); + tree.create('/tsconfig.app.json', JSON.stringify({compilerOptions: {target: 'es2020'}})); + tree.create('/tsconfig.lib.json', JSON.stringify({compilerOptions: {target: 'es2020'}})); + + await migrate()(tree, {} as any); + + const appTsconfig = parseConfig(tree, '/tsconfig.app.json'); + expect(appTsconfig.angularCompilerOptions.strictTemplates).toBe(false); + expect(parseConfig(tree, '/tsconfig.lib.json').angularCompilerOptions).toBeUndefined(); + }); }); diff --git a/packages/core/schematics/test/project_tsconfig_paths_spec.ts b/packages/core/schematics/test/project_tsconfig_paths_spec.ts index b30a72da346..7b272b5b384 100644 --- a/packages/core/schematics/test/project_tsconfig_paths_spec.ts +++ b/packages/core/schematics/test/project_tsconfig_paths_spec.ts @@ -100,4 +100,59 @@ describe('project tsconfig paths', () => { expect((await getProjectTsConfigPaths(testTree)).buildPaths).toEqual(['tsconfig.json']); }); + + it('should only return tsconfig paths of Angular builders when `angularBuildersOnly` is set', async () => { + testTree.create('/tsconfig.app.json', ''); + testTree.create('/tsconfig.nx.json', ''); + testTree.create('/tsconfig.lib.json', ''); + testTree.create('/tsconfig.ngx-build-plus.json', ''); + testTree.create( + '/angular.json', + JSON.stringify({ + version: 1, + projects: { + angular_app: { + root: '', + architect: { + build: { + builder: '@angular/build:application', + options: {tsConfig: './tsconfig.app.json'}, + }, + }, + }, + nx_angular_app: { + root: '', + architect: { + build: { + builder: '@nx/angular:webpack-browser', + options: {tsConfig: './tsconfig.nx.json'}, + }, + }, + }, + node_lib: { + root: '', + architect: { + build: {builder: '@nx/js:tsc', options: {tsConfig: './tsconfig.lib.json'}}, + }, + }, + ngx_build_plus_app: { + root: '', + architect: { + build: { + builder: 'ngx-build-plus:browser', + options: {tsConfig: './tsconfig.ngx-build-plus.json'}, + }, + }, + }, + }, + }), + ); + + const {buildPaths} = await getProjectTsConfigPaths(testTree, {angularBuildersOnly: true}); + expect(buildPaths).toEqual([ + 'tsconfig.app.json', + 'tsconfig.nx.json', + 'tsconfig.ngx-build-plus.json', + ]); + }); }); diff --git a/packages/core/schematics/utils/project_tsconfig_paths.ts b/packages/core/schematics/utils/project_tsconfig_paths.ts index a195a0238a8..dda7e7cb3ed 100644 --- a/packages/core/schematics/utils/project_tsconfig_paths.ts +++ b/packages/core/schematics/utils/project_tsconfig_paths.ts @@ -12,9 +12,14 @@ import {Tree} from '@angular-devkit/schematics'; /** * Gets all tsconfig paths from a CLI project by reading the workspace configuration * and looking for common tsconfig locations. + * + * When `angularBuildersOnly` is set, only targets that use an Angular builder are considered. + * This avoids picking up tsconfig files of non-Angular projects in a mixed workspace (e.g. an Nx + * monorepo), which should not be touched by Angular migrations. */ export async function getProjectTsConfigPaths( tree: Tree, + {angularBuildersOnly = false}: {angularBuildersOnly?: boolean} = {}, ): Promise<{buildPaths: string[]; testPaths: string[]}> { // Start with some tsconfig paths that are generally used within CLI projects. Note // that we are not interested in IDE-specific tsconfig files (e.g. /tsconfig.json) @@ -24,6 +29,10 @@ export async function getProjectTsConfigPaths( const workspace = await getWorkspace(tree); for (const [, project] of workspace.projects) { for (const [name, target] of project.targets) { + if (angularBuildersOnly && !isAngularBuilder(target.builder)) { + continue; + } + for (const [, options] of allTargetOptions(target)) { const tsConfig = options['tsConfig']; // Filter out tsconfig files that don't exist in the CLI project. @@ -46,6 +55,22 @@ export async function getProjectTsConfigPaths( }; } +/** Prefixes of Angular builders, including common community builders (e.g. Nx). */ +const angularBuilderPrefixes = [ + '@angular-devkit/build-angular:', + '@angular/build:', + '@nx/angular:', + '@angular-builders/', + 'ngx-build-plus:', +]; + +/** Whether the given builder is a recognized Angular builder. */ +function isAngularBuilder(builder: string | undefined): boolean { + return ( + builder !== undefined && angularBuilderPrefixes.some((prefix) => builder.startsWith(prefix)) + ); +} + /** Get options for all configurations for the passed builder target. */ function* allTargetOptions( target: workspaces.TargetDefinition,