diff --git a/packages/schematics/angular/migrations/migration-collection.json b/packages/schematics/angular/migrations/migration-collection.json index 502d121784fd..72d2cdef4030 100644 --- a/packages/schematics/angular/migrations/migration-collection.json +++ b/packages/schematics/angular/migrations/migration-collection.json @@ -13,16 +13,6 @@ "version": "21.0.0", "factory": "./karma/migration", "description": "Remove any karma configuration files that only contain the default content. The default configuration is automatically available without a specific project file." - }, - "update-typescript-lib": { - "version": "21.0.0", - "factory": "./update-typescript-lib/migration", - "description": "Updates the 'lib' property in tsconfig files to use 'es2022' or a more modern version." - }, - "update-module-resolution": { - "version": "21.0.0", - "factory": "./update-module-resolution/migration", - "description": "Update 'moduleResolution' to 'bundler' in TypeScript configurations. You can read more about this, here: https://www.typescriptlang.org/tsconfig/#moduleResolution" } } } diff --git a/packages/schematics/angular/migrations/update-module-resolution/migration.ts b/packages/schematics/angular/migrations/update-module-resolution/migration.ts deleted file mode 100644 index ca0419a4eeab..000000000000 --- a/packages/schematics/angular/migrations/update-module-resolution/migration.ts +++ /dev/null @@ -1,60 +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 { JsonObject } from '@angular-devkit/core'; -import { Rule, Tree } from '@angular-devkit/schematics'; -import { JSONFile } from '../../utility/json-file'; -import { allTargetOptions, allWorkspaceTargets, getWorkspace } from '../../utility/workspace'; - -export default function (): Rule { - return async (host) => { - const uniqueTsConfigs = new Set(); - - if (host.exists('tsconfig.json')) { - // Workspace level tsconfig - uniqueTsConfigs.add('tsconfig.json'); - } - - const workspace = await getWorkspace(host); - for (const [, target] of allWorkspaceTargets(workspace)) { - for (const [, opt] of allTargetOptions(target)) { - if (typeof opt?.tsConfig === 'string') { - uniqueTsConfigs.add(opt.tsConfig); - } - } - } - - for (const tsConfig of uniqueTsConfigs) { - if (host.exists(tsConfig)) { - updateModuleResolution(host, tsConfig); - } - } - }; -} - -function updateModuleResolution(host: Tree, tsConfigPath: string): void { - const json = new JSONFile(host, tsConfigPath); - const jsonPath = ['compilerOptions']; - const compilerOptions = json.get(jsonPath); - - if (compilerOptions && typeof compilerOptions === 'object') { - const { moduleResolution, module } = compilerOptions as JsonObject; - if (typeof moduleResolution !== 'string' || moduleResolution.toLowerCase() === 'bundler') { - return; - } - - if (typeof module === 'string' && module.toLowerCase() === 'preserve') { - return; - } - - json.modify(jsonPath, { - ...compilerOptions, - 'moduleResolution': 'bundler', - }); - } -} diff --git a/packages/schematics/angular/migrations/update-module-resolution/migration_spec.ts b/packages/schematics/angular/migrations/update-module-resolution/migration_spec.ts deleted file mode 100644 index 53448e80b66a..000000000000 --- a/packages/schematics/angular/migrations/update-module-resolution/migration_spec.ts +++ /dev/null @@ -1,118 +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 { isJsonObject } from '@angular-devkit/core'; -import { EmptyTree } from '@angular-devkit/schematics'; -import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; -import { Builders, ProjectType, WorkspaceSchema } from '../../utility/workspace-models'; - -describe('Migration to update moduleResolution', () => { - const schematicName = 'update-module-resolution'; - const schematicRunner = new SchematicTestRunner( - 'migrations', - require.resolve('../migration-collection.json'), - ); - - function createJsonFile(tree: UnitTestTree, filePath: string, content: {}): void { - const stringifiedContent = JSON.stringify(content, undefined, 2); - if (tree.exists(filePath)) { - tree.overwrite(filePath, stringifiedContent); - } else { - tree.create(filePath, stringifiedContent); - } - } - - function getCompilerOptionsValue(tree: UnitTestTree, filePath: string): Record { - const json = tree.readJson(filePath); - if (isJsonObject(json) && isJsonObject(json.compilerOptions)) { - return json.compilerOptions; - } - - throw new Error(`Cannot retrieve 'compilerOptions'.`); - } - - const angularConfig: WorkspaceSchema = { - version: 1, - projects: { - app: { - root: '', - sourceRoot: 'src', - projectType: ProjectType.Application, - prefix: 'app', - architect: { - build: { - builder: Builders.Browser, - options: { - tsConfig: 'src/tsconfig.app.json', - main: '', - polyfills: '', - }, - configurations: { - production: { - tsConfig: 'src/tsconfig.app.prod.json', - }, - }, - }, - test: { - builder: Builders.Karma, - options: { - karmaConfig: '', - tsConfig: 'src/tsconfig.spec.json', - }, - }, - }, - }, - }, - }; - - let tree: UnitTestTree; - beforeEach(() => { - tree = new UnitTestTree(new EmptyTree()); - const compilerOptions = { module: 'es2020', moduleResolution: 'node' }; - const configWithExtends = { extends: './tsconfig.json', compilerOptions }; - - // Workspace - createJsonFile(tree, 'angular.json', angularConfig); - createJsonFile(tree, 'tsconfig.json', { compilerOptions }); - - // Application - createJsonFile(tree, 'src/tsconfig.app.json', configWithExtends); - createJsonFile(tree, 'src/tsconfig.app.prod.json', configWithExtends); - createJsonFile(tree, 'src/tsconfig.spec.json', { compilerOptions }); - }); - - it(`should update moduleResolution to 'bundler' in workspace 'tsconfig.json'`, async () => { - const newTree = await schematicRunner.runSchematic(schematicName, {}, tree); - const compilerOptions = getCompilerOptionsValue(newTree, 'tsconfig.json'); - expect(compilerOptions).toEqual( - jasmine.objectContaining({ - moduleResolution: 'bundler', - }), - ); - }); - - it(`should update moduleResolution to 'bundler' in builder tsconfig`, async () => { - const newTree = await schematicRunner.runSchematic(schematicName, {}, tree); - const compilerOptions = getCompilerOptionsValue(newTree, 'src/tsconfig.spec.json'); - expect(compilerOptions).toEqual( - jasmine.objectContaining({ - moduleResolution: 'bundler', - }), - ); - }); - - it('should not update moduleResolution when module is preserve', async () => { - createJsonFile(tree, 'tsconfig.json', { - compilerOptions: { module: 'preserve', moduleResolution: 'node' }, - }); - - const newTree = await schematicRunner.runSchematic(schematicName, {}, tree); - const compilerOptions = getCompilerOptionsValue(newTree, 'tsconfig.json'); - expect(compilerOptions).toEqual({ module: 'preserve', moduleResolution: 'node' }); - }); -}); diff --git a/packages/schematics/angular/migrations/update-typescript-lib/migration.ts b/packages/schematics/angular/migrations/update-typescript-lib/migration.ts deleted file mode 100644 index 9022bcedf578..000000000000 --- a/packages/schematics/angular/migrations/update-typescript-lib/migration.ts +++ /dev/null @@ -1,94 +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 { Rule, Tree } from '@angular-devkit/schematics'; -import { JSONFile } from '../../utility/json-file'; -import { getWorkspace } from '../../utility/workspace'; - -export default function (): Rule { - return async (host, context) => { - // Workspace level tsconfig - if (host.exists('tsconfig.json')) { - updateLib(host, 'tsconfig.json'); - } - - const workspace = await getWorkspace(host); - - // Find all tsconfig which are references used by builders - for (const [, project] of workspace.projects) { - for (const [targetName, target] of project.targets) { - if (!target.options) { - continue; - } - - // Update all other known CLI builders that use a tsconfig - const tsConfigs = [target.options, ...Object.values(target.configurations || {})] - .filter((opt) => typeof opt?.tsConfig === 'string') - .map((opt) => (opt as { tsConfig: string }).tsConfig); - - const uniqueTsConfigs = new Set(tsConfigs); - for (const tsConfig of uniqueTsConfigs) { - if (host.exists(tsConfig)) { - updateLib(host, tsConfig); - } else { - context.logger.warn( - `'${tsConfig}' referenced in the '${targetName}' target does not exist.`, - ); - } - } - } - } - }; -} - -function updateLib(host: Tree, tsConfigPath: string): void { - const json = new JSONFile(host, tsConfigPath); - const jsonPath = ['compilerOptions', 'lib']; - const lib = json.get(jsonPath) as string[] | undefined; - - if (!lib || !Array.isArray(lib)) { - return; - } - - const esLibs = lib.filter((l) => typeof l === 'string' && l.toLowerCase().startsWith('es')); - const hasDom = lib.some((l) => typeof l === 'string' && l.toLowerCase() === 'dom'); - - if (esLibs.length === 0) { - return; - } - - const esLibToVersion = new Map(); - for (const l of esLibs) { - const version = l.toLowerCase().match(/^es(next|(\d+))$/)?.[1]; - if (version) { - esLibToVersion.set(l, version === 'next' ? Infinity : Number(version)); - } - } - - if (esLibToVersion.size === 0) { - return; - } - - const latestEsLib = [...esLibToVersion.entries()].sort(([, v1], [, v2]) => v2 - v1)[0]; - const latestVersion = latestEsLib[1]; - - if (hasDom) { - if (latestVersion <= 2022) { - json.remove(jsonPath); - } - - return; - } - - // No 'dom' with 'es' libs, so update 'es' lib. - if (latestVersion < 2022) { - const newLibs = lib.filter((l) => !esLibToVersion.has(l)); - newLibs.push('es2022'); - json.modify(jsonPath, newLibs); - } -} diff --git a/packages/schematics/angular/migrations/update-typescript-lib/migration_spec.ts b/packages/schematics/angular/migrations/update-typescript-lib/migration_spec.ts deleted file mode 100644 index 6cbfe6ebb00a..000000000000 --- a/packages/schematics/angular/migrations/update-typescript-lib/migration_spec.ts +++ /dev/null @@ -1,159 +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 { isJsonObject } from '@angular-devkit/core'; -import { EmptyTree } from '@angular-devkit/schematics'; -import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; -import { Builders, ProjectType, WorkspaceSchema } from '../../utility/workspace-models'; - -describe('Migration to update TypeScript lib', () => { - const schematicName = 'update-typescript-lib'; - - const schematicRunner = new SchematicTestRunner( - 'migrations', - require.resolve('../migration-collection.json'), - ); - - function createJsonFile(tree: UnitTestTree, filePath: string, content: {}): void { - const stringifiedContent = JSON.stringify(content, undefined, 2); - if (tree.exists(filePath)) { - tree.overwrite(filePath, stringifiedContent); - } else { - tree.create(filePath, stringifiedContent); - } - } - - function getCompilerOptions(tree: UnitTestTree, filePath: string): Record { - const json = tree.readJson(filePath); - if (isJsonObject(json) && isJsonObject(json.compilerOptions)) { - return json.compilerOptions; - } - - throw new Error(`Cannot retrieve 'compilerOptions'.`); - } - - function createWorkSpaceConfig(tree: UnitTestTree) { - const angularConfig: WorkspaceSchema = { - version: 1, - projects: { - app: { - root: '', - sourceRoot: 'src', - projectType: ProjectType.Application, - prefix: 'app', - architect: { - build: { - builder: Builders.Browser, - options: { - tsConfig: 'src/tsconfig.app.json', - main: '', - polyfills: '', - }, - configurations: { - production: { - tsConfig: 'src/tsconfig.app.prod.json', - }, - }, - }, - test: { - builder: Builders.Karma, - options: { - karmaConfig: '', - tsConfig: 'src/tsconfig.spec.json', - }, - }, - }, - }, - }, - }; - - createJsonFile(tree, 'angular.json', angularConfig); - } - - let tree: UnitTestTree; - beforeEach(() => { - tree = new UnitTestTree(new EmptyTree()); - createWorkSpaceConfig(tree); - - // Create tsconfigs - const compilerOptions = { lib: ['es2020', 'dom'] }; - const configWithExtends = { extends: './tsconfig.json', compilerOptions }; - - // Workspace - createJsonFile(tree, 'tsconfig.json', { compilerOptions }); - - // Application - createJsonFile(tree, 'src/tsconfig.app.json', configWithExtends); - createJsonFile(tree, 'src/tsconfig.app.prod.json', configWithExtends); - createJsonFile(tree, 'src/tsconfig.spec.json', { compilerOptions }); - }); - - it(`should remove 'lib' when 'dom' is present and ES version is less than 2022`, async () => { - const newTree = await schematicRunner.runSchematic(schematicName, {}, tree); - const compilerOptions = getCompilerOptions(newTree, 'tsconfig.json'); - expect(compilerOptions.lib).toBeUndefined(); - }); - - it(`should remove 'lib' when 'dom' is present and ES version is 2022`, async () => { - createJsonFile(tree, 'tsconfig.json', { compilerOptions: { lib: ['es2022', 'dom'] } }); - const newTree = await schematicRunner.runSchematic(schematicName, {}, tree); - const compilerOptions = getCompilerOptions(newTree, 'tsconfig.json'); - expect(compilerOptions.lib).toBeUndefined(); - }); - - it(`should not remove 'lib' when 'dom' is present and ES version is 'esnext'`, async () => { - createJsonFile(tree, 'tsconfig.json', { compilerOptions: { lib: ['esnext', 'dom'] } }); - const newTree = await schematicRunner.runSchematic(schematicName, {}, tree); - const compilerOptions = getCompilerOptions(newTree, 'tsconfig.json'); - expect(compilerOptions.lib).toEqual(['esnext', 'dom']); - }); - - it(`should update 'lib' to 'es2022' when 'dom' is not present and ES version is less than 2022`, async () => { - createJsonFile(tree, 'tsconfig.json', { compilerOptions: { lib: ['es2020'] } }); - const newTree = await schematicRunner.runSchematic(schematicName, {}, tree); - const compilerOptions = getCompilerOptions(newTree, 'tsconfig.json'); - expect(compilerOptions.lib).toEqual(['es2022']); - }); - - it(`should not update 'lib' when 'dom' is not present and ES version is 2022`, async () => { - createJsonFile(tree, 'tsconfig.json', { compilerOptions: { lib: ['es2022'] } }); - const newTree = await schematicRunner.runSchematic(schematicName, {}, tree); - const compilerOptions = getCompilerOptions(newTree, 'tsconfig.json'); - expect(compilerOptions.lib).toEqual(['es2022']); - }); - - it(`should not update 'lib' when 'dom' is not present and ES version is 'esnext'`, async () => { - createJsonFile(tree, 'tsconfig.json', { compilerOptions: { lib: ['esnext'] } }); - const newTree = await schematicRunner.runSchematic(schematicName, {}, tree); - const compilerOptions = getCompilerOptions(newTree, 'tsconfig.json'); - expect(compilerOptions.lib).toEqual(['esnext']); - }); - - it('should not error when a tsconfig is not found', async () => { - tree.delete('src/tsconfig.spec.json'); - await schematicRunner.runSchematic(schematicName, {}, tree); - }); - - it('should not error when compilerOptions is not defined', async () => { - createJsonFile(tree, 'tsconfig.json', {}); - await schematicRunner.runSchematic(schematicName, {}, tree); - }); - - it(`should not error when 'lib' is not defined`, async () => { - createJsonFile(tree, 'tsconfig.json', { compilerOptions: {} }); - await schematicRunner.runSchematic(schematicName, {}, tree); - }); - - it(`should remove 'lib' from all tsconfigs`, async () => { - const newTree = await schematicRunner.runSchematic(schematicName, {}, tree); - expect(getCompilerOptions(newTree, 'tsconfig.json').lib).toBeUndefined(); - expect(getCompilerOptions(newTree, 'src/tsconfig.app.json').lib).toBeUndefined(); - expect(getCompilerOptions(newTree, 'src/tsconfig.app.prod.json').lib).toBeUndefined(); - expect(getCompilerOptions(newTree, 'src/tsconfig.spec.json').lib).toBeUndefined(); - }); -});