diff --git a/packages/angular/cli/commands/update-impl.ts b/packages/angular/cli/commands/update-impl.ts index 44fec1059d8d..c0c461452bd0 100644 --- a/packages/angular/cli/commands/update-impl.ts +++ b/packages/angular/cli/commands/update-impl.ts @@ -607,36 +607,40 @@ export class UpdateCommand extends Command { return 1; } - if (node.package?.name === '@angular/cli') { - // Migrations for non LTS versions of Angular CLI are no longer included in @schematics/angular v12. + if (manifest.version === node.package?.version) { + this.logger.info(`Package '${packageName}' is already up to date.`); + continue; + } + + if (node.package && /^@(?:angular|nguniversal)\//.test(node.package.name)) { + const { name, version } = node.package; const toBeInstalledMajorVersion = +manifest.version.split('.')[0]; - const currentMajorVersion = +node.package.version.split('.')[0]; - if (currentMajorVersion < 10 && toBeInstalledMajorVersion >= 12) { - const updateVersions: Record = { - 1: 6, - 6: 7, - 7: 8, - 8: 9, - 9: 10, - }; - - const updateTo = updateVersions[currentMajorVersion]; - this.logger.error( - 'Updating multiple major versions at once is not recommended. ' + - `Run 'ng update @angular/cli@${updateTo}' in your workspace directory ` + - `to update to latest '${updateTo}.x' version of '@angular/cli'.\n\n` + - 'For more information about the update process, see https://update.angular.io/.', - ); + const currentMajorVersion = +version.split('.')[0]; + + if (toBeInstalledMajorVersion - currentMajorVersion > 1) { + // Only allow updating a single version at a time. + if (currentMajorVersion < 6) { + // Before version 6, the major versions were not always sequential. + // Example @angular/core skipped version 3, @angular/cli skipped versions 2-5. + this.logger.error( + `Updating multiple major versions of '${name}' at once is not supported. Please migrate each major version individually.\n` + + `For more information about the update process, see https://update.angular.io/.`, + ); + } else { + const nextMajorVersionFromCurrent = currentMajorVersion + 1; + + this.logger.error( + `Updating multiple major versions of '${name}' at once is not supported. Please migrate each major version individually.\n` + + `Run 'ng update ${name}@${nextMajorVersionFromCurrent}' in your workspace directory ` + + `to update to latest '${nextMajorVersionFromCurrent}.x' version of '${name}'.\n\n` + + `For more information about the update process, see https://update.angular.io/?v=${currentMajorVersion}.0-${nextMajorVersionFromCurrent}.0`, + ); + } return 1; } } - if (manifest.version === node.package?.version) { - this.logger.info(`Package '${packageName}' is already up to date.`); - continue; - } - packagesToUpdate.push(requestIdentifier.toString()); } diff --git a/packages/schematics/angular/migrations/migration-collection.json b/packages/schematics/angular/migrations/migration-collection.json index 1df6534c049d..7f7835399925 100644 --- a/packages/schematics/angular/migrations/migration-collection.json +++ b/packages/schematics/angular/migrations/migration-collection.json @@ -1,125 +1,5 @@ { "schematics": { - "lazy-loading-syntax": { - "version": "9.0.0-next.6", - "factory": "./update-12/update-lazy-module-paths", - "description": "Lazy loading syntax migration. Update lazy loading syntax to use dynamic imports." - }, - "tslint-version-6": { - "version": "10.0.0-beta.0", - "factory": "./update-10/update-tslint", - "description": "Update tslint to version 6 and adjust rules to maintain existing behavior." - }, - "rename-browserslist-config": { - "version": "10.0.0-beta.0", - "factory": "./update-10/rename-browserslist-config", - "description": "Update Browserslist configuration file name to '.browserslistrc' from deprecated 'browserslist'." - }, - "remove-es5-browser-support-option": { - "version": "10.0.0-beta.2", - "factory": "./update-10/remove-es5-browser-support", - "description": "Remove deprecated 'es5BrowserSupport' browser builder option. The inclusion for ES5 polyfills will be determined from the browsers listed in the browserslist configuration." - }, - "schematic-options-10": { - "version": "10.0.0-beta.2", - "factory": "./update-10/schematic-options", - "description": "Replace deprecated and removed 'styleext' and 'spec' Angular schematic options with 'style' and 'skipTests', respectively." - }, - "update-angular-config": { - "version": "10.0.0-beta.6", - "factory": "./update-10/update-angular-config", - "description": "Remove deprecated options from 'angular.json' that are no longer present in v10." - }, - "tslint-add-deprecation-rule": { - "version": "10.0.0-beta.7", - "factory": "./update-10/add-deprecation-rule-tslint", - "description": "Add the tslint deprecation rule to tslint JSON configuration files." - }, - "update-libraries-tslib": { - "version": "10.0.0-beta.7", - "factory": "./update-10/update-libraries-tslib", - "description": "Update library projects to use tslib version 2 as a direct dependency. Read more about this here: https://v10.angular.io/guide/migration-update-libraries-tslib" - }, - "update-workspace-dependencies": { - "version": "10.0.0-rc.2", - "factory": "./update-10/update-dependencies", - "description": "Update workspace dependencies to match a new v10 project." - }, - "update-module-and-target-compiler-options": { - "version": "10.0.1", - "factory": "./update-10/update-module-and-target-compiler-options", - "description": "Update 'module' and 'target' TypeScript compiler options. Read more about this here: https://v10.angular.io/guide/migration-update-module-and-target-compiler-options" - }, - "remove-solution-style-tsconfig": { - "version": "10.1.0-next.5", - "factory": "./update-10/remove-solution-style-tsconfig", - "description": "Removing \"Solution Style\" TypeScript configuration file support." - }, - "replace-ng-packagr-builder": { - "version": "11.0.0-next.0", - "factory": "./update-11/replace-ng-packagr-builder", - "description": "Replace deprecated library builder '@angular-devkit/build-ng-packagr'." - }, - "add-declaration-map-compiler-option": { - "version": "11.0.0-next.2", - "factory": "./update-11/add-declaration-map-compiler-option", - "description": "Add 'declarationMap' compiler options for non production library builds." - }, - "update-angular-config-v11": { - "version": "11.0.0-next.8", - "factory": "./update-11/update-angular-config", - "description": "Remove deprecated options from 'angular.json' that are no longer present in v11." - }, - "update-workspace-dependencies-v11": { - "version": "11.0.0", - "factory": "./update-11/update-dependencies", - "description": "Update workspace dependencies to match a new v11 project." - }, - "update-angular-config-v12": { - "version": "12.0.0-next.0", - "factory": "./update-12/update-angular-config", - "description": "Remove deprecated options from 'angular.json' that are no longer present in v12." - }, - "update-zonejs": { - "version": "12.0.0-next.1", - "factory": "./update-12/update-zonejs", - "description": "Update 'zone.js' to version 0.11.x. Read more about this here: https://github.com/angular/angular/blob/master/packages/zone.js/CHANGELOG.md#breaking-changes-since-zonejs-v0111" - }, - "remove-emit-decorator-metadata": { - "version": "12.0.0-next.2", - "factory": "./update-12/remove-emit-decorator-metadata", - "description": "Remove 'emitDecoratorMetadata' TypeScript compiler option. Decorator metadata is no longer needed by Angular. Read more about this here: https://www.typescriptlang.org/docs/handbook/decorators.html#metadata" - }, - "lazy-loading-string-syntax": { - "version": "12.0.0-next.4", - "factory": "./update-12/update-lazy-module-paths", - "description": "Lazy loading syntax migration. Update lazy loading string syntax to use dynamic imports." - }, - "remove-deprecated-i18n-options": { - "version": "12.0.0-next.7", - "factory": "./update-12/update-i18n", - "description": "Remove deprecated ViewEngine-based i18n build and extract options. Options present in the configuration will be converted to use non-deprecated options." - }, - "update-web-workers-webpack-5": { - "version": "12.0.0-next.7", - "factory": "./update-12/update-web-workers", - "description": "Updates Web Worker consumer usage to use the new syntax supported directly by Webpack 5." - }, - "schematic-options-12": { - "version": "12.0.1", - "factory": "./update-12/schematic-options", - "description": "Remove invalid 'skipTests' option in '@schematics/angular:module' Angular schematic options." - }, - "replace-deprecated-prod-flag": { - "version": "12.1.0", - "factory": "./update-12/replace-prod-flag", - "description": "Replace the deprecated '--prod' in package.json scripts." - }, - "production-by-default": { - "version": "9999.0.0", - "factory": "./update-12/production-default-config", - "description": "Optional migration to update Angular CLI workspace configurations to 'production' mode by default." - }, "schematic-options-13": { "version": "13.0.0", "factory": "./update-13/schematic-options", diff --git a/packages/schematics/angular/migrations/update-10/add-deprecation-rule-tslint.ts b/packages/schematics/angular/migrations/update-10/add-deprecation-rule-tslint.ts deleted file mode 100644 index b38af9a040da..000000000000 --- a/packages/schematics/angular/migrations/update-10/add-deprecation-rule-tslint.ts +++ /dev/null @@ -1,56 +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.io/license - */ - -import { JsonValue } from '@angular-devkit/core'; -import { Rule } from '@angular-devkit/schematics'; -import { getPackageJsonDependency } from '../../utility/dependencies'; -import { JSONFile } from '../../utility/json-file'; - -const TSLINT_CONFIG_PATH = '/tslint.json'; -const RULES_TO_ADD: Record = { - deprecation: { - severity: 'warning', - }, -}; - -export default function (): Rule { - return (tree, context) => { - const logger = context.logger; - - // Update tslint dependency - const current = getPackageJsonDependency(tree, 'tslint'); - - if (!current) { - logger.info('Skipping: "tslint" in not a dependency of this workspace.'); - - return; - } - - // Update tslint config. - let json; - try { - json = new JSONFile(tree, TSLINT_CONFIG_PATH); - } catch { - const config = ['tslint.js', 'tslint.yaml'].find((c) => tree.exists(c)); - if (config) { - logger.warn(`Expected a JSON configuration file but found "${config}".`); - } else { - logger.warn('Cannot find "tslint.json" configuration file.'); - } - - return; - } - - for (const [name, value] of Object.entries(RULES_TO_ADD)) { - const ruleName = ['rules', name]; - if (json.get(ruleName) === undefined) { - json.modify(ruleName, value); - } - } - }; -} diff --git a/packages/schematics/angular/migrations/update-10/add-deprecation-rule-tslint_spec.ts b/packages/schematics/angular/migrations/update-10/add-deprecation-rule-tslint_spec.ts deleted file mode 100644 index 35e36b502684..000000000000 --- a/packages/schematics/angular/migrations/update-10/add-deprecation-rule-tslint_spec.ts +++ /dev/null @@ -1,90 +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.io/license - */ - -import { EmptyTree } from '@angular-devkit/schematics'; -import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; - -describe('Migration of tslint to version 6', () => { - const schematicRunner = new SchematicTestRunner( - 'migrations', - require.resolve('../migration-collection.json'), - ); - - let tree: UnitTestTree; - const TSLINT_PATH = '/tslint.json'; - const PACKAGE_JSON_PATH = '/package.json'; - - const PACKAGE_JSON = { - devDependencies: { - tslint: '~6.1.0', - }, - }; - - beforeEach(() => { - tree = new UnitTestTree(new EmptyTree()); - tree.create(PACKAGE_JSON_PATH, JSON.stringify(PACKAGE_JSON, null, 2)); - }); - - it('should add new rules', async () => { - const config = { - extends: 'tslint:recommended', - rules: { - 'no-use-before-declare': true, - 'arrow-return-shorthand': false, - 'label-position': true, - }, - }; - tree.create(TSLINT_PATH, JSON.stringify(config, null, 2)); - - const newTree = await schematicRunner - .runSchematicAsync('tslint-add-deprecation-rule', {}, tree) - .toPromise(); - const { rules } = JSON.parse(newTree.readContent(TSLINT_PATH)); - expect(rules['deprecation'].severity).toBe('warning'); - }); - - it('should not update already present rules', async () => { - const config = { - extends: 'tslint:recommended', - rules: { - 'no-use-before-declare': true, - 'arrow-return-shorthand': false, - 'label-position': true, - deprecation: false, - }, - }; - tree.create(TSLINT_PATH, JSON.stringify(config, null, 2)); - - const newTree = await schematicRunner - .runSchematicAsync('tslint-add-deprecation-rule', {}, tree) - .toPromise(); - const { rules } = JSON.parse(newTree.readContent(TSLINT_PATH)); - expect(rules['deprecation']).toBe(false); - }); - - it('should not update already present rules with different severity', async () => { - const config = { - extends: 'tslint:recommended', - rules: { - 'no-use-before-declare': true, - 'arrow-return-shorthand': false, - 'label-position': true, - deprecation: { - severity: 'error', - }, - }, - }; - tree.create(TSLINT_PATH, JSON.stringify(config, null, 2)); - - const newTree = await schematicRunner - .runSchematicAsync('tslint-add-deprecation-rule', {}, tree) - .toPromise(); - const { rules } = JSON.parse(newTree.readContent(TSLINT_PATH)); - expect(rules['deprecation'].severity).toBe('error'); - }); -}); diff --git a/packages/schematics/angular/migrations/update-10/remove-es5-browser-support.ts b/packages/schematics/angular/migrations/update-10/remove-es5-browser-support.ts deleted file mode 100644 index 68c827d59df4..000000000000 --- a/packages/schematics/angular/migrations/update-10/remove-es5-browser-support.ts +++ /dev/null @@ -1,139 +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.io/license - */ - -import { Path, getSystemPath, logging, normalize, resolve, workspaces } from '@angular-devkit/core'; -import { Rule } from '@angular-devkit/schematics'; -import { getWorkspace, updateWorkspace } from '../../utility/workspace'; -import { Builders, ProjectType } from '../../utility/workspace-models'; - -export default function (): Rule { - return async (host, context) => { - const workspace = await getWorkspace(host); - - for (const [projectName, project] of workspace.projects) { - if (project.extensions.projectType !== ProjectType.Application) { - // Only interested in application projects - continue; - } - - for (const [, target] of project.targets) { - // Only interested in Angular Devkit Browser builder - if (target?.builder !== Builders.Browser) { - continue; - } - - const isES5Needed = await isES5SupportNeeded( - resolve(normalize(host.root.path), normalize(project.root)), - ); - - // Check options - if (target.options) { - target.options = removeE5BrowserSupportOption( - projectName, - target.options, - isES5Needed, - context.logger, - ); - } - - // Go through each configuration entry - if (!target.configurations) { - continue; - } - - for (const [configurationName, options] of Object.entries(target.configurations)) { - target.configurations[configurationName] = removeE5BrowserSupportOption( - projectName, - options, - isES5Needed, - context.logger, - configurationName, - ); - } - } - } - - return updateWorkspace(workspace); - }; -} - -type TargetOptions = workspaces.TargetDefinition['options']; - -function removeE5BrowserSupportOption( - projectName: string, - options: TargetOptions, - isES5Needed: boolean | undefined, - logger: logging.LoggerApi, - configurationName = '', -): TargetOptions { - if (typeof options?.es5BrowserSupport !== 'boolean') { - return options; - } - - const configurationPath = configurationName ? `configurations.${configurationName}.` : ''; - - if (options.es5BrowserSupport && isES5Needed === false) { - logger.warn( - `Project '${projectName}' doesn't require ES5 support, but '${configurationPath}es5BrowserSupport' was set to 'true'.\n` + - `ES5 polyfills will no longer be added when building this project${ - configurationName ? ` with '${configurationName}' configuration.` : '.' - }\n` + - `If ES5 polyfills are needed, add the supported ES5 browsers in the browserslist configuration.`, - ); - } else if (!options.es5BrowserSupport && isES5Needed === true) { - logger.warn( - `Project '${projectName}' requires ES5 support, but '${configurationPath}es5BrowserSupport' was set to 'false'.\n` + - `ES5 polyfills will be added when building this project${ - configurationName ? ` with '${configurationName}' configuration.` : '.' - }\n` + - `If ES5 polyfills are not needed, remove the unsupported ES5 browsers from the browserslist configuration.`, - ); - } - - return { - ...options, - es5BrowserSupport: undefined, - }; -} - -/** - * True, when one or more browsers requires ES5 support - */ -async function isES5SupportNeeded(projectRoot: Path): Promise { - // y: feature is fully available - // n: feature is unavailable - // a: feature is partially supported - // x: feature is prefixed - const criteria = ['y', 'a']; - - try { - // eslint-disable-next-line import/no-extraneous-dependencies - const browserslist = (await import('browserslist')).default; - const supportedBrowsers = browserslist(undefined, { - path: getSystemPath(projectRoot), - }); - - // eslint-disable-next-line import/no-extraneous-dependencies - const { feature, features } = await import('caniuse-lite'); - const data = feature(features['es6-module']); - - return supportedBrowsers.some((browser) => { - const [agentId, version] = browser.split(' '); - - const browserData = data.stats[agentId]; - const featureStatus = (browserData && browserData[version]) as string | undefined; - - // We are only interested in the first character - // Ex: when 'a #4 #5', we only need to check for 'a' - // as for such cases we should polyfill these features as needed - return !featureStatus || !criteria.includes(featureStatus.charAt(0)); - }); - } catch { - return undefined; - } -} diff --git a/packages/schematics/angular/migrations/update-10/remove-es5-browser-support_spec.ts b/packages/schematics/angular/migrations/update-10/remove-es5-browser-support_spec.ts deleted file mode 100644 index 02761956ec22..000000000000 --- a/packages/schematics/angular/migrations/update-10/remove-es5-browser-support_spec.ts +++ /dev/null @@ -1,100 +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.io/license - */ - -import { JsonObject } from '@angular-devkit/core'; -import { EmptyTree } from '@angular-devkit/schematics'; -import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; -import { - BuilderTarget, - Builders, - ProjectType, - WorkspaceSchema, -} from '../../utility/workspace-models'; - -function getBuildTarget(tree: UnitTestTree): BuilderTarget { - return JSON.parse(tree.readContent('/angular.json')).projects.app.architect.build; -} - -function createWorkSpaceConfig(tree: UnitTestTree, es5BrowserSupport: boolean | undefined) { - const angularConfig: WorkspaceSchema = { - version: 1, - projects: { - app: { - root: '', - sourceRoot: 'src', - projectType: ProjectType.Application, - prefix: 'app', - architect: { - build: { - builder: Builders.Browser, - options: { - es5BrowserSupport, - sourceMaps: true, - buildOptimizer: false, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any, - configurations: { - one: { - es5BrowserSupport, - vendorChunk: false, - buildOptimizer: true, - }, - two: { - es5BrowserSupport, - vendorChunk: false, - buildOptimizer: true, - sourceMaps: false, - }, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any, - }, - }, - }, - }, - }; - - tree.create('/angular.json', JSON.stringify(angularConfig, undefined, 2)); -} - -describe(`Migration to remove deprecated 'es5BrowserSupport' option`, () => { - const schematicName = 'remove-es5-browser-support-option'; - - const schematicRunner = new SchematicTestRunner( - 'migrations', - require.resolve('../migration-collection.json'), - ); - - let tree: UnitTestTree; - beforeEach(() => { - tree = new UnitTestTree(new EmptyTree()); - }); - - it(`should remove option when set to 'false'`, async () => { - createWorkSpaceConfig(tree, false); - - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - const { options, configurations } = getBuildTarget(newTree); - - expect(options.es5BrowserSupport).toBeUndefined(); - expect(configurations).toBeDefined(); - expect(configurations?.one.es5BrowserSupport).toBeUndefined(); - expect(configurations?.two.es5BrowserSupport).toBeUndefined(); - }); - - it(`should remove option and when set to 'true'`, async () => { - createWorkSpaceConfig(tree, true); - - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - const { options, configurations } = getBuildTarget(newTree); - - expect(options.es5BrowserSupport).toBeUndefined(); - expect(configurations).toBeDefined(); - expect(configurations?.one.es5BrowserSupport).toBeUndefined(); - expect(configurations?.two.es5BrowserSupport).toBeUndefined(); - }); -}); diff --git a/packages/schematics/angular/migrations/update-10/remove-solution-style-tsconfig.ts b/packages/schematics/angular/migrations/update-10/remove-solution-style-tsconfig.ts deleted file mode 100644 index f37c796441b3..000000000000 --- a/packages/schematics/angular/migrations/update-10/remove-solution-style-tsconfig.ts +++ /dev/null @@ -1,86 +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.io/license - */ - -import { Path, dirname, join, normalize, resolve } from '@angular-devkit/core'; -import { DirEntry, Rule } from '@angular-devkit/schematics'; -import { JSONFile } from '../../utility/json-file'; - -function* visitExtendedJsonFiles(directory: DirEntry): IterableIterator { - for (const path of directory.subfiles) { - if (!path.endsWith('.json')) { - continue; - } - - const entry = directory.file(path); - const content = entry?.content.toString(); - if (content?.includes('tsconfig.base.json')) { - yield join(directory.path, path); - } - } - - for (const path of directory.subdirs) { - if (path === 'node_modules' || path.startsWith('.')) { - continue; - } - - yield* visitExtendedJsonFiles(directory.dir(path)); - } -} - -export default function (): Rule { - return (host, context) => { - const logger = context.logger; - - const tsConfigExists = host.exists('tsconfig.json'); - if (tsConfigExists) { - const files = new JSONFile(host, 'tsconfig.json').get(['files']); - if (!(Array.isArray(files) && files.length === 0)) { - logger.info('Migration has already been executed.'); - - return; - } - } - - if (host.exists('tsconfig.base.json')) { - if (tsConfigExists) { - host.overwrite('tsconfig.json', host.read('tsconfig.base.json') || ''); - host.delete('tsconfig.base.json'); - } else { - host.rename('tsconfig.base.json', 'tsconfig.json'); - } - } - - // Iterate over all tsconfig files and change the extends from 'tsconfig.base.json' to 'tsconfig.json'. - const extendsJsonPath = ['extends']; - for (const path of visitExtendedJsonFiles(host.root)) { - try { - const tsConfigDir = dirname(normalize(path)); - const tsConfigJson = new JSONFile(host, path); - const extendsValue = tsConfigJson.get(extendsJsonPath); - - if ( - typeof extendsValue === 'string' && - '/tsconfig.base.json' === resolve(tsConfigDir, normalize(extendsValue)) - ) { - // tsconfig extends the workspace tsconfig path. - tsConfigJson.modify( - extendsJsonPath, - extendsValue.replace('tsconfig.base.json', 'tsconfig.json'), - ); - } - } catch (error) { - logger.warn( - `${error.message || error}\n` + - 'If this is a TypeScript configuration file you will need to update the "extends" value manually.', - ); - - continue; - } - } - }; -} diff --git a/packages/schematics/angular/migrations/update-10/remove-solution-style-tsconfig_spec.ts b/packages/schematics/angular/migrations/update-10/remove-solution-style-tsconfig_spec.ts deleted file mode 100644 index 7d3c58a786a2..000000000000 --- a/packages/schematics/angular/migrations/update-10/remove-solution-style-tsconfig_spec.ts +++ /dev/null @@ -1,144 +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.io/license - */ - -import { EmptyTree } from '@angular-devkit/schematics'; -import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; -import { parse as parseJson } from 'jsonc-parser'; -import { Builders, ProjectType, WorkspaceSchema } from '../../utility/workspace-models'; - -describe('Migration to remove "Solution Style" tsconfig', () => { - const schematicName = 'remove-solution-style-tsconfig'; - - const schematicRunner = new SchematicTestRunner( - 'migrations', - require.resolve('../migration-collection.json'), - ); - - function createJsonFile(tree: UnitTestTree, filePath: string, content: {}) { - tree.create(filePath, JSON.stringify(content, undefined, 2)); - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - function readJsonFile(tree: UnitTestTree, filePath: string): any { - return parseJson(tree.readContent(filePath).toString()); - } - - 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', - webWorkerTsConfig: 'src/tsconfig.worker.json', - main: '', - polyfills: '', - }, - }, - 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 = { target: 'es2015' }; - createJsonFile(tree, 'tsconfig.json', { files: [] }); - createJsonFile(tree, 'tsconfig.base.json', { compilerOptions }); - createJsonFile(tree, 'tsconfig.common.json', { compilerOptions }); - createJsonFile(tree, 'src/tsconfig.json', { - extends: './../tsconfig.base.json', - compilerOptions, - }); - createJsonFile(tree, 'src/tsconfig.base.json', { - extends: './../tsconfig.base.json', - compilerOptions, - }); - createJsonFile(tree, 'src/tsconfig.tsc.json', { - extends: './tsconfig.base.json', - compilerOptions, - }); - createJsonFile(tree, 'src/tsconfig.app.json', { - extends: './../tsconfig.common.json', - compilerOptions, - }); - createJsonFile(tree, 'src/tsconfig.spec.json', { - extends: './../tsconfig.base.json', - compilerOptions, - }); - createJsonFile(tree, 'src/tsconfig.worker.json', { - extends: './../tsconfig.base.json', - compilerOptions, - }); - }); - - it(`should rename 'tsconfig.base.json' to 'tsconfig.json'`, async () => { - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - expect(readJsonFile(newTree, 'tsconfig.json')['compilerOptions']).toBeTruthy(); - expect(newTree.exists('tsconfig.base.json')).toBeFalse(); - }); - - it(`should update extends from 'tsconfig.base.json' to 'tsconfig.json'`, async () => { - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - expect(readJsonFile(newTree, 'src/tsconfig.spec.json').extends).toEqual('./../tsconfig.json'); - expect(readJsonFile(newTree, 'src/tsconfig.worker.json').extends).toEqual('./../tsconfig.json'); - }); - - it('should not update extends if not extended the root tsconfig', async () => { - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - expect(readJsonFile(newTree, 'src/tsconfig.tsc.json').extends).toEqual('./tsconfig.base.json'); - }); - - it('should not error out when a JSON file is a blank', async () => { - tree.create('blank.json', ''); - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - expect(readJsonFile(newTree, 'src/tsconfig.json').extends).toEqual('./../tsconfig.json'); - }); - - it('should show warning with full path when parsing invalid JSON', async () => { - const logs: string[] = []; - schematicRunner.logger.subscribe((m) => logs.push(m.message)); - - tree.create('src/invalid/error.json', `{ "extends": "./../../tsconfig.base.json", invalid }`); - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - - expect(readJsonFile(newTree, 'src/tsconfig.spec.json').extends).toEqual('./../tsconfig.json'); - expect(logs.join('\n')).toContain( - 'Failed to parse "/src/invalid/error.json" as JSON AST Object. InvalidSymbol at location: 43.', - ); - }); - - it(`should not error when 'tsconfig.json' doesn't exist`, async () => { - tree.delete('tsconfig.json'); - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - - expect(readJsonFile(newTree, 'tsconfig.json')['compilerOptions']).toBeTruthy(); - expect(newTree.exists('tsconfig.base.json')).toBeFalse(); - }); -}); diff --git a/packages/schematics/angular/migrations/update-10/rename-browserslist-config.ts b/packages/schematics/angular/migrations/update-10/rename-browserslist-config.ts deleted file mode 100644 index 7f651b973d9f..000000000000 --- a/packages/schematics/angular/migrations/update-10/rename-browserslist-config.ts +++ /dev/null @@ -1,36 +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.io/license - */ - -import { join } from '@angular-devkit/core'; -import { DirEntry, Rule } from '@angular-devkit/schematics'; - -function* visit(directory: DirEntry): IterableIterator { - for (const path of directory.subfiles) { - if (path !== 'browserslist') { - continue; - } - - yield join(directory.path, path); - } - - for (const path of directory.subdirs) { - if (path === 'node_modules' || path.startsWith('.')) { - continue; - } - - yield* visit(directory.dir(path)); - } -} - -export default function (): Rule { - return (tree) => { - for (const path of visit(tree.root)) { - tree.rename(path, path.replace(/browserslist$/, '.browserslistrc')); - } - }; -} diff --git a/packages/schematics/angular/migrations/update-10/rename-browserslist-config_spec.ts b/packages/schematics/angular/migrations/update-10/rename-browserslist-config_spec.ts deleted file mode 100644 index 581a2f1071a8..000000000000 --- a/packages/schematics/angular/migrations/update-10/rename-browserslist-config_spec.ts +++ /dev/null @@ -1,51 +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.io/license - */ - -import { EmptyTree } from '@angular-devkit/schematics'; -import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; - -describe('Migration to rename Browserslist configurations', () => { - const schematicName = 'rename-browserslist-config'; - - const schematicRunner = new SchematicTestRunner( - 'migrations', - require.resolve('../migration-collection.json'), - ); - - let tree: UnitTestTree; - beforeEach(() => { - tree = new UnitTestTree(new EmptyTree()); - }); - - it(`should rename file 'browserslist' to 'browserslistrc'`, async () => { - tree.create('/browserslist', 'IE9'); - tree.create('/src/app/home/browserslist', 'IE9'); - - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - expect(newTree.exists('/.browserslistrc')).toBeTruthy(); - expect(newTree.exists('/browserslist')).toBeFalsy(); - - expect(newTree.exists('/src/app/home/.browserslistrc')).toBeTruthy(); - expect(newTree.exists('/src/app/home/browserslist')).toBeFalsy(); - }); - - it(`should not rename "browserslist" file in 'node_modules'`, async () => { - tree.create('/node_modules/browserslist', 'IE9'); - - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - expect(newTree.exists('/node_modules/browserslist')).toBeTruthy(); - expect(newTree.exists('/node_modules/.browserslistrc')).toBeFalsy(); - }); - - it(`should not rename a folder which is named 'browserslist'`, async () => { - tree.create('/app/browserslist/file.txt', 'content'); - - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - expect(newTree.exists('/app/browserslist/file.txt')).toBeTruthy(); - }); -}); diff --git a/packages/schematics/angular/migrations/update-10/schematic-options.ts b/packages/schematics/angular/migrations/update-10/schematic-options.ts deleted file mode 100644 index 56b62746f3dd..000000000000 --- a/packages/schematics/angular/migrations/update-10/schematic-options.ts +++ /dev/null @@ -1,54 +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.io/license - */ - -import { json } from '@angular-devkit/core'; -import { Rule } from '@angular-devkit/schematics'; -import { updateWorkspace } from '../../utility/workspace'; - -export default function (): Rule { - return updateWorkspace((workspace) => { - // Update root level schematics options if present - const rootSchematics = workspace.extensions.schematics; - if (rootSchematics && json.isJsonObject(rootSchematics)) { - updateSchematicsField(rootSchematics); - } - - // Update project level schematics options if present - for (const [, project] of workspace.projects) { - const projectSchematics = project.extensions.schematics; - if (projectSchematics && json.isJsonObject(projectSchematics)) { - updateSchematicsField(projectSchematics); - } - } - }); -} - -function updateSchematicsField(schematics: json.JsonObject): void { - for (const [schematicName, schematicOptions] of Object.entries(schematics)) { - if (!json.isJsonObject(schematicOptions)) { - continue; - } - - if (!schematicName.startsWith('@schematics/angular:')) { - continue; - } - - // Replace `styleext` with `style` - if (schematicOptions.styleext !== undefined) { - schematicOptions.style = schematicOptions.styleext; - delete schematicOptions.styleext; - } - - // Replace `spec` with `skipTests` - if (schematicOptions.spec !== undefined) { - // skipTests value is inverted - schematicOptions.skipTests = !schematicOptions.spec; - delete schematicOptions.spec; - } - } -} diff --git a/packages/schematics/angular/migrations/update-10/schematic-options_spec.ts b/packages/schematics/angular/migrations/update-10/schematic-options_spec.ts deleted file mode 100644 index 2837259769e5..000000000000 --- a/packages/schematics/angular/migrations/update-10/schematic-options_spec.ts +++ /dev/null @@ -1,184 +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.io/license - */ - -import { EmptyTree } from '@angular-devkit/schematics'; -import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; - -const workspacePath = '/angular.json'; - -describe('Migration to version 10', () => { - describe('Migrate workspace config', () => { - const schematicRunner = new SchematicTestRunner( - 'migrations', - require.resolve('../migration-collection.json'), - ); - - let tree: UnitTestTree; - - beforeEach(async () => { - tree = new UnitTestTree(new EmptyTree()); - tree = await schematicRunner - .runExternalSchematicAsync( - require.resolve('../../collection.json'), - 'ng-new', - { - name: 'migration-test', - version: '1.2.3', - directory: '.', - }, - tree, - ) - .toPromise(); - }); - - describe('schematic options', () => { - it('should replace styleext with style', async () => { - const workspace = JSON.parse(tree.readContent(workspacePath)); - workspace.schematics = { - '@schematics/angular:component': { - styleext: 'scss', - }, - }; - tree.overwrite(workspacePath, JSON.stringify(workspace, undefined, 2)); - - const tree2 = await schematicRunner - .runSchematicAsync('schematic-options-10', {}, tree.branch()) - .toPromise(); - const { schematics } = JSON.parse(tree2.readContent(workspacePath)); - expect(schematics['@schematics/angular:component'].styleext).toBeUndefined(); - expect(schematics['@schematics/angular:component'].style).toBe('scss'); - }); - - it('should not replace styleext with style in non-Angular schematic', async () => { - const workspace = JSON.parse(tree.readContent(workspacePath)); - workspace.schematics = { - '@schematics/some-other:component': { - styleext: 'scss', - }, - }; - tree.overwrite(workspacePath, JSON.stringify(workspace, undefined, 2)); - - const tree2 = await schematicRunner - .runSchematicAsync('schematic-options-10', {}, tree.branch()) - .toPromise(); - const { schematics } = JSON.parse(tree2.readContent(workspacePath)); - expect(schematics['@schematics/some-other:component'].styleext).toBe('scss'); - expect(schematics['@schematics/some-other:component'].style).toBeUndefined(); - }); - - it('should replace spec (false) with skipTests (true)', async () => { - const workspace = JSON.parse(tree.readContent(workspacePath)); - workspace.schematics = { - '@schematics/angular:component': { - spec: false, - }, - }; - tree.overwrite(workspacePath, JSON.stringify(workspace, undefined, 2)); - - const tree2 = await schematicRunner - .runSchematicAsync('schematic-options-10', {}, tree.branch()) - .toPromise(); - const { schematics } = JSON.parse(tree2.readContent(workspacePath)); - expect(schematics['@schematics/angular:component'].spec).toBeUndefined(); - expect(schematics['@schematics/angular:component'].skipTests).toBe(true); - }); - - it('should replace spec (true) with skipTests (false)', async () => { - const workspace = JSON.parse(tree.readContent(workspacePath)); - workspace.schematics = { - '@schematics/angular:component': { - spec: true, - }, - }; - tree.overwrite(workspacePath, JSON.stringify(workspace, undefined, 2)); - - const tree2 = await schematicRunner - .runSchematicAsync('schematic-options-10', {}, tree.branch()) - .toPromise(); - const { schematics } = JSON.parse(tree2.readContent(workspacePath)); - expect(schematics['@schematics/angular:component'].spec).toBeUndefined(); - expect(schematics['@schematics/angular:component'].skipTests).toBe(false); - }); - - it('should replace spec with skipTests for multiple Angular schematics', async () => { - const workspace = JSON.parse(tree.readContent(workspacePath)); - workspace.schematics = { - '@schematics/angular:component': { - spec: false, - }, - '@schematics/angular:directive': { - spec: false, - }, - '@schematics/angular:service': { - spec: true, - }, - '@schematics/angular:module': { - spec: false, - }, - '@schematics/angular:guard': { - spec: true, - }, - }; - tree.overwrite(workspacePath, JSON.stringify(workspace, undefined, 2)); - - const tree2 = await schematicRunner - .runSchematicAsync('schematic-options-10', {}, tree.branch()) - .toPromise(); - const { schematics } = JSON.parse(tree2.readContent(workspacePath)); - for (const key of Object.keys(workspace.schematics)) { - expect(schematics[key].spec).toBeUndefined(); - expect(schematics[key].skipTests).toBe(!workspace.schematics[key].spec); - } - }); - - it('should replace both styleext with style and spec with skipTests', async () => { - const workspace = JSON.parse(tree.readContent(workspacePath)); - workspace.schematics = { - '@schematics/angular:component': { - styleext: 'scss', - spec: false, - }, - }; - tree.overwrite(workspacePath, JSON.stringify(workspace, undefined, 2)); - - const tree2 = await schematicRunner - .runSchematicAsync('schematic-options-10', {}, tree.branch()) - .toPromise(); - const { schematics } = JSON.parse(tree2.readContent(workspacePath)); - expect(schematics['@schematics/angular:component'].styleext).toBeUndefined(); - expect(schematics['@schematics/angular:component'].style).toBe('scss'); - expect(schematics['@schematics/angular:component'].spec).toBeUndefined(); - expect(schematics['@schematics/angular:component'].skipTests).toBe(true); - }); - - it('should replace both styleext with style and spec with skipTests in a project', async () => { - const workspace = JSON.parse(tree.readContent(workspacePath)); - workspace.projects['migration-test'].schematics = { - '@schematics/angular:component': { - styleext: 'scss', - spec: false, - }, - }; - tree.overwrite(workspacePath, JSON.stringify(workspace, undefined, 2)); - - const tree2 = await schematicRunner - .runSchematicAsync('schematic-options-10', {}, tree.branch()) - .toPromise(); - const { - projects: { - 'migration-test': { schematics }, - }, - } = JSON.parse(tree2.readContent(workspacePath)); - expect(schematics['@schematics/angular:component'].styleext).toBeUndefined(); - expect(schematics['@schematics/angular:component'].style).toBe('scss'); - expect(schematics['@schematics/angular:component'].spec).toBeUndefined(); - expect(schematics['@schematics/angular:component'].skipTests).toBe(true); - }); - }); - }); -}); diff --git a/packages/schematics/angular/migrations/update-10/update-angular-config.ts b/packages/schematics/angular/migrations/update-10/update-angular-config.ts deleted file mode 100644 index e65d4a452abf..000000000000 --- a/packages/schematics/angular/migrations/update-10/update-angular-config.ts +++ /dev/null @@ -1,123 +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.io/license - */ - -import { JsonObject, JsonValue, isJsonObject, workspaces } from '@angular-devkit/core'; -import { Rule } from '@angular-devkit/schematics'; -import { updateWorkspace } from '../../utility/workspace'; -import { Builders, ProjectType } from '../../utility/workspace-models'; - -export default function (): Rule { - return updateWorkspace((workspace) => { - // Remove deprecated CLI root level options - removeDeprecatedCLIOptions(workspace.extensions); - - for (const [, project] of workspace.projects) { - // Project level - removeDeprecatedCLIOptions(project.extensions); - - if (project.extensions.projectType !== ProjectType.Application) { - // Only interested in application projects since these changes only effects application builders - continue; - } - - for (const [, target] of project.targets) { - // Only interested in Angular Devkit builders - if (!target?.builder.startsWith('@angular-devkit/build-angular')) { - continue; - } - - let optionsToRemove: Record = { - evalSourceMap: undefined, - skipAppShell: undefined, - profile: undefined, - elementExplorer: undefined, - }; - - if (target.builder === Builders.Server) { - optionsToRemove = { - ...optionsToRemove, - vendorChunk: undefined, - commonChunk: undefined, - }; - } - - // Check options - if (target.options) { - target.options = { - ...updateVendorSourceMap(target.options), - ...optionsToRemove, - }; - } - - // Go through each configuration entry - if (!target.configurations) { - continue; - } - - for (const configurationName of Object.keys(target.configurations)) { - target.configurations[configurationName] = { - ...updateVendorSourceMap(target.configurations[configurationName]), - ...optionsToRemove, - }; - } - } - } - }); -} - -type TargetOptions = workspaces.TargetDefinition['options']; - -function updateVendorSourceMap(options: TargetOptions): TargetOptions { - if (!options) { - return {}; - } - - const { vendorSourceMap: vendor, sourceMap = true } = options; - - if (vendor === undefined) { - return options; - } - - if (sourceMap === true) { - return { - ...options, - sourceMap: { - styles: true, - scripts: true, - vendor, - }, - vendorSourceMap: undefined, - }; - } - - if (typeof sourceMap === 'object') { - return { - ...options, - sourceMap: { - ...sourceMap, - vendor, - }, - vendorSourceMap: undefined, - }; - } - - return { - ...options, - vendorSourceMap: undefined, - }; -} - -function removeDeprecatedCLIOptions(extensions: Record) { - const cliOptions = extensions?.cli; - if (cliOptions && isJsonObject(cliOptions) && isJsonObject(cliOptions.warnings)) { - (cliOptions.warnings as Partial) = { - ...cliOptions.warnings, - typescriptMismatch: undefined, - }; - } -} diff --git a/packages/schematics/angular/migrations/update-10/update-angular-config_spec.ts b/packages/schematics/angular/migrations/update-10/update-angular-config_spec.ts deleted file mode 100644 index 0b470af36293..000000000000 --- a/packages/schematics/angular/migrations/update-10/update-angular-config_spec.ts +++ /dev/null @@ -1,155 +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.io/license - */ - -import { JsonObject } from '@angular-devkit/core'; -import { EmptyTree } from '@angular-devkit/schematics'; -import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; -import { - BuilderTarget, - Builders, - ProjectType, - WorkspaceSchema, -} from '../../utility/workspace-models'; - -function getBuildTarget(tree: UnitTestTree): BuilderTarget { - return JSON.parse(tree.readContent('/angular.json')).projects.app.architect.build; -} - -function createWorkSpaceConfig(tree: UnitTestTree) { - const angularConfig: WorkspaceSchema = { - version: 1, - cli: { - warnings: { - versionMismatch: false, - typescriptMismatch: true, - }, - }, - projects: { - app: { - cli: { - warnings: { - versionMismatch: false, - typescriptMismatch: true, - }, - }, - root: '', - sourceRoot: 'src', - projectType: ProjectType.Application, - prefix: 'app', - architect: { - build: { - builder: Builders.Browser, - options: { - skipAppShell: true, - sourceMap: true, - vendorSourceMap: true, - evalSourceMap: false, - buildOptimizer: true, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any, - configurations: { - one: { - sourceMap: false, - vendorSourceMap: false, - skipAppShell: false, - evalSourceMap: true, - buildOptimizer: false, - }, - two: { - sourceMap: { - styles: false, - scripts: true, - }, - evalSourceMap: true, - vendorSourceMap: false, - skipAppShell: true, - buildOptimizer: true, - }, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any, - }, - }, - }, - }, - }; - - tree.create('/angular.json', JSON.stringify(angularConfig, undefined, 2)); -} - -describe(`Migration to update 'angular.json'`, () => { - const schematicName = 'update-angular-config'; - - const schematicRunner = new SchematicTestRunner( - 'migrations', - require.resolve('../migration-collection.json'), - ); - - let tree: UnitTestTree; - beforeEach(() => { - tree = new UnitTestTree(new EmptyTree()); - createWorkSpaceConfig(tree); - }); - - it(`should remove 'skipAppShell'`, async () => { - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - const { options, configurations } = getBuildTarget(newTree); - - expect(options.skipAppShell).toBeUndefined(); - expect(configurations).toBeDefined(); - expect(configurations?.one.skipAppShell).toBeUndefined(); - expect(configurations?.two.skipAppShell).toBeUndefined(); - }); - - it(`should remove 'evalSourceMap'`, async () => { - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - const { options, configurations } = getBuildTarget(newTree); - - expect(options.evalSourceMap).toBeUndefined(); - expect(configurations).toBeDefined(); - expect(configurations?.one.evalSourceMap).toBeUndefined(); - expect(configurations?.two.evalSourceMap).toBeUndefined(); - }); - - it(`should remove 'vendorSourceMap' and set 'vendor' option in 'sourceMap'`, async () => { - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - const { options, configurations } = getBuildTarget(newTree); - - expect(options.vendorSourceMap).toBeUndefined(); - expect(options.sourceMap).toEqual({ - vendor: true, - scripts: true, - styles: true, - }); - expect(options.vendorSourceMap).toBeUndefined(); - - expect(configurations).toBeDefined(); - expect(configurations?.one.vendorSourceMap).toBeUndefined(); - expect(configurations?.one.sourceMap).toBe(false); - - expect(configurations?.two.vendorSourceMap).toBeUndefined(); - expect(configurations?.two.sourceMap).toEqual({ - vendor: false, - scripts: true, - styles: false, - }); - }); - - it(`should remove root level 'typescriptMismatch'`, async () => { - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - const config = JSON.parse(newTree.readContent('/angular.json')).cli.warnings; - expect(config.typescriptMismatch).toBeUndefined(); - expect(config.versionMismatch).toBeFalse(); - }); - - it(`should remove project level 'typescriptMismatch'`, async () => { - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - const config = JSON.parse(newTree.readContent('/angular.json')).projects.app.cli.warnings; - expect(config.typescriptMismatch).toBeUndefined(); - expect(config.versionMismatch).toBeFalse(); - }); -}); diff --git a/packages/schematics/angular/migrations/update-10/update-dependencies.ts b/packages/schematics/angular/migrations/update-10/update-dependencies.ts deleted file mode 100644 index f6c87c238b95..000000000000 --- a/packages/schematics/angular/migrations/update-10/update-dependencies.ts +++ /dev/null @@ -1,70 +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.io/license - */ - -import { Rule } from '@angular-devkit/schematics'; -import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks'; -import { addPackageJsonDependency, getPackageJsonDependency } from '../../utility/dependencies'; -import { latestVersions } from '../../utility/latest-versions'; - -export default function (): Rule { - return (host, context) => { - const dependenciesToUpdate: Record = { - 'jasmine-core': '~3.5.0', - 'jasmine-spec-reporter': '~5.0.0', - 'karma': '~5.0.0', - 'karma-chrome-launcher': '~3.1.0', - 'karma-coverage-istanbul-reporter': '~3.0.2', - 'karma-jasmine': '~4.0.0', - 'karma-jasmine-html-reporter': '^1.5.0', - 'protractor': '~7.0.0', - 'ng-packagr': latestVersions['ng-packagr'], - 'tslib': '^2.0.0', - }; - - let hasChanges = false; - for (const [name, version] of Object.entries(dependenciesToUpdate)) { - const current = getPackageJsonDependency(host, name); - if (!current || current.version === version) { - continue; - } - - addPackageJsonDependency(host, { - type: current.type, - name, - version, - overwrite: true, - }); - - hasChanges = true; - } - - if (hasChanges) { - context.addTask(new NodePackageInstallTask()); - } - - // Check for @angular-devkit/schematics and @angular-devkit/core - for (const name of ['@angular-devkit/schematics', '@angular-devkit/core']) { - if (getPackageJsonDependency(host, name)) { - context.logger.info( - `Package "${name}" found in the workspace package.json. ` + - 'This package typically does not need to be installed manually. ' + - 'If it is not being used by project code, it can be removed from the package.json.', - ); - } - } - - if (getPackageJsonDependency(host, 'rxjs-compat')) { - context.logger.info( - `Package "rxjs-compat" found in the workspace package.json. ` + - 'This package typically was used during migration from RxJs version 5 to 6 during the Angular 5 ' + - 'timeframe and may no longer be needed.\n' + - 'Read more about this: https://rxjs-dev.firebaseapp.com/guide/v6/migration', - ); - } - }; -} diff --git a/packages/schematics/angular/migrations/update-10/update-libraries-tslib.ts b/packages/schematics/angular/migrations/update-10/update-libraries-tslib.ts deleted file mode 100644 index efe255b16f36..000000000000 --- a/packages/schematics/angular/migrations/update-10/update-libraries-tslib.ts +++ /dev/null @@ -1,49 +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.io/license - */ - -import { join, normalize } from '@angular-devkit/core'; -import { Rule } from '@angular-devkit/schematics'; -import { - NodeDependencyType, - addPackageJsonDependency, - removePackageJsonDependency, -} from '../../utility/dependencies'; -import { getWorkspace } from '../../utility/workspace'; -import { ProjectType } from '../../utility/workspace-models'; - -export default function (): Rule { - return async (host) => { - const workspace = await getWorkspace(host); - - for (const [, project] of workspace.projects) { - if (project.extensions.projectType !== ProjectType.Library) { - // Only interested in library projects - continue; - } - - const packageJsonPath = join(normalize(project.root), 'package.json'); - if (!host.exists(packageJsonPath)) { - continue; - } - - // Remove tslib from any type of dependency - removePackageJsonDependency(host, 'tslib', packageJsonPath); - - // Add tslib as a direct dependency - addPackageJsonDependency( - host, - { - name: 'tslib', - version: '^2.0.0', - type: NodeDependencyType.Default, - }, - packageJsonPath, - ); - } - }; -} diff --git a/packages/schematics/angular/migrations/update-10/update-libraries-tslib_spec.ts b/packages/schematics/angular/migrations/update-10/update-libraries-tslib_spec.ts deleted file mode 100644 index e3eff6cb1abd..000000000000 --- a/packages/schematics/angular/migrations/update-10/update-libraries-tslib_spec.ts +++ /dev/null @@ -1,67 +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.io/license - */ - -import { EmptyTree } from '@angular-devkit/schematics'; -import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; -import { ProjectType, WorkspaceSchema } from '../../utility/workspace-models'; - -function createWorkSpaceConfig(tree: UnitTestTree) { - const angularConfig: WorkspaceSchema = { - version: 1, - projects: { - lib: { - root: '/project/lib', - sourceRoot: '/project/lib/src', - projectType: ProjectType.Library, - prefix: 'lib', - architect: {}, - }, - }, - }; - - tree.create('/angular.json', JSON.stringify(angularConfig, undefined, 2)); -} - -describe(`Migration to add tslib as a direct dependency in library projects`, () => { - const schematicName = 'update-libraries-tslib'; - - const schematicRunner = new SchematicTestRunner( - 'migrations', - require.resolve('../migration-collection.json'), - ); - - let tree: UnitTestTree; - beforeEach(() => { - tree = new UnitTestTree(new EmptyTree()); - createWorkSpaceConfig(tree); - - tree.create( - '/project/lib/package.json', - JSON.stringify( - { - peerDependencies: { - '@angular/common': '^9.0.0', - '@angular/core': '^9.0.0', - 'tslib': '1.0.0', - }, - }, - undefined, - 2, - ), - ); - }); - - it(`should update tslib to version 2 as a direct dependency`, async () => { - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - const { peerDependencies, dependencies } = JSON.parse( - newTree.readContent('/project/lib/package.json'), - ); - expect(peerDependencies['tslib']).toBeUndefined(); - expect(dependencies['tslib']).toBe('^2.0.0'); - }); -}); diff --git a/packages/schematics/angular/migrations/update-10/update-module-and-target-compiler-options.ts b/packages/schematics/angular/migrations/update-10/update-module-and-target-compiler-options.ts deleted file mode 100644 index 0c9c16a11cbc..000000000000 --- a/packages/schematics/angular/migrations/update-10/update-module-and-target-compiler-options.ts +++ /dev/null @@ -1,160 +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.io/license - */ - -import { dirname, join, normalize } from '@angular-devkit/core'; -import { Rule, Tree } from '@angular-devkit/schematics'; -import { JSONFile } from '../../utility/json-file'; -import { getWorkspace } from '../../utility/workspace'; -import { Builders } from '../../utility/workspace-models'; - -interface ModuleAndTargetReplamenent { - oldModule?: string; - newModule?: string | false; - oldTarget?: string; - newTarget?: string; -} - -export default function (): Rule { - return async (host, { logger }) => { - // Workspace level tsconfig - try { - updateModuleAndTarget(host, 'tsconfig.json', { - oldModule: 'esnext', - newModule: 'es2020', - }); - } catch (error) { - logger.warn( - `Unable to update 'tsconfig.json' module option from 'esnext' to 'es2020': ${ - error.message || error - }`, - ); - } - - const workspace = await getWorkspace(host); - // Find all tsconfig which are refereces used by builders - for (const [, project] of workspace.projects) { - for (const [, target] of project.targets) { - // E2E builder doesn't reference a tsconfig but it uses one found in the root folder. - if ( - target.builder === Builders.Protractor && - typeof target.options?.protractorConfig === 'string' - ) { - const tsConfigPath = join( - dirname(normalize(target.options.protractorConfig)), - 'tsconfig.json', - ); - - try { - updateModuleAndTarget(host, tsConfigPath, { - oldTarget: 'es5', - newTarget: 'es2018', - }); - } catch (error) { - logger.warn( - `Unable to update '${tsConfigPath}' target option from 'es5' to 'es2018': ${ - error.message || error - }`, - ); - } - - 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)]; - - if (uniqueTsConfigs.length < 1) { - continue; - } - - switch (target.builder as Builders) { - case Builders.Server: - uniqueTsConfigs.forEach((p) => { - try { - updateModuleAndTarget(host, p, { - oldModule: 'commonjs', - // False will remove the module - // NB: For server we no longer use commonjs because it is bundled using webpack which has it's own module system. - // This ensures that lazy-loaded works on the server. - newModule: false, - }); - } catch (error) { - logger.warn( - `Unable to remove '${p}' module option (was 'commonjs'): ${ - error.message || error - }`, - ); - } - - try { - updateModuleAndTarget(host, p, { - newTarget: 'es2016', - }); - } catch (error) { - logger.warn( - `Unable to update '${p}' target option to 'es2016': ${error.message || error}`, - ); - } - }); - break; - case Builders.Karma: - case Builders.Browser: - case Builders.DeprecatedNgPackagr: - uniqueTsConfigs.forEach((p) => { - try { - updateModuleAndTarget(host, p, { - oldModule: 'esnext', - newModule: 'es2020', - }); - } catch (error) { - logger.warn( - `Unable to update '${p}' module option from 'esnext' to 'es2020': ${ - error.message || error - }`, - ); - } - }); - break; - } - } - } - }; -} - -function updateModuleAndTarget( - host: Tree, - tsConfigPath: string, - replacements: ModuleAndTargetReplamenent, -) { - const json = new JSONFile(host, tsConfigPath); - - const { oldTarget, newTarget, newModule, oldModule } = replacements; - if (newTarget) { - const target = json.get(['compilerOptions', 'target']); - - if ( - (typeof target === 'string' && (!oldTarget || oldTarget === target.toLowerCase())) || - !target - ) { - json.modify(['compilerOptions', 'target'], newTarget); - } - } - - if (newModule === false) { - json.remove(['compilerOptions', 'module']); - } else if (newModule) { - const module = json.get(['compilerOptions', 'module']); - if (typeof module === 'string' && oldModule === module.toLowerCase()) { - json.modify(['compilerOptions', 'module'], newModule); - } - } -} diff --git a/packages/schematics/angular/migrations/update-10/update-module-and-target-compiler-options_spec.ts b/packages/schematics/angular/migrations/update-10/update-module-and-target-compiler-options_spec.ts deleted file mode 100644 index 4c06a6b05c35..000000000000 --- a/packages/schematics/angular/migrations/update-10/update-module-and-target-compiler-options_spec.ts +++ /dev/null @@ -1,156 +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.io/license - */ - -import { EmptyTree } from '@angular-devkit/schematics'; -import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; -import { parse as parseJson } from 'jsonc-parser'; -import { Builders, ProjectType, WorkspaceSchema } from '../../utility/workspace-models'; - -describe('Migration to update target and module compiler options', () => { - const schematicName = 'update-module-and-target-compiler-options'; - - const schematicRunner = new SchematicTestRunner( - 'migrations', - require.resolve('../migration-collection.json'), - ); - - function createJsonFile(tree: UnitTestTree, filePath: string, content: {}) { - tree.create(filePath, JSON.stringify(content, undefined, 2)); - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - function readJsonFile(tree: UnitTestTree, filePath: string): any { - return parseJson(tree.readContent(filePath).toString()); - } - - 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', - }, - }, - e2e: { - builder: Builders.Protractor, - options: { - protractorConfig: 'src/e2e/protractor.conf.js', - devServerTarget: '', - }, - }, - server: { - builder: Builders.Server, - options: { - tsConfig: 'src/tsconfig.server.json', - outputPath: '', - main: '', - }, - }, - }, - }, - }, - }; - - createJsonFile(tree, 'angular.json', angularConfig); - } - - let tree: UnitTestTree; - beforeEach(() => { - tree = new UnitTestTree(new EmptyTree()); - createWorkSpaceConfig(tree); - - // Create tsconfigs - const compilerOptions = { target: 'es2015', module: 'esnext' }; - - // Workspace - createJsonFile(tree, 'tsconfig.json', { compilerOptions }); - - // Application - createJsonFile(tree, 'src/tsconfig.app.json', { compilerOptions }); - createJsonFile(tree, 'src/tsconfig.app.prod.json', { compilerOptions }); - createJsonFile(tree, 'src/tsconfig.spec.json', { compilerOptions }); - - // E2E - createJsonFile(tree, 'src/e2e/protractor.conf.js', ''); - createJsonFile(tree, 'src/e2e/tsconfig.json', { - compilerOptions: { module: 'commonjs', target: 'es5' }, - }); - - // Universal - createJsonFile(tree, 'src/tsconfig.server.json', { compilerOptions: { module: 'commonjs' } }); - }); - - it(`should update module and target in workspace 'tsconfig.json'`, async () => { - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - const { module } = readJsonFile(newTree, 'tsconfig.json').compilerOptions; - expect(module).toBe('es2020'); - }); - - it(`should update module and target in 'tsconfig.json' which is referenced in option`, async () => { - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - const { module } = readJsonFile(newTree, 'src/tsconfig.spec.json').compilerOptions; - expect(module).toBe('es2020'); - }); - - it(`should update module and target in 'tsconfig.json' which is referenced in a configuration`, async () => { - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - const { module } = readJsonFile(newTree, 'src/tsconfig.app.prod.json').compilerOptions; - expect(module).toBe('es2020'); - }); - - it(`should update target to es2018 in E2E 'tsconfig.json'`, async () => { - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - const { module, target } = readJsonFile(newTree, 'src/e2e/tsconfig.json').compilerOptions; - expect(module).toBe('commonjs'); - expect(target).toBe('es2018'); - }); - - it(`should remove module in 'tsconfig.server.json'`, async () => { - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - const { module } = readJsonFile(newTree, 'src/tsconfig.server.json').compilerOptions; - expect(module).toBeUndefined(); - }); - - it(`should add target in 'tsconfig.server.json'`, async () => { - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - const { target } = readJsonFile(newTree, 'src/tsconfig.server.json').compilerOptions; - expect(target).toBe('es2016'); - }); - - it(`should update target to es2016 in 'tsconfig.server.json'`, async () => { - tree.delete('src/tsconfig.server.json'); - createJsonFile(tree, 'src/tsconfig.server.json', { - compilerOptions: { module: 'commonjs', target: 'es5' }, - }); - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - const { target } = readJsonFile(newTree, 'src/tsconfig.server.json').compilerOptions; - expect(target).toBe('es2016'); - }); -}); diff --git a/packages/schematics/angular/migrations/update-10/update-tslint.ts b/packages/schematics/angular/migrations/update-10/update-tslint.ts deleted file mode 100644 index 82886c0160d1..000000000000 --- a/packages/schematics/angular/migrations/update-10/update-tslint.ts +++ /dev/null @@ -1,137 +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.io/license - */ - -import { JsonValue } from '@angular-devkit/core'; -import { Rule } from '@angular-devkit/schematics'; -import { addPackageJsonDependency, getPackageJsonDependency } from '../../utility/dependencies'; -import { JSONFile } from '../../utility/json-file'; - -export const TSLINT_VERSION = '~6.1.0'; -const TSLINT_CONFIG_PATH = '/tslint.json'; - -const RULES_TO_DELETE: string[] = ['no-use-before-declare', 'no-unused-variable']; - -const RULES_TO_ADD: Record = { - align: { - options: ['parameters', 'statements'], - }, - 'arrow-return-shorthand': true, - curly: true, - eofline: true, - 'import-spacing': true, - indent: { - options: ['spaces'], - }, - 'variable-name': { - options: ['ban-keywords', 'check-format', 'allow-pascal-case'], - }, - semicolon: { options: ['always'] }, - 'space-before-function-paren': { - options: { - anonymous: 'never', - asyncArrow: 'always', - constructor: 'never', - method: 'never', - named: 'never', - }, - }, - 'typedef-whitespace': { - options: [ - { - 'call-signature': 'nospace', - 'index-signature': 'nospace', - parameter: 'nospace', - 'property-declaration': 'nospace', - 'variable-declaration': 'nospace', - }, - { - 'call-signature': 'onespace', - 'index-signature': 'onespace', - parameter: 'onespace', - 'property-declaration': 'onespace', - 'variable-declaration': 'onespace', - }, - ], - }, - whitespace: { - options: [ - 'check-branch', - 'check-decl', - 'check-operator', - 'check-separator', - 'check-type', - 'check-typecast', - ], - }, -}; - -export default function (): Rule { - return (tree, context) => { - const logger = context.logger; - - // Update tslint dependency - const current = getPackageJsonDependency(tree, 'tslint'); - - if (!current) { - logger.info('"tslint" in not a dependency of this workspace.'); - - return; - } - - if (current.version !== TSLINT_VERSION) { - addPackageJsonDependency(tree, { - type: current.type, - name: 'tslint', - version: TSLINT_VERSION, - overwrite: true, - }); - } - - // Update tslint config. - let json; - try { - json = new JSONFile(tree, TSLINT_CONFIG_PATH); - } catch { - const config = ['tslint.js', 'tslint.yaml'].find((c) => tree.exists(c)); - if (config) { - logger.warn(`Expected a JSON configuration file but found "${config}".`); - } else { - logger.warn('Cannot find "tslint.json" configuration file.'); - } - - return; - } - - // Remove old/deprecated rules. - for (const rule of RULES_TO_DELETE) { - json.remove(['rules', rule]); - } - - // Add new rules only iif the configuration extends 'tslint:recommended'. - // This is because some rules conflict with prettier or other tools. - const extend = json.get(['extends']); - if ( - extend !== 'tslint:recommended' || - (Array.isArray(extend) && extend.some((e) => e.value !== 'tslint:recommended')) - ) { - logger.warn( - `tslint configuration does not extend "tslint:recommended" or it extends multiple configurations.` + - '\nSkipping rule changes as some rules might conflict.', - ); - - return; - } - - for (const [name, value] of Object.entries(RULES_TO_ADD)) { - const ruleName = ['rules', name]; - if (json.get(ruleName) === undefined) { - json.modify(ruleName, value); - } - } - }; -} diff --git a/packages/schematics/angular/migrations/update-10/update-tslint_spec.ts b/packages/schematics/angular/migrations/update-10/update-tslint_spec.ts deleted file mode 100644 index 4bc13acbca2d..000000000000 --- a/packages/schematics/angular/migrations/update-10/update-tslint_spec.ts +++ /dev/null @@ -1,135 +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.io/license - */ - -import { EmptyTree } from '@angular-devkit/schematics'; -import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; -import { TSLINT_VERSION } from './update-tslint'; - -describe('Migration of tslint to version 6', () => { - const schematicRunner = new SchematicTestRunner( - 'migrations', - require.resolve('../migration-collection.json'), - ); - - let tree: UnitTestTree; - const TSLINT_PATH = '/tslint.json'; - const PACKAGE_JSON_PATH = '/package.json'; - - const TSLINT_CONFIG = { - extends: 'tslint:recommended', - rules: { - 'no-use-before-declare': true, - 'arrow-return-shorthand': false, - 'label-position': true, - }, - }; - - const PACKAGE_JSON = { - devDependencies: { - tslint: '~5.1.0', - }, - }; - - beforeEach(() => { - tree = new UnitTestTree(new EmptyTree()); - tree.create(PACKAGE_JSON_PATH, JSON.stringify(PACKAGE_JSON, null, 2)); - tree.create(TSLINT_PATH, JSON.stringify(TSLINT_CONFIG, null, 2)); - }); - - it('should update tslint dependency', async () => { - const newTree = await schematicRunner - .runSchematicAsync('tslint-version-6', {}, tree) - .toPromise(); - const packageJson = JSON.parse(newTree.readContent(PACKAGE_JSON_PATH)); - expect(packageJson.devDependencies.tslint).toBe(TSLINT_VERSION); - }); - - it('should remove old/deprecated rules', async () => { - const newTree = await schematicRunner - .runSchematicAsync('tslint-version-6', {}, tree) - .toPromise(); - const { rules } = JSON.parse(newTree.readContent(TSLINT_PATH)); - expect(rules['no-use-before-declare']).toBeUndefined(); - }); - - it('should add new rules', async () => { - const newTree = await schematicRunner - .runSchematicAsync('tslint-version-6', {}, tree) - .toPromise(); - const { rules } = JSON.parse(newTree.readContent(TSLINT_PATH)); - expect(rules['eofline']).toBe(true); - }); - - it('should not update already present rules', async () => { - const newTree = await schematicRunner - .runSchematicAsync('tslint-version-6', {}, tree) - .toPromise(); - const { rules } = JSON.parse(newTree.readContent(TSLINT_PATH)); - expect(rules['arrow-return-shorthand']).toBe(false); - }); - - it(`should not add new rules when not extending 'tslint:recommended'`, async () => { - tree.overwrite( - TSLINT_PATH, - JSON.stringify( - { - ...TSLINT_CONFIG, - extends: 'tslint-config-prettier', - }, - null, - 2, - ), - ); - - const newTree = await schematicRunner - .runSchematicAsync('tslint-version-6', {}, tree) - .toPromise(); - const { rules } = JSON.parse(newTree.readContent(TSLINT_PATH)); - expect(rules['eofline']).toBeUndefined(); - }); - - it(`should not add new rules when extending multiple configs`, async () => { - tree.overwrite( - TSLINT_PATH, - JSON.stringify( - { - ...TSLINT_CONFIG, - extends: ['tslint:recommended', 'tslint-config-prettier'], - }, - null, - 2, - ), - ); - - const newTree = await schematicRunner - .runSchematicAsync('tslint-version-6', {}, tree) - .toPromise(); - const { rules } = JSON.parse(newTree.readContent(TSLINT_PATH)); - expect(rules['eofline']).toBeUndefined(); - }); - - it(`should remove old/deprecated rules when extending multiple configs`, async () => { - tree.overwrite( - TSLINT_PATH, - JSON.stringify( - { - ...TSLINT_CONFIG, - extends: ['tslint:recommended', 'tslint-config-prettier'], - }, - null, - 2, - ), - ); - - const newTree = await schematicRunner - .runSchematicAsync('tslint-version-6', {}, tree) - .toPromise(); - const { rules } = JSON.parse(newTree.readContent(TSLINT_PATH)); - expect(rules['no-use-before-declare']).toBeUndefined(); - }); -}); diff --git a/packages/schematics/angular/migrations/update-11/add-declaration-map-compiler-option.ts b/packages/schematics/angular/migrations/update-11/add-declaration-map-compiler-option.ts deleted file mode 100644 index 7e9ef1f988d1..000000000000 --- a/packages/schematics/angular/migrations/update-11/add-declaration-map-compiler-option.ts +++ /dev/null @@ -1,48 +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.io/license - */ - -import { Rule, Tree } from '@angular-devkit/schematics'; -import { JSONFile } from '../../utility/json-file'; -import { getWorkspace } from '../../utility/workspace'; -import { Builders } from '../../utility/workspace-models'; - -export default function (): Rule { - return async (host) => { - const workspace = await getWorkspace(host); - - for (const [, project] of workspace.projects) { - for (const [, target] of project.targets) { - if (target.builder !== Builders.NgPackagr) { - continue; - } - - if (!target.configurations) { - continue; - } - - for (const options of Object.values(target.configurations)) { - addDeclarationMapValue(host, options?.tsConfig, false); - } - - addDeclarationMapValue(host, target.options?.tsConfig, true); - } - } - }; -} - -function addDeclarationMapValue(host: Tree, tsConfigPath: unknown, value: boolean): void { - if (typeof tsConfigPath !== 'string') { - return; - } - - const declarationMapPath = ['compilerOptions', 'declarationMap']; - const file = new JSONFile(host, tsConfigPath); - if (file.get(declarationMapPath) === undefined) { - file.modify(declarationMapPath, value); - } -} diff --git a/packages/schematics/angular/migrations/update-11/add-declaration-map-compiler-option_spec.ts b/packages/schematics/angular/migrations/update-11/add-declaration-map-compiler-option_spec.ts deleted file mode 100644 index 506c1d431b6c..000000000000 --- a/packages/schematics/angular/migrations/update-11/add-declaration-map-compiler-option_spec.ts +++ /dev/null @@ -1,93 +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.io/license - */ - -import { EmptyTree } from '@angular-devkit/schematics'; -import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; -import { Builders, ProjectType, WorkspaceSchema } from '../../utility/workspace-models'; - -function createWorkSpaceConfig(tree: UnitTestTree) { - const angularConfig: WorkspaceSchema = { - version: 1, - projects: { - lib: { - root: '/project/lib', - sourceRoot: '/project/lib/src', - projectType: ProjectType.Library, - prefix: 'lib', - architect: { - build: { - builder: Builders.NgPackagr, - options: { - tsConfig: 'projects/lib/tsconfig.lib.json', - }, - configurations: { - production: { - tsConfig: 'projects/lib/tsconfig.lib.prod.json', - }, - }, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any, - }, - }, - }, - }; - - tree.create('/angular.json', JSON.stringify(angularConfig, undefined, 2)); -} - -describe(`Migration to add 'declarationMap' compiler option`, () => { - const schematicName = 'add-declaration-map-compiler-option'; - - const schematicRunner = new SchematicTestRunner( - 'migrations', - require.resolve('../migration-collection.json'), - ); - - let tree: UnitTestTree; - beforeEach(() => { - tree = new UnitTestTree(new EmptyTree()); - createWorkSpaceConfig(tree); - const tsConfig = JSON.stringify({ compilerOptions: {} }, undefined, 2); - tree.create('/projects/lib/tsconfig.lib.json', tsConfig); - tree.create('/projects/lib/tsconfig.lib.prod.json', tsConfig); - }); - - it(`should be added with a value of 'false' in a prod tsconfig`, async () => { - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - const { compilerOptions } = JSON.parse( - newTree.readContent('/projects/lib/tsconfig.lib.prod.json'), - ); - expect(compilerOptions.declarationMap).toBeFalse(); - }); - - it(`should be added with a value of 'true' in a non prod tsconfig`, async () => { - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - const { compilerOptions } = JSON.parse(newTree.readContent('/projects/lib/tsconfig.lib.json')); - expect(compilerOptions.declarationMap).toBeTrue(); - }); - - it('should not be overriden when already set', async () => { - const tsConfigPath = '/projects/lib/tsconfig.lib.json'; - tree.overwrite( - tsConfigPath, - JSON.stringify( - { - compilerOptions: { - declarationMap: false, - }, - }, - undefined, - 2, - ), - ); - - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - const { compilerOptions } = JSON.parse(newTree.readContent(tsConfigPath)); - expect(compilerOptions.declarationMap).toBeFalse(); - }); -}); diff --git a/packages/schematics/angular/migrations/update-11/replace-ng-packagr-builder.ts b/packages/schematics/angular/migrations/update-11/replace-ng-packagr-builder.ts deleted file mode 100644 index df8c28b78482..000000000000 --- a/packages/schematics/angular/migrations/update-11/replace-ng-packagr-builder.ts +++ /dev/null @@ -1,40 +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.io/license - */ - -import { Rule, chain } from '@angular-devkit/schematics'; -import { - NodeDependencyType, - addPackageJsonDependency, - removePackageJsonDependency, -} from '../../utility/dependencies'; -import { latestVersions } from '../../utility/latest-versions'; -import { updateWorkspace } from '../../utility/workspace'; -import { Builders } from '../../utility/workspace-models'; - -export default function (): Rule { - return chain([ - updateWorkspace((workspace) => { - for (const [, project] of workspace.projects) { - for (const [, target] of project.targets) { - if (target.builder === Builders.DeprecatedNgPackagr) { - target.builder = Builders.NgPackagr; - } - } - } - }), - (host) => { - removePackageJsonDependency(host, '@angular-devkit/build-ng-packagr'); - addPackageJsonDependency(host, { - type: NodeDependencyType.Dev, - name: '@angular-devkit/build-angular', - version: latestVersions.DevkitBuildAngular, - overwrite: false, - }); - }, - ]); -} diff --git a/packages/schematics/angular/migrations/update-11/replace-ng-packagr-builder_spec.ts b/packages/schematics/angular/migrations/update-11/replace-ng-packagr-builder_spec.ts deleted file mode 100644 index 9472611f2702..000000000000 --- a/packages/schematics/angular/migrations/update-11/replace-ng-packagr-builder_spec.ts +++ /dev/null @@ -1,81 +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.io/license - */ - -import { EmptyTree } from '@angular-devkit/schematics'; -import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; -import { Builders, ProjectType, WorkspaceSchema } from '../../utility/workspace-models'; - -function createWorkSpaceConfig(tree: UnitTestTree) { - const angularConfig: WorkspaceSchema = { - version: 1, - projects: { - lib: { - root: '/project/lib', - sourceRoot: '/project/lib/src', - projectType: ProjectType.Library, - prefix: 'lib', - architect: { - build: { - builder: Builders.DeprecatedNgPackagr, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any, - }, - }, - }, - }; - - tree.create('/angular.json', JSON.stringify(angularConfig, undefined, 2)); -} - -describe(`Migration to replace '@angular-devkit/build-ng-packagr' builder`, () => { - const schematicName = 'replace-ng-packagr-builder'; - - const schematicRunner = new SchematicTestRunner( - 'migrations', - require.resolve('../migration-collection.json'), - ); - - let tree: UnitTestTree; - beforeEach(() => { - tree = new UnitTestTree(new EmptyTree()); - createWorkSpaceConfig(tree); - tree.create( - '/package.json', - JSON.stringify( - { - devDependencies: { - '@angular/compiler-cli': '0.0.0', - '@angular-devkit/build-ng-packagr': '0.0.0', - }, - }, - undefined, - 2, - ), - ); - }); - - it(`should remove '@angular-devkit/build-ng-packagr' from devDependencies`, async () => { - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - const { devDependencies } = JSON.parse(newTree.readContent('/package.json')); - expect(devDependencies['@angular-devkit/build-ng-packagr']).toBeUndefined(); - }); - - it(`should add '@angular-devkit/build-angular' to devDependencies`, async () => { - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - const { devDependencies } = JSON.parse(newTree.readContent('/package.json')); - expect(devDependencies['@angular-devkit/build-angular']).toBeTruthy(); - }); - - it('should update library builder target', async () => { - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - const { - projects: { lib }, - } = JSON.parse(newTree.readContent('/angular.json')); - expect(lib.architect.build.builder).toBe('@angular-devkit/build-angular:ng-packagr'); - }); -}); diff --git a/packages/schematics/angular/migrations/update-11/update-angular-config.ts b/packages/schematics/angular/migrations/update-11/update-angular-config.ts deleted file mode 100644 index cd10f8fd8851..000000000000 --- a/packages/schematics/angular/migrations/update-11/update-angular-config.ts +++ /dev/null @@ -1,89 +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.io/license - */ - -import { JsonArray, isJsonArray, isJsonObject, workspaces } from '@angular-devkit/core'; -import { Rule } from '@angular-devkit/schematics'; -import { updateWorkspace } from '../../utility/workspace'; - -export default function (): Rule { - return updateWorkspace((workspace) => { - const optionsToRemove: Record = { - environment: undefined, - extractCss: undefined, - tsconfigFileName: undefined, - rebaseRootRelativeCssUrls: undefined, - }; - - for (const [, project] of workspace.projects) { - for (const [, target] of project.targets) { - // Only interested in Angular Devkit builders - if (!target?.builder.startsWith('@angular-devkit/build-angular')) { - continue; - } - - // Check options - if (target.options) { - target.options = { - ...updateLazyScriptsStyleOption(target.options), - ...optionsToRemove, - }; - } - - // Go through each configuration entry - if (!target.configurations) { - continue; - } - - for (const configurationName of Object.keys(target.configurations)) { - target.configurations[configurationName] = { - ...updateLazyScriptsStyleOption(target.configurations[configurationName]), - ...optionsToRemove, - }; - } - } - } - }); -} - -type TargetOptions = workspaces.TargetDefinition['options']; - -function updateLazyScriptsStyleOption(options: TargetOptions): TargetOptions { - function visitor( - options: NonNullable, - type: 'scripts' | 'styles', - ): JsonArray | undefined { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - if (!options[type] || !isJsonArray(options[type]!)) { - return undefined; - } - const entries = []; - for (const entry of options[type] as JsonArray) { - if (isJsonObject(entry) && 'lazy' in entry) { - entries.push({ - ...entry, - inject: !entry.lazy, - lazy: undefined, - }); - } else { - entries.push(entry); - } - } - - return entries as JsonArray; - } - - if (!options) { - return undefined; - } - - return { - ...options, - styles: visitor(options, 'styles'), - scripts: visitor(options, 'scripts'), - }; -} diff --git a/packages/schematics/angular/migrations/update-11/update-angular-config_spec.ts b/packages/schematics/angular/migrations/update-11/update-angular-config_spec.ts deleted file mode 100644 index 358db2c8d31c..000000000000 --- a/packages/schematics/angular/migrations/update-11/update-angular-config_spec.ts +++ /dev/null @@ -1,117 +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.io/license - */ - -import { JsonObject } from '@angular-devkit/core'; -import { EmptyTree } from '@angular-devkit/schematics'; -import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; -import { - BuilderTarget, - Builders, - ProjectType, - WorkspaceSchema, -} from '../../utility/workspace-models'; - -function getBuildTarget(tree: UnitTestTree): BuilderTarget { - return JSON.parse(tree.readContent('/angular.json')).projects.app.architect.build; -} - -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: { - scripts: [{ lazy: true, name: 'bundle-1.js' }], - extractCss: false, - sourceMaps: true, - buildOptimizer: false, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any, - configurations: { - one: { - aot: true, - scripts: [ - { lazy: true, name: 'bundle-1.js' }, - { lazy: false, name: 'bundle-2.js' }, - { inject: true, name: 'bundle-3.js' }, - 'bundle-4.js', - ], - styles: [ - { lazy: true, name: 'bundle-1.css' }, - { lazy: false, name: 'bundle-2.css' }, - { inject: true, name: 'bundle-3.css' }, - 'bundle-3.css', - ], - }, - two: { - extractCss: true, - aot: true, - }, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any, - }, - }, - }, - }, - }; - - tree.create('/angular.json', JSON.stringify(angularConfig, undefined, 2)); -} - -const schematicName = 'update-angular-config-v11'; - -describe(`Migration to update 'angular.json'. ${schematicName}`, () => { - const schematicRunner = new SchematicTestRunner( - 'migrations', - require.resolve('../migration-collection.json'), - ); - - let tree: UnitTestTree; - beforeEach(() => { - tree = new UnitTestTree(new EmptyTree()); - createWorkSpaceConfig(tree); - }); - - it(`should remove 'extractCss'`, async () => { - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - const { options, configurations } = getBuildTarget(newTree); - - expect(options.extractCss).toBeUndefined(); - expect(configurations).toBeDefined(); - expect(configurations?.one.extractCss).toBeUndefined(); - expect(configurations?.two.extractCss).toBeUndefined(); - }); - - it(`should replace 'lazy' with 'inject'`, async () => { - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - const { options, configurations } = getBuildTarget(newTree); - - expect(configurations?.one.scripts).toEqual([ - { inject: false, name: 'bundle-1.js' }, - { inject: true, name: 'bundle-2.js' }, - { inject: true, name: 'bundle-3.js' }, - 'bundle-4.js', - ]); - - expect(configurations?.one.styles).toEqual([ - { inject: false, name: 'bundle-1.css' }, - { inject: true, name: 'bundle-2.css' }, - { inject: true, name: 'bundle-3.css' }, - 'bundle-3.css', - ]); - - expect(options.scripts).toEqual([{ inject: false, name: 'bundle-1.js' }]); - }); -}); diff --git a/packages/schematics/angular/migrations/update-11/update-dependencies.ts b/packages/schematics/angular/migrations/update-11/update-dependencies.ts deleted file mode 100644 index 2d84b18daa20..000000000000 --- a/packages/schematics/angular/migrations/update-11/update-dependencies.ts +++ /dev/null @@ -1,48 +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.io/license - */ - -import { Rule } from '@angular-devkit/schematics'; -import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks'; -import { addPackageJsonDependency, getPackageJsonDependency } from '../../utility/dependencies'; - -export default function (): Rule { - return (host, context) => { - const dependenciesToUpdate: Record = { - '@types/jasmine': '~3.6.0', - 'codelyzer': '^6.0.0', - 'jasmine-core': '~3.6.0', - 'jasmine-spec-reporter': '~5.0.0', - 'karma-chrome-launcher': '~3.1.0', - 'karma-coverage': '~2.0.3', - 'karma-jasmine': '~4.0.0', - 'karma-jasmine-html-reporter': '^1.5.0', - 'tslib': '^2.0.0', - }; - - let hasChanges = false; - for (const [name, version] of Object.entries(dependenciesToUpdate)) { - const current = getPackageJsonDependency(host, name); - if (!current || current.version === version) { - continue; - } - - addPackageJsonDependency(host, { - type: current.type, - name, - version, - overwrite: true, - }); - - hasChanges = true; - } - - if (hasChanges) { - context.addTask(new NodePackageInstallTask()); - } - }; -} diff --git a/packages/schematics/angular/migrations/update-12/production-default-config.ts b/packages/schematics/angular/migrations/update-12/production-default-config.ts deleted file mode 100644 index 6d122248cae2..000000000000 --- a/packages/schematics/angular/migrations/update-12/production-default-config.ts +++ /dev/null @@ -1,147 +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.io/license - */ - -import { JsonValue, logging, tags, workspaces } from '@angular-devkit/core'; -import { Rule } from '@angular-devkit/schematics'; -import { allTargetOptions, allWorkspaceTargets, updateWorkspace } from '../../utility/workspace'; -import { Builders } from '../../utility/workspace-models'; - -export default function (): Rule { - return async (_host, context) => - updateWorkspace((workspace) => { - for (const [name, target] of allWorkspaceTargets(workspace)) { - let defaultConfiguration: string | undefined; - - // Only interested in 1st party builders - switch (target.builder) { - case Builders.AppShell: - case Builders.Browser: - case Builders.Server: - case Builders.NgPackagr: - defaultConfiguration = 'production'; - break; - case Builders.DevServer: - case Builders.Protractor: - case '@nguniversal/builders:ssr-dev-server': - defaultConfiguration = 'development'; - break; - case Builders.TsLint: - case Builders.ExtractI18n: - case Builders.Karma: - // Nothing to update - break; - default: - context.logger - .warn(tags.stripIndents`Cannot update "${name}" target configuration as it's using "${target.builder}" - which is a third-party builder. This target configuration will require manual review.`); - - continue; - } - - if (!defaultConfiguration) { - continue; - } - - updateTarget(name, target, context.logger, defaultConfiguration); - } - }); -} - -function getArchitectTargetWithConfig(currentTarget: string, overrideConfig?: string): string { - const [project, target, config = 'development'] = currentTarget.split(':'); - - return `${project}:${target}:${overrideConfig || config}`; -} - -function updateTarget( - targetName: string, - target: workspaces.TargetDefinition, - logger: logging.LoggerApi, - defaultConfiguration: string, -): void { - if (!target.configurations) { - target.configurations = {}; - } - - if (target.configurations?.development) { - logger.info( - tags.stripIndents`Skipping updating "${targetName}" target configuration as a "development" configuration is already defined.`, - ); - - return; - } - - if (!target.configurations?.production) { - logger.info( - tags.stripIndents`Skipping updating "${targetName}" target configuration as a "production" configuration is not defined.`, - ); - - return; - } - - const developmentOptions: Record = {}; - let serverTarget = true; - let browserTarget = true; - let devServerTarget = true; - - for (const [, options] of allTargetOptions(target)) { - if (typeof options.serverTarget === 'string') { - options.serverTarget = getArchitectTargetWithConfig(options.serverTarget); - if (!developmentOptions.serverTarget) { - developmentOptions.serverTarget = getArchitectTargetWithConfig( - options.serverTarget, - 'development', - ); - } - } else { - serverTarget = false; - } - - if (typeof options.browserTarget === 'string') { - options.browserTarget = getArchitectTargetWithConfig(options.browserTarget); - if (!developmentOptions.browserTarget) { - developmentOptions.browserTarget = getArchitectTargetWithConfig( - options.browserTarget, - 'development', - ); - } - } else { - browserTarget = false; - } - - if (typeof options.devServerTarget === 'string') { - options.devServerTarget = getArchitectTargetWithConfig(options.devServerTarget); - if (!developmentOptions.devServerTarget) { - developmentOptions.devServerTarget = getArchitectTargetWithConfig( - options.devServerTarget, - 'development', - ); - } - } else { - devServerTarget = false; - } - } - - // If all configurastions have a target defined delete the one in options. - if (target.options) { - if (serverTarget) { - delete target.options.serverTarget; - } - - if (browserTarget) { - delete target.options.browserTarget; - } - - if (devServerTarget) { - delete target.options.devServerTarget; - } - } - - target.defaultConfiguration = defaultConfiguration; - target.configurations.development = developmentOptions; -} diff --git a/packages/schematics/angular/migrations/update-12/production-default-config_spec.ts b/packages/schematics/angular/migrations/update-12/production-default-config_spec.ts deleted file mode 100644 index 53582ff0fc7c..000000000000 --- a/packages/schematics/angular/migrations/update-12/production-default-config_spec.ts +++ /dev/null @@ -1,316 +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.io/license - */ - -import { JsonObject } 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'; - -function getArchitect(tree: UnitTestTree): JsonObject { - return JSON.parse(tree.readContent('/angular.json')).projects.app.architect; -} - -function createWorkSpaceConfig(tree: UnitTestTree) { - const angularConfig: WorkspaceSchema = { - version: 1, - projects: { - app: { - root: '', - sourceRoot: 'src', - projectType: ProjectType.Application, - prefix: 'app', - architect: { - browser: { - builder: Builders.Browser, - options: { - outputPath: 'dist/integration-project', - index: 'src/index.html', - main: 'src/main.ts', - polyfills: 'src/polyfills.ts', - tsConfig: 'tsconfig.app.json', - aot: true, - sourceMap: true, - assets: ['src/favicon.ico', 'src/assets'], - styles: ['src/styles.css'], - scripts: [], - }, - configurations: { - production: { - deployUrl: 'http://cdn.com', - fileReplacements: [ - { - replace: 'src/environments/environment.ts', - with: 'src/environments/environment.prod.ts', - }, - ], - optimization: true, - outputHashing: 'all', - sourceMap: false, - namedChunks: false, - extractLicenses: true, - vendorChunk: false, - buildOptimizer: true, - watch: true, - budgets: [ - { - type: 'initial', - maximumWarning: '2mb', - maximumError: '5mb', - }, - ], - }, - optimization_sm: { - sourceMap: true, - optimization: true, - namedChunks: false, - vendorChunk: true, - buildOptimizer: true, - }, - }, - }, - ng_packagr: { - builder: Builders.NgPackagr, - options: { - watch: true, - tsConfig: 'projects/lib/tsconfig.lib.json', - }, - configurations: { - production: { - watch: false, - tsConfig: 'projects/lib/tsconfig.lib.prod.json', - }, - }, - }, - dev_server: { - builder: Builders.DevServer, - options: { - browserTarget: 'app:build', - watch: false, - }, - configurations: { - production: { - browserTarget: 'app:build:production', - }, - optimization_sm: { - browserTarget: 'app:build:optimization_sm', - }, - }, - }, - app_shell: { - builder: Builders.AppShell, - options: { - browserTarget: 'app:build', - serverTarget: 'app:server', - }, - configurations: { - optimization_sm: { - browserTarget: 'app:build:optimization_sm', - serverTarget: 'app:server:optimization_sm', - }, - production: { - browserTarget: 'app:build:production', - serverTarget: 'app:server:optimization_sm', - }, - }, - }, - server: { - builder: Builders.Server, - options: { - outputPath: 'dist/server', - main: 'server.ts', - tsConfig: 'tsconfig.server.json', - optimization: false, - sourceMap: true, - }, - configurations: { - optimization_sm: { - sourceMap: true, - optimization: true, - }, - production: { - fileReplacements: [ - { - replace: 'src/environments/environment.ts', - with: 'src/environments/environment.prod.ts', - }, - ], - sourceMap: false, - optimization: true, - }, - }, - }, - }, - }, - }, - }; - - tree.create('/angular.json', JSON.stringify(angularConfig, undefined, 2)); -} - -const schematicName = 'production-by-default'; -describe(`Migration to update 'angular.json' configurations to production by default. ${schematicName}`, () => { - const schematicRunner = new SchematicTestRunner( - 'migrations', - require.resolve('../migration-collection.json'), - ); - - let tree: UnitTestTree; - beforeEach(() => { - tree = new UnitTestTree(new EmptyTree()); - createWorkSpaceConfig(tree); - }); - - it('update browser builder configurations', async () => { - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - const { browser } = getArchitect(newTree); - const output = { - builder: '@angular-devkit/build-angular:browser', - options: { - outputPath: 'dist/integration-project', - index: 'src/index.html', - main: 'src/main.ts', - polyfills: 'src/polyfills.ts', - tsConfig: 'tsconfig.app.json', - aot: true, - sourceMap: true, - assets: ['src/favicon.ico', 'src/assets'], - styles: ['src/styles.css'], - scripts: [], - }, - configurations: { - production: { - deployUrl: 'http://cdn.com', - optimization: true, - outputHashing: 'all', - sourceMap: false, - namedChunks: false, - extractLicenses: true, - vendorChunk: false, - buildOptimizer: true, - watch: true, - fileReplacements: [ - { - replace: 'src/environments/environment.ts', - with: 'src/environments/environment.prod.ts', - }, - ], - budgets: [ - { - type: 'initial', - maximumWarning: '2mb', - maximumError: '5mb', - }, - ], - }, - optimization_sm: { - sourceMap: true, - optimization: true, - namedChunks: false, - vendorChunk: true, - buildOptimizer: true, - }, - development: {}, - }, - defaultConfiguration: 'production', - }; - - expect(browser).toEqual(output); - }); - - it('update ng-packagr builder configurations', async () => { - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - const { ng_packagr } = getArchitect(newTree); - const output = { - builder: '@angular-devkit/build-angular:ng-packagr', - options: { watch: true, tsConfig: 'projects/lib/tsconfig.lib.json' }, - configurations: { - production: { watch: false, tsConfig: 'projects/lib/tsconfig.lib.prod.json' }, - development: {}, - }, - defaultConfiguration: 'production', - }; - - expect(ng_packagr).toEqual(output); - }); - - it('update dev-server builder configurations', async () => { - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - const { dev_server } = getArchitect(newTree); - const output = { - builder: '@angular-devkit/build-angular:dev-server', - options: { watch: false }, - configurations: { - production: { browserTarget: 'app:build:production' }, - optimization_sm: { browserTarget: 'app:build:optimization_sm' }, - development: { browserTarget: 'app:build:development' }, - }, - defaultConfiguration: 'development', - }; - - expect(dev_server).toEqual(output); - }); - - it('update server builder configurations', async () => { - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - const { server } = getArchitect(newTree); - const output = { - builder: '@angular-devkit/build-angular:server', - options: { - outputPath: 'dist/server', - main: 'server.ts', - tsConfig: 'tsconfig.server.json', - optimization: false, - sourceMap: true, - }, - configurations: { - optimization_sm: { sourceMap: true, optimization: true }, - production: { - fileReplacements: [ - { - replace: 'src/environments/environment.ts', - with: 'src/environments/environment.prod.ts', - }, - ], - sourceMap: false, - optimization: true, - }, - development: {}, - }, - defaultConfiguration: 'production', - }; - - expect(server).toEqual(output); - }); - - it('update app-shell builder configurations', async () => { - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - const { app_shell } = getArchitect(newTree); - - const output = { - builder: '@angular-devkit/build-angular:app-shell', - options: {}, - configurations: { - optimization_sm: { - browserTarget: 'app:build:optimization_sm', - serverTarget: 'app:server:optimization_sm', - }, - production: { - browserTarget: 'app:build:production', - serverTarget: 'app:server:optimization_sm', - }, - development: { - serverTarget: 'app:server:development', - browserTarget: 'app:build:development', - }, - }, - defaultConfiguration: 'production', - }; - - expect(app_shell).toEqual(output); - }); -}); diff --git a/packages/schematics/angular/migrations/update-12/remove-emit-decorator-metadata.ts b/packages/schematics/angular/migrations/update-12/remove-emit-decorator-metadata.ts deleted file mode 100644 index cc4716f1497e..000000000000 --- a/packages/schematics/angular/migrations/update-12/remove-emit-decorator-metadata.ts +++ /dev/null @@ -1,61 +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.io/license - */ - -import { join } from '@angular-devkit/core'; -import { DirEntry, Rule } from '@angular-devkit/schematics'; -import { JSONFile } from '../../utility/json-file'; -import { allWorkspaceTargets, getWorkspace } from '../../utility/workspace'; - -function* visitJsonFiles(directory: DirEntry): IterableIterator { - for (const path of directory.subfiles) { - if (!path.endsWith('.json')) { - continue; - } - - yield join(directory.path, path); - } - - for (const path of directory.subdirs) { - if (path === 'node_modules' || path.startsWith('.')) { - continue; - } - - yield* visitJsonFiles(directory.dir(path)); - } -} - -export default function (): Rule { - return async (tree, { logger }) => { - const workspace = await getWorkspace(tree); - const hasThirdPartyBuilders = [...allWorkspaceTargets(workspace)].some(([, target]) => { - const { builder } = target; - - return !( - builder.startsWith('@angular-devkit/build-angular') || - builder.startsWith('@nguniversal/builders') - ); - }); - - if (hasThirdPartyBuilders) { - logger.warn( - 'Skipping migration as the workspace uses third-party builders which may ' + - 'require "emitDecoratorMetadata" TypeScript compiler option.', - ); - - return; - } - - for (const path of visitJsonFiles(tree.root)) { - const content = tree.read(path); - if (content?.toString().includes('"emitDecoratorMetadata"')) { - const json = new JSONFile(tree, path); - json.remove(['compilerOptions', 'emitDecoratorMetadata']); - } - } - }; -} diff --git a/packages/schematics/angular/migrations/update-12/remove-emit-decorator-metadata_spec.ts b/packages/schematics/angular/migrations/update-12/remove-emit-decorator-metadata_spec.ts deleted file mode 100644 index d2a9a7b4207f..000000000000 --- a/packages/schematics/angular/migrations/update-12/remove-emit-decorator-metadata_spec.ts +++ /dev/null @@ -1,155 +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.io/license - */ - -import { EmptyTree } from '@angular-devkit/schematics'; -import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; -import { parse as parseJson } from 'jsonc-parser'; -import { Builders } from '../../utility/workspace-models'; - -describe('Migration to remove "emitDecoratorMetadata" compiler option', () => { - const schematicName = 'remove-emit-decorator-metadata'; - - const schematicRunner = new SchematicTestRunner( - 'migrations', - require.resolve('../migration-collection.json'), - ); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - function readJsonFile(tree: UnitTestTree, filePath: string): any { - return parseJson(tree.readContent(filePath).toString()); - } - - let tree: UnitTestTree; - beforeEach(() => { - tree = new UnitTestTree(new EmptyTree()); - tree.create( - '/angular.json', - JSON.stringify( - { - version: 1, - projects: { - app: { - root: '', - sourceRoot: 'src', - prefix: 'app', - architect: { - browser: { - builder: Builders.Browser, - }, - }, - }, - }, - }, - undefined, - 2, - ), - ); - }); - - it(`should rename 'emitDecoratorMetadata' when set to false`, async () => { - tree.create( - '/tsconfig.json', - JSON.stringify( - { - compilerOptions: { - emitDecoratorMetadata: false, - strict: true, - }, - }, - undefined, - 2, - ), - ); - - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - const { compilerOptions } = readJsonFile(newTree, '/tsconfig.json'); - expect(compilerOptions['emitDecoratorMetadata']).toBeUndefined(); - expect(compilerOptions['strict']).toBeTrue(); - }); - - it(`should rename 'emitDecoratorMetadata' when set to true`, async () => { - tree.create( - '/tsconfig.json', - JSON.stringify( - { - compilerOptions: { - emitDecoratorMetadata: true, - strict: true, - }, - }, - undefined, - 2, - ), - ); - - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - const { compilerOptions } = readJsonFile(newTree, '/tsconfig.json'); - expect(compilerOptions['emitDecoratorMetadata']).toBeUndefined(); - expect(compilerOptions['strict']).toBeTrue(); - }); - - it(`should not rename 'emitDecoratorMetadata' when it's not under 'compilerOptions'`, async () => { - tree.create( - '/foo.json', - JSON.stringify( - { - options: { - emitDecoratorMetadata: true, - }, - }, - undefined, - 2, - ), - ); - - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - const { options } = readJsonFile(newTree, '/foo.json'); - expect(options['emitDecoratorMetadata']).toBeTrue(); - }); - - it(`should not remove 'emitDecoratorMetadata' when one of the builders is a third-party`, async () => { - tree.create( - '/tsconfig.json', - JSON.stringify( - { - compilerOptions: { - emitDecoratorMetadata: true, - strict: true, - }, - }, - undefined, - 2, - ), - ); - tree.overwrite( - '/angular.json', - JSON.stringify( - { - version: 1, - projects: { - app: { - root: '', - sourceRoot: 'src', - prefix: 'app', - architect: { - browser: { - builder: '@nrwl/jest', - }, - }, - }, - }, - }, - undefined, - 2, - ), - ); - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - const { compilerOptions } = readJsonFile(newTree, '/tsconfig.json'); - expect(compilerOptions['emitDecoratorMetadata']).toBeTrue(); - }); -}); diff --git a/packages/schematics/angular/migrations/update-12/replace-prod-flag.ts b/packages/schematics/angular/migrations/update-12/replace-prod-flag.ts deleted file mode 100644 index f70f3961f72e..000000000000 --- a/packages/schematics/angular/migrations/update-12/replace-prod-flag.ts +++ /dev/null @@ -1,30 +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.io/license - */ - -import { Rule } from '@angular-devkit/schematics'; -import { JSONFile } from '../../utility/json-file'; - -export default function (): Rule { - return (tree) => { - const file = new JSONFile(tree, 'package.json'); - const scripts = file.get(['scripts']); - if (!scripts || typeof scripts !== 'object') { - return; - } - - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const updatedScripts = Object.entries(scripts!).map(([key, value]) => [ - key, - typeof value === 'string' - ? value.replace(/ --prod(?!\w)/g, ' --configuration production') - : value, - ]); - - file.modify(['scripts'], Object.fromEntries(updatedScripts)); - }; -} diff --git a/packages/schematics/angular/migrations/update-12/replace-prod-flag_spec.ts b/packages/schematics/angular/migrations/update-12/replace-prod-flag_spec.ts deleted file mode 100644 index a482d675326c..000000000000 --- a/packages/schematics/angular/migrations/update-12/replace-prod-flag_spec.ts +++ /dev/null @@ -1,74 +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.io/license - */ - -import { EmptyTree } from '@angular-devkit/schematics'; -import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; - -describe(`Migration to replace '--prod' flag from package.json scripts`, () => { - const pkgJsonPath = '/package.json'; - const schematicName = 'replace-deprecated-prod-flag'; - - const schematicRunner = new SchematicTestRunner( - 'migrations', - require.resolve('../migration-collection.json'), - ); - - let tree: UnitTestTree; - - beforeEach(async () => { - tree = new UnitTestTree(new EmptyTree()); - }); - - it(`should replace '--prod' with '--configuration production'`, async () => { - tree.create( - pkgJsonPath, - JSON.stringify( - { - scripts: { - build: 'ng build --prod', - test: 'ng test --prod && ng e2e --prod', - }, - }, - undefined, - 2, - ), - ); - const tree2 = await schematicRunner - .runSchematicAsync(schematicName, {}, tree.branch()) - .toPromise(); - - const { scripts } = JSON.parse(tree2.readContent(pkgJsonPath)); - expect(scripts).toEqual({ - build: 'ng build --configuration production', - test: 'ng test --configuration production && ng e2e --configuration production', - }); - }); - - it(`should not replace flags that start with '--prod...'`, async () => { - tree.create( - pkgJsonPath, - JSON.stringify( - { - scripts: { - test: 'npx test --production && ng e2e --prod', - }, - }, - undefined, - 2, - ), - ); - const tree2 = await schematicRunner - .runSchematicAsync(schematicName, {}, tree.branch()) - .toPromise(); - - const { scripts } = JSON.parse(tree2.readContent(pkgJsonPath)); - expect(scripts).toEqual({ - test: 'npx test --production && ng e2e --configuration production', - }); - }); -}); diff --git a/packages/schematics/angular/migrations/update-12/schematic-options.ts b/packages/schematics/angular/migrations/update-12/schematic-options.ts deleted file mode 100644 index 50870ef2be64..000000000000 --- a/packages/schematics/angular/migrations/update-12/schematic-options.ts +++ /dev/null @@ -1,41 +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.io/license - */ - -import { json } from '@angular-devkit/core'; -import { Rule } from '@angular-devkit/schematics'; -import { updateWorkspace } from '../../utility/workspace'; - -export default function (): Rule { - return updateWorkspace((workspace) => { - // Update root level schematics options if present - const rootSchematics = workspace.extensions.schematics; - if (rootSchematics && json.isJsonObject(rootSchematics)) { - updateSchematicsField(rootSchematics); - } - - // Update project level schematics options if present - for (const [, project] of workspace.projects) { - const projectSchematics = project.extensions.schematics; - if (projectSchematics && json.isJsonObject(projectSchematics)) { - updateSchematicsField(projectSchematics); - } - } - }); -} - -function updateSchematicsField(schematics: json.JsonObject): void { - for (const [schematicName, schematicOptions] of Object.entries(schematics)) { - if (!json.isJsonObject(schematicOptions)) { - continue; - } - - if (schematicName === '@schematics/angular:module') { - delete schematicOptions.skipTests; - } - } -} diff --git a/packages/schematics/angular/migrations/update-12/schematic-options_spec.ts b/packages/schematics/angular/migrations/update-12/schematic-options_spec.ts deleted file mode 100644 index f9891f62a367..000000000000 --- a/packages/schematics/angular/migrations/update-12/schematic-options_spec.ts +++ /dev/null @@ -1,76 +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.io/license - */ - -import { EmptyTree } from '@angular-devkit/schematics'; -import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; - -describe('Migration to remove schematics old options in angular.json', () => { - const workspacePath = '/angular.json'; - const schematicName = 'schematic-options-12'; - - const schematicRunner = new SchematicTestRunner( - 'migrations', - require.resolve('../migration-collection.json'), - ); - - let tree: UnitTestTree; - - beforeEach(async () => { - tree = new UnitTestTree(new EmptyTree()); - tree = await schematicRunner - .runExternalSchematicAsync( - require.resolve('../../collection.json'), - 'ng-new', - { - name: 'migration-test', - version: '1.2.3', - directory: '.', - }, - tree, - ) - .toPromise(); - }); - - describe('schematic options', () => { - it('should remove `skipTests` from `@schematics/angular:module`', async () => { - const workspace = JSON.parse(tree.readContent(workspacePath)); - workspace.schematics = { - '@schematics/angular:module': { - skipTests: true, - }, - }; - tree.overwrite(workspacePath, JSON.stringify(workspace, undefined, 2)); - - const tree2 = await schematicRunner - .runSchematicAsync(schematicName, {}, tree.branch()) - .toPromise(); - const { schematics } = JSON.parse(tree2.readContent(workspacePath)); - expect(schematics['@schematics/angular:module'].skipTests).toBeUndefined(); - }); - - it('should not remove `skipTests` from non `@schematics/angular:module` schematic', async () => { - const workspace = JSON.parse(tree.readContent(workspacePath)); - workspace.schematics = { - '@schematics/angular:component': { - skipTests: true, - }, - '@schematics/some-other:module': { - skipTests: true, - }, - }; - tree.overwrite(workspacePath, JSON.stringify(workspace, undefined, 2)); - - const tree2 = await schematicRunner - .runSchematicAsync(schematicName, {}, tree.branch()) - .toPromise(); - const { schematics } = JSON.parse(tree2.readContent(workspacePath)); - expect(schematics['@schematics/angular:component'].skipTests).toBeTrue(); - expect(schematics['@schematics/some-other:module'].skipTests).toBeTrue(); - }); - }); -}); diff --git a/packages/schematics/angular/migrations/update-12/update-angular-config.ts b/packages/schematics/angular/migrations/update-12/update-angular-config.ts deleted file mode 100644 index d9e91ea64ea2..000000000000 --- a/packages/schematics/angular/migrations/update-12/update-angular-config.ts +++ /dev/null @@ -1,103 +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.io/license - */ - -import { JsonValue, tags, workspaces } from '@angular-devkit/core'; -import { Rule } from '@angular-devkit/schematics'; -import { allTargetOptions, allWorkspaceTargets, updateWorkspace } from '../../utility/workspace'; - -type BuilderOptionsType = Readonly< - [optionName: string, oldDefault: JsonValue | undefined, newDefault: JsonValue | undefined][] ->; - -const BrowserBuilderOptions: BuilderOptionsType = [ - ['aot', false, true], - ['vendorChunk', true, false], - ['extractLicenses', false, true], - ['buildOptimizer', false, true], - ['sourceMap', true, false], - ['optimization', false, true], - ['namedChunks', true, false], -]; - -const ServerBuilderOptions: BuilderOptionsType = [ - ['sourceMap', true, false], - ['optimization', false, true], -]; - -export default function (): Rule { - return (_tree, context) => - updateWorkspace((workspace) => { - for (const [targetName, target, projectName] of allWorkspaceTargets(workspace)) { - if ( - !target.builder.startsWith('@angular-devkit/') && - !target.builder.startsWith('@nguniversal/') - ) { - context.logger.warn(tags.stripIndent` - "${targetName}" target in "${projectName}" project is using a third-party builder. - You may need to adjust the options to retain the existing behavior. - For more information, see the breaking changes section within the release notes: https://github.com/angular/angular-cli/releases/tag/v12.0.0 - `); - - continue; - } - - // Only interested in Angular Devkit browser and server builders - switch (target.builder) { - case '@angular-devkit/build-angular:server': - updateOptions(target, ServerBuilderOptions); - break; - case '@angular-devkit/build-angular:browser': - updateOptions(target, BrowserBuilderOptions); - break; - } - - for (const [, options] of allTargetOptions(target)) { - delete options.experimentalRollupPass; - delete options.lazyModules; - delete options.forkTypeChecker; - } - } - }); -} - -function updateOptions( - target: workspaces.TargetDefinition, - optionsToUpdate: typeof ServerBuilderOptions | typeof BrowserBuilderOptions, -): void { - // This is a hacky way to make this migration idempotent. - // `defaultConfiguration` was only introduced in v12 projects and hence v11 projects do not have this property. - // Setting it as an empty string will not cause any side-effect. - if (typeof target.defaultConfiguration === 'string') { - return; - } - - target.defaultConfiguration = ''; - - if (!target.options) { - target.options = {}; - } - - const configurationOptions = target.configurations && Object.values(target.configurations); - - for (const [optionName, oldDefault, newDefault] of optionsToUpdate) { - let value = target.options[optionName]; - if (value === newDefault) { - // Value is same as new default - delete target.options[optionName]; - } else if (value === undefined) { - // Value is not defined, hence the default in the builder was used. - target.options[optionName] = oldDefault; - value = oldDefault; - } - - // Remove overrides in configurations which are no longer needed. - configurationOptions - ?.filter((o) => o && o[optionName] === value) - .forEach((o) => o && delete o[optionName]); - } -} diff --git a/packages/schematics/angular/migrations/update-12/update-angular-config_spec.ts b/packages/schematics/angular/migrations/update-12/update-angular-config_spec.ts deleted file mode 100644 index 0cabc91fa6b4..000000000000 --- a/packages/schematics/angular/migrations/update-12/update-angular-config_spec.ts +++ /dev/null @@ -1,140 +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.io/license - */ - -import { JsonObject } from '@angular-devkit/core'; -import { EmptyTree } from '@angular-devkit/schematics'; -import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; -import { - BuilderTarget, - Builders, - ProjectType, - WorkspaceSchema, -} from '../../utility/workspace-models'; - -function getBuildTarget(tree: UnitTestTree): BuilderTarget { - return JSON.parse(tree.readContent('/angular.json')).projects.app.architect.build; -} - -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: { - aot: true, - optimization: true, - experimentalRollupPass: false, - buildOptimizer: false, - namedChunks: true, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any, - configurations: { - one: { - aot: true, - }, - two: { - experimentalRollupPass: true, - aot: false, - optimization: false, - }, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any, - }, - }, - }, - }, - }; - - tree.create('/angular.json', JSON.stringify(angularConfig, undefined, 2)); -} - -const schematicName = 'update-angular-config-v12'; - -describe(`Migration to update 'angular.json'. ${schematicName}`, () => { - const schematicRunner = new SchematicTestRunner( - 'migrations', - require.resolve('../migration-collection.json'), - ); - - let tree: UnitTestTree; - beforeEach(() => { - tree = new UnitTestTree(new EmptyTree()); - createWorkSpaceConfig(tree); - }); - - it(`should remove 'experimentalRollupPass'`, async () => { - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - const { options, configurations } = getBuildTarget(newTree); - - expect(options.experimentalRollupPass).toBeUndefined(); - expect(options.buildOptimizer).toBeFalse(); - expect(configurations).toBeDefined(); - expect(configurations?.one.experimentalRollupPass).toBeUndefined(); - expect(configurations?.two.experimentalRollupPass).toBeUndefined(); - }); - - it(`should remove value from "options" section which value is now the new default`, async () => { - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - const { options, configurations } = getBuildTarget(newTree); - - expect(options.aot).toBeUndefined(); - expect(configurations?.one.aot).toBeUndefined(); - expect(configurations?.two.aot).toBeFalse(); - }); - - it(`should remove value from "configuration" section when value is the same as that of "options"`, async () => { - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - const { options, configurations } = getBuildTarget(newTree); - - expect(options.aot).toBeUndefined(); - expect(configurations?.one.aot).toBeUndefined(); - expect(configurations?.two.aot).toBeFalse(); - }); - - it(`should add value in "options" section when option was not defined`, async () => { - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - const { options, configurations } = getBuildTarget(newTree); - - expect(options.sourceMap).toBeTrue(); - expect(configurations?.one.sourceMap).toBeUndefined(); - expect(configurations?.two.sourceMap).toBeUndefined(); - expect(configurations?.two.optimization).toBeFalse(); - }); - - it(`should not remove value in "options" when value is not the new default`, async () => { - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - const { options } = getBuildTarget(newTree); - - expect(options.namedChunks).toBeTrue(); - expect(options.buildOptimizer).toBeFalse(); - }); - - it('migration should be idempotent', async () => { - const { options } = getBuildTarget(tree); - expect(options.aot).toBeTrue(); - - // First run - const newTree1 = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - const { options: options1 } = getBuildTarget(newTree1); - expect(options1.aot).toBeUndefined(); - - // Second run - const newTree2 = await schematicRunner - .runSchematicAsync(schematicName, {}, newTree1) - .toPromise(); - const { options: options2 } = getBuildTarget(newTree2); - expect(options2.aot).toBeUndefined(); - }); -}); diff --git a/packages/schematics/angular/migrations/update-12/update-i18n.ts b/packages/schematics/angular/migrations/update-12/update-i18n.ts deleted file mode 100644 index 7b257b8c489b..000000000000 --- a/packages/schematics/angular/migrations/update-12/update-i18n.ts +++ /dev/null @@ -1,205 +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.io/license - */ - -import { logging, workspaces } from '@angular-devkit/core'; -import { Rule, Tree } from '@angular-devkit/schematics'; -import { posix } from 'path'; -import { - NodeDependencyType, - addPackageJsonDependency, - getPackageJsonDependency, -} from '../../utility/dependencies'; -import { latestVersions } from '../../utility/latest-versions'; -import { allTargetOptions, allWorkspaceTargets, updateWorkspace } from '../../utility/workspace'; -import { Builders } from '../../utility/workspace-models'; - -export default function (): Rule { - return (tree, { logger }) => - updateWorkspace((workspace) => { - // Process extraction targets first since they use browser option values - for (const [, target, , project] of allWorkspaceTargets(workspace)) { - switch (target.builder) { - case Builders.ExtractI18n: - addProjectI18NOptions(tree, target, project); - removeExtracti18nDeprecatedOptions(target); - break; - } - } - - for (const [, target] of allWorkspaceTargets(workspace)) { - switch (target.builder) { - case Builders.Browser: - case Builders.Server: - updateBaseHrefs(target); - removeFormatOption(target); - addBuilderI18NOptions(target, logger); - break; - } - } - }); -} - -function addProjectI18NOptions( - tree: Tree, - builderConfig: workspaces.TargetDefinition, - projectConfig: workspaces.ProjectDefinition, -) { - const browserConfig = projectConfig.targets.get('build'); - if (!browserConfig || browserConfig.builder !== Builders.Browser) { - return; - } - - // browser builder options - let locales: Record | undefined; - for (const [, options] of allTargetOptions(browserConfig)) { - const localeId = options.i18nLocale; - if (typeof localeId !== 'string') { - continue; - } - - const localeFile = options.i18nFile; - if (typeof localeFile !== 'string') { - continue; - } - - let baseHref = options.baseHref; - if (typeof baseHref === 'string') { - // If the configuration baseHref is already the default locale value, do not include it - if (baseHref === `/${localeId}/`) { - baseHref = undefined; - } - } else { - // If the configuration does not contain a baseHref, ensure the main option value is used. - baseHref = ''; - } - - if (!locales) { - locales = { - [localeId]: - baseHref === undefined - ? localeFile - : { - translation: localeFile, - baseHref, - }, - }; - } else { - locales[localeId] = - baseHref === undefined - ? localeFile - : { - translation: localeFile, - baseHref, - }; - } - } - - if (locales) { - // Get sourceLocale from extract-i18n builder - const i18nOptions = [...allTargetOptions(builderConfig)]; - const sourceLocale = i18nOptions - .map(([, o]) => o.i18nLocale) - .find((x) => !!x && typeof x === 'string'); - - projectConfig.extensions['i18n'] = { - locales, - ...(sourceLocale ? { sourceLocale } : {}), - }; - - // Add @angular/localize if not already a dependency - if (!getPackageJsonDependency(tree, '@angular/localize')) { - addPackageJsonDependency(tree, { - name: '@angular/localize', - version: latestVersions.Angular, - type: NodeDependencyType.Default, - }); - } - } -} - -function addBuilderI18NOptions( - builderConfig: workspaces.TargetDefinition, - logger: logging.LoggerApi, -) { - for (const [, options] of allTargetOptions(builderConfig)) { - const localeId = options.i18nLocale; - const i18nFile = options.i18nFile; - - const outputPath = options.outputPath; - if (typeof localeId === 'string' && i18nFile && typeof outputPath === 'string') { - if (outputPath.match(new RegExp(`[/\\\\]${localeId}[/\\\\]?$`))) { - const newOutputPath = outputPath.replace(new RegExp(`[/\\\\]${localeId}[/\\\\]?$`), ''); - options.outputPath = newOutputPath; - } else { - logger.warn( - `Output path value "${outputPath}" for locale "${localeId}" is not supported with the new localization system. ` + - `With the current value, the localized output would be written to "${posix.join( - outputPath, - localeId, - )}". ` + - `Keeping existing options for the target configuration of locale "${localeId}".`, - ); - - continue; - } - } - - if (typeof localeId === 'string') { - // add new localize option - options.localize = [localeId]; - delete options.i18nLocale; - } - - if (i18nFile !== undefined) { - delete options.i18nFile; - } - } -} - -function removeFormatOption(builderConfig: workspaces.TargetDefinition) { - for (const [, options] of allTargetOptions(builderConfig)) { - // The format is always auto-detected now - delete options.i18nFormat; - } -} - -function updateBaseHrefs(builderConfig: workspaces.TargetDefinition) { - const mainBaseHref = builderConfig.options?.baseHref; - const hasMainBaseHref = - !!mainBaseHref && typeof mainBaseHref === 'string' && mainBaseHref !== '/'; - - for (const [, options] of allTargetOptions(builderConfig)) { - const localeId = options.i18nLocale; - const i18nFile = options.i18nFile; - - // localize base HREF values are controlled by the i18n configuration - const baseHref = options.baseHref; - if (localeId !== undefined && i18nFile !== undefined && baseHref !== undefined) { - // if the main option set has a non-default base href, - // ensure that the augmented base href has the correct base value - if (hasMainBaseHref) { - options.baseHref = '/'; - } else { - delete options.baseHref; - } - } - } -} - -function removeExtracti18nDeprecatedOptions(builderConfig: workspaces.TargetDefinition) { - for (const [, options] of allTargetOptions(builderConfig)) { - // deprecated options - delete options.i18nLocale; - - if (options.i18nFormat !== undefined) { - // i18nFormat has been changed to format - options.format = options.i18nFormat; - delete options.i18nFormat; - } - } -} diff --git a/packages/schematics/angular/migrations/update-12/update-i18n_spec.ts b/packages/schematics/angular/migrations/update-12/update-i18n_spec.ts deleted file mode 100644 index 65967893a1a4..000000000000 --- a/packages/schematics/angular/migrations/update-12/update-i18n_spec.ts +++ /dev/null @@ -1,307 +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.io/license - */ - -import { EmptyTree } from '@angular-devkit/schematics'; -import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; -import { latestVersions } from '../../utility/latest-versions'; -import { WorkspaceTargets } from '../../utility/workspace-models'; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function getWorkspaceTargets(tree: UnitTestTree): any { - return JSON.parse(tree.readContent(workspacePath)).projects['migration-test'].architect; -} - -function updateWorkspaceTargets(tree: UnitTestTree, workspaceTargets: WorkspaceTargets) { - const config = JSON.parse(tree.readContent(workspacePath)); - config.projects['migration-test'].architect = workspaceTargets; - tree.overwrite(workspacePath, JSON.stringify(config, undefined, 2)); -} - -const workspacePath = '/angular.json'; - -describe('Migration to version 12', () => { - describe('Migrate workspace config', () => { - const schematicRunner = new SchematicTestRunner( - 'migrations', - require.resolve('../migration-collection.json'), - ); - - let tree: UnitTestTree; - - beforeEach(async () => { - tree = new UnitTestTree(new EmptyTree()); - tree = await schematicRunner - .runExternalSchematicAsync( - require.resolve('../../collection.json'), - 'ng-new', - { - name: 'migration-test', - version: '1.2.3', - directory: '.', - }, - tree, - ) - .toPromise(); - - tree.overwrite( - 'angular.json', - tree.readContent('angular.json').replace(/development/g, 'production'), - ); - }); - - describe('i18n configuration', () => { - function getI18NConfig(localId: string): object { - return { - outputPath: `dist/my-project/${localId}/`, - i18nFile: `src/locale/messages.${localId}.xlf`, - i18nFormat: 'xlf', - i18nLocale: localId, - }; - } - - describe('when i18n builder options are set', () => { - it(`should add '@angular/localize' as a dependency`, async () => { - const config = getWorkspaceTargets(tree); - config.build.options = getI18NConfig('fr'); - config.build.configurations.de = getI18NConfig('de'); - updateWorkspaceTargets(tree, config); - - const tree2 = await schematicRunner - .runSchematicAsync('remove-deprecated-i18n-options', {}, tree.branch()) - .toPromise(); - const { dependencies } = JSON.parse(tree2.readContent('/package.json')); - expect(dependencies['@angular/localize']).toBe(latestVersions.Angular); - }); - - it(`should add 'localize' option in configuration`, async () => { - let config = getWorkspaceTargets(tree); - config.build.options.aot = false; - config.build.options = getI18NConfig('fr'); - config.build.configurations.de = getI18NConfig('de'); - updateWorkspaceTargets(tree, config); - - const tree2 = await schematicRunner - .runSchematicAsync('remove-deprecated-i18n-options', {}, tree.branch()) - .toPromise(); - config = getWorkspaceTargets(tree2).build; - expect(config.options.localize).toEqual(['fr']); - expect(config.configurations.de.localize).toEqual(['de']); - }); - - it('should remove deprecated i18n options', async () => { - let config = getWorkspaceTargets(tree); - config.build.options.aot = false; - config.build.options = getI18NConfig('fr'); - config.build.configurations.de = getI18NConfig('de'); - updateWorkspaceTargets(tree, config); - - const tree2 = await schematicRunner - .runSchematicAsync('remove-deprecated-i18n-options', {}, tree.branch()) - .toPromise(); - config = getWorkspaceTargets(tree2).build; - expect(config.options.i18nFormat).toBeUndefined(); - expect(config.options.i18nFile).toBeUndefined(); - expect(config.options.i18nLocale).toBeUndefined(); - expect(config.configurations.de.i18nFormat).toBeUndefined(); - expect(config.configurations.de.i18nFile).toBeUndefined(); - expect(config.configurations.de.i18nLocale).toBeUndefined(); - }); - - it('should remove baseHref option when used with i18n options and no main base href', async () => { - let config = getWorkspaceTargets(tree); - config.build.configurations.fr = { ...getI18NConfig('fr'), baseHref: '/fr/' }; - config.build.configurations.de = { ...getI18NConfig('de'), baseHref: '/abc/' }; - updateWorkspaceTargets(tree, config); - - const tree2 = await schematicRunner - .runSchematicAsync('remove-deprecated-i18n-options', {}, tree.branch()) - .toPromise(); - config = getWorkspaceTargets(tree2).build; - expect(config.configurations.fr.baseHref).toBeUndefined(); - expect(config.configurations.de.baseHref).toBeUndefined(); - }); - - it('should remove outputPath option when used with i18n options and contains locale code', async () => { - let config = getWorkspaceTargets(tree); - config.build.options.outputPath = 'dist'; - config.build.configurations.fr = { ...getI18NConfig('fr'), outputPath: 'dist/fr' }; - config.build.configurations.de = { ...getI18NConfig('de'), outputPath: '/dist/de/' }; - updateWorkspaceTargets(tree, config); - - const tree2 = await schematicRunner - .runSchematicAsync('remove-deprecated-i18n-options', {}, tree.branch()) - .toPromise(); - config = getWorkspaceTargets(tree2).build; - expect(config.configurations.fr.outputPath).toBe('dist'); - expect(config.configurations.de.outputPath).toBe('/dist'); - }); - - it('should keep old i18n options except format when output path is not supported', async () => { - let config = getWorkspaceTargets(tree); - config.build.options.outputPath = 'dist'; - config.build.configurations.fr = { ...getI18NConfig('fr'), outputPath: 'dist/abc' }; - config.build.configurations.de = { ...getI18NConfig('de'), outputPath: '/dist/123' }; - updateWorkspaceTargets(tree, config); - - const tree2 = await schematicRunner - .runSchematicAsync('remove-deprecated-i18n-options', {}, tree.branch()) - .toPromise(); - config = getWorkspaceTargets(tree2).build; - expect(config.configurations.fr.outputPath).toBe('dist/abc'); - expect(config.configurations.fr.i18nFormat).toBeUndefined(); - expect(config.configurations.fr.i18nFile).toBeDefined(); - expect(config.configurations.fr.i18nLocale).toBe('fr'); - expect(config.configurations.de.outputPath).toBe('/dist/123'); - expect(config.configurations.de.i18nFormat).toBeUndefined(); - expect(config.configurations.de.i18nFile).toBeDefined(); - expect(config.configurations.de.i18nLocale).toBe('de'); - }); - - it('should keep baseHref option when not used with i18n options', async () => { - let config = getWorkspaceTargets(tree); - config.build.options = getI18NConfig('fr'); - config.build.configurations.de = getI18NConfig('de'); - config.build.configurations.staging = { baseHref: '/de/' }; - updateWorkspaceTargets(tree, config); - - const tree2 = await schematicRunner - .runSchematicAsync('remove-deprecated-i18n-options', {}, tree.branch()) - .toPromise(); - config = getWorkspaceTargets(tree2).build; - expect(config.configurations.staging.baseHref).toBe('/de/'); - }); - - it('should keep baseHref options when used with i18n options and main baseHref option', async () => { - let config = getWorkspaceTargets(tree); - config.build.options = { baseHref: '/my-app/' }; - config.build.configurations.de = { ...getI18NConfig('de'), baseHref: '/de/' }; - updateWorkspaceTargets(tree, config); - - const tree2 = await schematicRunner - .runSchematicAsync('remove-deprecated-i18n-options', {}, tree.branch()) - .toPromise(); - config = getWorkspaceTargets(tree2).build; - expect(config.options.baseHref).toBe('/my-app/'); - expect(config.configurations.de.baseHref).toBe('/'); - }); - - it('should remove deprecated extract-i18n options', async () => { - let config = getWorkspaceTargets(tree); - config['extract-i18n'].options.i18nFormat = 'xmb'; - config['extract-i18n'].options.i18nLocale = 'en-GB'; - updateWorkspaceTargets(tree, config); - - const tree2 = await schematicRunner - .runSchematicAsync('remove-deprecated-i18n-options', {}, tree.branch()) - .toPromise(); - config = getWorkspaceTargets(tree2)['extract-i18n']; - expect(config.options.i18nFormat).toBeUndefined(); - expect(config.options.i18nLocale).toBeUndefined(); - expect(config.options.format).toBe('xmb'); - }); - - it(`should add i18n 'sourceLocale' project config when 'extract-i18n' 'i18nLocale' is defined`, async () => { - const config = getWorkspaceTargets(tree); - config.build.options.aot = false; - config.build.options = getI18NConfig('fr'); - config['extract-i18n'].options.i18nLocale = 'en-GB'; - config.build.configurations.de = getI18NConfig('de'); - updateWorkspaceTargets(tree, config); - - const tree2 = await schematicRunner - .runSchematicAsync('remove-deprecated-i18n-options', {}, tree.branch()) - .toPromise(); - const projectConfig = JSON.parse(tree2.readContent(workspacePath)).projects[ - 'migration-test' - ]; - expect(projectConfig.i18n.sourceLocale).toBe('en-GB'); - expect(projectConfig.i18n.locales).toBeDefined(); - }); - - it(`should add i18n 'locales' project config`, async () => { - const config = getWorkspaceTargets(tree); - config.build.configurations.fr = getI18NConfig('fr'); - config.build.configurations.de = getI18NConfig('de'); - updateWorkspaceTargets(tree, config); - - const tree2 = await schematicRunner - .runSchematicAsync('remove-deprecated-i18n-options', {}, tree.branch()) - .toPromise(); - const projectConfig = JSON.parse(tree2.readContent(workspacePath)).projects[ - 'migration-test' - ]; - expect(projectConfig.i18n.sourceLocale).toBeUndefined(); - expect(projectConfig.i18n.locales).toEqual({ - de: { translation: 'src/locale/messages.de.xlf', baseHref: '' }, - fr: { translation: 'src/locale/messages.fr.xlf', baseHref: '' }, - }); - }); - - it(`should add i18n 'locales' project config when using baseHref options`, async () => { - const config = getWorkspaceTargets(tree); - config.build.configurations.fr = { ...getI18NConfig('fr'), baseHref: '/fr/' }; - config.build.configurations.de = { ...getI18NConfig('de'), baseHref: '/abc/' }; - updateWorkspaceTargets(tree, config); - - const tree2 = await schematicRunner - .runSchematicAsync('remove-deprecated-i18n-options', {}, tree.branch()) - .toPromise(); - const projectConfig = JSON.parse(tree2.readContent(workspacePath)).projects[ - 'migration-test' - ]; - expect(projectConfig.i18n.sourceLocale).toBeUndefined(); - expect(projectConfig.i18n.locales).toEqual({ - de: { translation: 'src/locale/messages.de.xlf', baseHref: '/abc/' }, - fr: 'src/locale/messages.fr.xlf', - }); - }); - - it(`should add i18n 'locales' project config when using baseHref options and main base href`, async () => { - const config = getWorkspaceTargets(tree); - config.build.options = { baseHref: '/my-app/' }; - config.build.configurations.fr = { ...getI18NConfig('fr'), baseHref: '/fr/' }; - config.build.configurations.de = { ...getI18NConfig('de'), baseHref: '/abc/' }; - updateWorkspaceTargets(tree, config); - - const tree2 = await schematicRunner - .runSchematicAsync('remove-deprecated-i18n-options', {}, tree.branch()) - .toPromise(); - const projectConfig = JSON.parse(tree2.readContent(workspacePath)).projects[ - 'migration-test' - ]; - expect(projectConfig.i18n.sourceLocale).toBeUndefined(); - expect(projectConfig.i18n.locales).toEqual({ - de: { translation: 'src/locale/messages.de.xlf', baseHref: '/abc/' }, - fr: 'src/locale/messages.fr.xlf', - }); - }); - }); - - describe('when i18n builder options are not set', () => { - it(`should not add 'localize' option`, async () => { - const tree2 = await schematicRunner - .runSchematicAsync('remove-deprecated-i18n-options', {}, tree.branch()) - .toPromise(); - const config = getWorkspaceTargets(tree2).build; - expect(config.options.localize).toBeUndefined(); - expect(config.configurations.production.localize).toBeUndefined(); - }); - - it('should not add i18n project config', async () => { - const tree2 = await schematicRunner - .runSchematicAsync('remove-deprecated-i18n-options', {}, tree.branch()) - .toPromise(); - const projectConfig = JSON.parse(tree2.readContent(workspacePath)).projects[ - 'migration-test' - ]; - expect(projectConfig.i18n).toBeUndefined(); - }); - }); - }); - }); -}); diff --git a/packages/schematics/angular/migrations/update-12/update-lazy-module-paths.ts b/packages/schematics/angular/migrations/update-12/update-lazy-module-paths.ts deleted file mode 100644 index 74ee406cbfba..000000000000 --- a/packages/schematics/angular/migrations/update-12/update-lazy-module-paths.ts +++ /dev/null @@ -1,77 +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.io/license - */ - -import { DirEntry, Rule, UpdateRecorder } from '@angular-devkit/schematics'; -import * as ts from '../../third_party/github.com/Microsoft/TypeScript/lib/typescript'; - -function* visit(directory: DirEntry): IterableIterator { - for (const path of directory.subfiles) { - if (path.endsWith('.ts') && !path.endsWith('.d.ts')) { - const entry = directory.file(path); - if (entry) { - const content = entry.content; - if (content.includes('loadChildren')) { - const source = ts.createSourceFile( - entry.path, - content.toString().replace(/^\uFEFF/, ''), - ts.ScriptTarget.Latest, - true, - ); - - yield source; - } - } - } - } - - for (const path of directory.subdirs) { - if (path === 'node_modules' || path.startsWith('.')) { - continue; - } - - yield* visit(directory.dir(path)); - } -} - -export default function (): Rule { - return (tree) => { - for (const sourceFile of visit(tree.root)) { - let recorder: UpdateRecorder | undefined; - - ts.forEachChild(sourceFile, function analyze(node) { - if ( - ts.isPropertyAssignment(node) && - (ts.isIdentifier(node.name) || ts.isStringLiteral(node.name)) && - node.name.text === 'loadChildren' && - ts.isStringLiteral(node.initializer) - ) { - const valueNode = node.initializer; - const parts = valueNode.text.split('#'); - const path = parts[0]; - const moduleName = parts[1] || 'default'; - - const fix = `() => import('${path}').then(m => m.${moduleName})`; - - if (!recorder) { - recorder = tree.beginUpdate(sourceFile.fileName); - } - - const index = valueNode.getStart(); - const length = valueNode.getWidth(); - recorder.remove(index, length).insertLeft(index, fix); - } - - ts.forEachChild(node, analyze); - }); - - if (recorder) { - tree.commitUpdate(recorder); - } - } - }; -} diff --git a/packages/schematics/angular/migrations/update-12/update-lazy-module-paths_spec.ts b/packages/schematics/angular/migrations/update-12/update-lazy-module-paths_spec.ts deleted file mode 100644 index 57baa0b43ac9..000000000000 --- a/packages/schematics/angular/migrations/update-12/update-lazy-module-paths_spec.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.io/license - */ - -import { normalize, virtualFs } from '@angular-devkit/core'; -import { EmptyTree } from '@angular-devkit/schematics'; -import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; - -describe('Migration to version 8', () => { - const schematicRunner = new SchematicTestRunner( - 'migrations', - require.resolve('../migration-collection.json'), - ); - - let tree: UnitTestTree; - - const lazyRoutePath = normalize('src/lazy-route.ts'); - const lazyRoute = virtualFs.stringToFileBuffer(` - import { Route } from '@angular/router'; - const routes: Array = [ - { - path: '', - loadChildren: './lazy/lazy.module#LazyModule' - } - ]; - `); - - const lazyChildRoute = virtualFs.stringToFileBuffer(` - import { Route } from '@angular/router'; - const routes: Array = [ - { - path: '', - children: [{ - path: 'child', - loadChildren: './lazy/lazy.module#LazyModule' - }] - } - ]; - `); - - describe('Migration to import() style lazy routes', () => { - beforeEach(async () => { - tree = new UnitTestTree(new EmptyTree()); - tree.create('/package.json', JSON.stringify({})); - }); - - it('should replace the module path string', async () => { - tree.create(lazyRoutePath, Buffer.from(lazyRoute)); - - await schematicRunner.runSchematicAsync('lazy-loading-string-syntax', {}, tree).toPromise(); - await schematicRunner.engine.executePostTasks().toPromise(); - - const routes = tree.readContent(lazyRoutePath); - - expect(routes).not.toContain('./lazy/lazy.module#LazyModule'); - expect(routes).toContain( - `loadChildren: () => import('./lazy/lazy.module').then(m => m.LazyModule)`, - ); - }); - - it('should replace the module path string in a child path', async () => { - tree.create(lazyRoutePath, Buffer.from(lazyChildRoute)); - - await schematicRunner.runSchematicAsync('lazy-loading-string-syntax', {}, tree).toPromise(); - await schematicRunner.engine.executePostTasks().toPromise(); - - const routes = tree.readContent(lazyRoutePath); - - expect(routes).not.toContain('./lazy/lazy.module#LazyModule'); - - expect(routes).toContain( - `loadChildren: () => import('./lazy/lazy.module').then(m => m.LazyModule)`, - ); - }); - - it('should replace the module path string when file has BOM', async () => { - tree.create(lazyRoutePath, '\uFEFF' + Buffer.from(lazyRoute).toString()); - - await schematicRunner.runSchematicAsync('lazy-loading-string-syntax', {}, tree).toPromise(); - await schematicRunner.engine.executePostTasks().toPromise(); - - const routes = tree.readContent(lazyRoutePath); - - expect(routes).not.toContain('./lazy/lazy.module#LazyModule'); - expect(routes).toContain( - `loadChildren: () => import('./lazy/lazy.module').then(m => m.LazyModule)`, - ); - }); - }); -}); diff --git a/packages/schematics/angular/migrations/update-12/update-web-workers.ts b/packages/schematics/angular/migrations/update-12/update-web-workers.ts deleted file mode 100644 index cbe0ee5b9390..000000000000 --- a/packages/schematics/angular/migrations/update-12/update-web-workers.ts +++ /dev/null @@ -1,101 +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.io/license - */ - -import { DirEntry, Rule, UpdateRecorder } from '@angular-devkit/schematics'; -import * as ts from '../../third_party/github.com/Microsoft/TypeScript/lib/typescript'; - -function* visit(directory: DirEntry): IterableIterator { - for (const path of directory.subfiles) { - if (path.endsWith('.ts') && !path.endsWith('.d.ts')) { - const entry = directory.file(path); - if (entry) { - const content = entry.content; - if (content.includes('Worker')) { - const source = ts.createSourceFile( - entry.path, - // Remove UTF-8 BOM if present - // TypeScript expects the BOM to be stripped prior to parsing - content.toString().replace(/^\uFEFF/, ''), - ts.ScriptTarget.Latest, - true, - ); - - yield source; - } - } - } - } - - for (const path of directory.subdirs) { - if (path === 'node_modules' || path.startsWith('.')) { - continue; - } - - yield* visit(directory.dir(path)); - } -} - -function hasPropertyWithValue(node: ts.Expression, name: string, value: unknown): boolean { - if (!ts.isObjectLiteralExpression(node)) { - return false; - } - - for (const property of node.properties) { - if (!ts.isPropertyAssignment(property)) { - continue; - } - if (!ts.isIdentifier(property.name) || property.name.text !== 'type') { - continue; - } - if (ts.isStringLiteralLike(property.initializer)) { - return property.initializer.text === 'module'; - } - } - - return false; -} - -export default function (): Rule { - return (tree) => { - for (const sourceFile of visit(tree.root)) { - let recorder: UpdateRecorder | undefined; - - ts.forEachChild(sourceFile, function analyze(node) { - // Only modify code in the form of `new Worker('./app.worker', { type: 'module' })`. - // `worker-plugin` required the second argument to be an object literal with type=module - if ( - ts.isNewExpression(node) && - ts.isIdentifier(node.expression) && - node.expression.text === 'Worker' && - node.arguments?.length === 2 && - ts.isStringLiteralLike(node.arguments[0]) && - hasPropertyWithValue(node.arguments[1], 'type', 'module') - ) { - const valueNode = node.arguments[0] as ts.StringLiteralLike; - - // Webpack expects a URL constructor: https://webpack.js.org/guides/web-workers/ - const fix = `new URL('${valueNode.text}', import.meta.url)`; - - if (!recorder) { - recorder = tree.beginUpdate(sourceFile.fileName); - } - - const index = valueNode.getStart(); - const length = valueNode.getWidth(); - recorder.remove(index, length).insertLeft(index, fix); - } - - ts.forEachChild(node, analyze); - }); - - if (recorder) { - tree.commitUpdate(recorder); - } - } - }; -} diff --git a/packages/schematics/angular/migrations/update-12/update-web-workers_spec.ts b/packages/schematics/angular/migrations/update-12/update-web-workers_spec.ts deleted file mode 100644 index 10070fb97471..000000000000 --- a/packages/schematics/angular/migrations/update-12/update-web-workers_spec.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.io/license - */ - -import { EmptyTree } from '@angular-devkit/schematics'; -import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; - -describe('Migration to update Web Workers for Webpack 5', () => { - const schematicRunner = new SchematicTestRunner( - 'migrations', - require.resolve('../migration-collection.json'), - ); - - let tree: UnitTestTree; - - const workerConsumerPath = 'src/consumer.ts'; - const workerConsumerContent = ` - import { enableProdMode } from '@angular/core'; - import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; - import { AppModule } from './app/app.module'; - import { environment } from './environments/environment'; - if (environment.production) { enableProdMode(); } - platformBrowserDynamic().bootstrapModule(AppModule).catch(err => console.error(err)); - - const worker = new Worker('./app/app.worker', { type: 'module' }); - worker.onmessage = ({ data }) => { - console.log('page got message:', data); - }; - worker.postMessage('hello'); - `; - - beforeEach(async () => { - tree = new UnitTestTree(new EmptyTree()); - tree.create('/package.json', JSON.stringify({})); - }); - - it('should replace the string path argument with a URL constructor', async () => { - tree.create(workerConsumerPath, workerConsumerContent); - - await schematicRunner.runSchematicAsync('update-web-workers-webpack-5', {}, tree).toPromise(); - await schematicRunner.engine.executePostTasks().toPromise(); - - const consumer = tree.readContent(workerConsumerPath); - - expect(consumer).not.toContain(`new Worker('./app/app.worker'`); - expect(consumer).toContain( - `new Worker(new URL('./app/app.worker', import.meta.url), { type: 'module' });`, - ); - }); - - it('should not replace the first argument if arguments types are invalid', async () => { - tree.create(workerConsumerPath, workerConsumerContent.replace(`'./app/app.worker'`, '42')); - - await schematicRunner.runSchematicAsync('update-web-workers-webpack-5', {}, tree).toPromise(); - await schematicRunner.engine.executePostTasks().toPromise(); - - const consumer = tree.readContent(workerConsumerPath); - - expect(consumer).toContain(`new Worker(42`); - expect(consumer).not.toContain( - `new Worker(new URL('42', import.meta.url), { type: 'module' });`, - ); - }); - - it('should not replace the first argument if type value is not "module"', async () => { - tree.create(workerConsumerPath, workerConsumerContent.replace(`type: 'module'`, `type: 'xyz'`)); - - await schematicRunner.runSchematicAsync('update-web-workers-webpack-5', {}, tree).toPromise(); - await schematicRunner.engine.executePostTasks().toPromise(); - - const consumer = tree.readContent(workerConsumerPath); - - expect(consumer).toContain(`new Worker('./app/app.worker'`); - expect(consumer).not.toContain(`new Worker(new URL('42', import.meta.url), { type: 'xyz' });`); - }); - - it('should replace the module path string when file has BOM', async () => { - tree.create(workerConsumerPath, '\uFEFF' + workerConsumerContent); - - await schematicRunner.runSchematicAsync('update-web-workers-webpack-5', {}, tree).toPromise(); - await schematicRunner.engine.executePostTasks().toPromise(); - - const consumer = tree.readContent(workerConsumerPath); - - expect(consumer).not.toContain(`new Worker('./app/app.worker'`); - expect(consumer).toContain( - `new Worker(new URL('./app/app.worker', import.meta.url), { type: 'module' });`, - ); - }); -}); diff --git a/packages/schematics/angular/migrations/update-12/update-zonejs.ts b/packages/schematics/angular/migrations/update-12/update-zonejs.ts deleted file mode 100644 index 41def83155ae..000000000000 --- a/packages/schematics/angular/migrations/update-12/update-zonejs.ts +++ /dev/null @@ -1,90 +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.io/license - */ - -import { join } from '@angular-devkit/core'; -import { DirEntry, Rule } from '@angular-devkit/schematics'; -import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks'; -import { addPackageJsonDependency, getPackageJsonDependency } from '../../utility/dependencies'; - -const fileExtensionRegexp = /\.(([cm]?j|t)sx?)$/; - -function* visitJavaScriptFiles(directory: DirEntry): IterableIterator { - for (const path of directory.subfiles) { - if (!fileExtensionRegexp.test(path)) { - continue; - } - - yield join(directory.path, path); - } - - for (const path of directory.subdirs) { - if (path === 'node_modules' || path.startsWith('.') || path === 'dist') { - continue; - } - - yield* visitJavaScriptFiles(directory.dir(path)); - } -} - -export default function (): Rule { - return (tree, context) => { - const current = getPackageJsonDependency(tree, 'zone.js'); - if (current && current.version !== '~0.11.4') { - addPackageJsonDependency(tree, { - type: current.type, - name: 'zone.js', - version: '~0.11.4', - overwrite: true, - }); - - context.addTask(new NodePackageInstallTask()); - } - - for (const path of visitJavaScriptFiles(tree.root)) { - const buffer = tree.read(path); - if (!buffer) { - return; - } - - const content = buffer.toString(); - if (!content.includes('zone.js/dist/')) { - continue; - } - - // RegExp that replaces - // - import 'zone.js/dist/zone-testing' -> import 'zone.js/testing' - // - require('zone.js/dist/zone-testing') -> require('zone.js/testing') - // - import 'zone.js/dist/zone' -> import 'zone.js' - // - require('zone.js/dist/zone') -> require('zone.js') - // - import 'zone.js/dist/zone-error' -> import 'zone.js/plugins/zone-error' - // - require('zone.js/dist/zone-error') -> require('zone.js/plugins/zone-error') - tree.overwrite( - path, - content.replace( - /(?<=(?:require\s*\(|import\s+)['"]zone\.js)\/dist\/zone-?\w*(?=['"]\)?)/g, - (match) => { - switch (match) { - case '/dist/zone': - case '/dist/zone-evergreen': - return ''; - case '/dist/zone-testing': - case '/dist/zone-evergreen-testing': - return '/testing'; - case '/dist/zone-node': - return '/node'; - case '/dist/zone-mix': - return '/mix'; - default: - return `/plugins${match.substr(5)}`; - } - }, - ), - ); - } - }; -} diff --git a/packages/schematics/angular/migrations/update-12/update-zonejs_spec.ts b/packages/schematics/angular/migrations/update-12/update-zonejs_spec.ts deleted file mode 100644 index 10fff9a10b4b..000000000000 --- a/packages/schematics/angular/migrations/update-12/update-zonejs_spec.ts +++ /dev/null @@ -1,153 +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.io/license - */ - -import { EmptyTree } from '@angular-devkit/schematics'; -import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; -import { getPackageJsonDependency } from '../../utility/dependencies'; - -const schematicName = 'update-zonejs'; -describe(`Migration to update 'zone.js' to 0.11.x. ${schematicName}`, () => { - const schematicRunner = new SchematicTestRunner( - 'migrations', - require.resolve('../migration-collection.json'), - ); - - let tree: UnitTestTree; - beforeEach(() => { - tree = new UnitTestTree(new EmptyTree()); - tree.create( - '/package.json', - JSON.stringify({ 'dependencies': { 'zone.js': '~0.10.0' } }, undefined, 2), - ); - }); - - it(`should update 'zone.js' dependency in 'package.json'`, async () => { - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - expect(getPackageJsonDependency(newTree, 'zone.js')?.version).toBe('~0.11.4'); - }); - - it(`should update 'zone.js/dist/zone' import`, async () => { - tree.create( - 'file.ts', - ` - import 'zone.js'; - import 'zone.js/dist/zone'; - import "zone.js/dist/zone"; - // import "zone.js/dist/zone"; - `, - ); - - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - expect(newTree.readContent('file.ts')).toBe(` - import 'zone.js'; - import 'zone.js'; - import "zone.js"; - // import "zone.js"; - `); - }); - - it(`should update 'zone.js/dist/zone' require`, async () => { - tree.create( - 'file.ts', - ` - require('zone.js'); - require('zone.js/dist/zone'); - require("zone.js/dist/zone"); - // require("zone.js/dist/zone"); - `, - ); - - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - expect(newTree.readContent('file.ts')).toBe(` - require('zone.js'); - require('zone.js'); - require("zone.js"); - // require("zone.js"); - `); - }); - - it(`should update 'zone.js/dist/zone-error' import`, async () => { - tree.create( - 'file.ts', - ` - import 'zone.js/plugins/zone-error'; - import 'zone.js/dist/zone-error'; - import "zone.js/dist/zone-error"; - // import "zone.js/dist/zone-error"; - `, - ); - - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - expect(newTree.readContent('file.ts')).toBe(` - import 'zone.js/plugins/zone-error'; - import 'zone.js/plugins/zone-error'; - import "zone.js/plugins/zone-error"; - // import "zone.js/plugins/zone-error"; - `); - }); - - it(`should update 'zone.js/dist/zone-error' require`, async () => { - tree.create( - 'file.ts', - ` - require('zone.js/plugins/zone-error'); - require('zone.js/dist/zone-error'); - require("zone.js/dist/zone-error"); - // require("zone.js/dist/zone-error"); - `, - ); - - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - expect(newTree.readContent('file.ts')).toBe(` - require('zone.js/plugins/zone-error'); - require('zone.js/plugins/zone-error'); - require("zone.js/plugins/zone-error"); - // require("zone.js/plugins/zone-error"); - `); - }); - - it(`should update 'zone.js/dist/zone-testing' import`, async () => { - tree.create( - 'file.ts', - ` - import 'zone.js/testing'; - import 'zone.js/dist/zone-testing'; - import "zone.js/dist/zone-testing"; - // import "zone.js/dist/zone-testing"; - `, - ); - - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - expect(newTree.readContent('file.ts')).toBe(` - import 'zone.js/testing'; - import 'zone.js/testing'; - import "zone.js/testing"; - // import "zone.js/testing"; - `); - }); - - it(`should update 'zone.js/dist/zone-testing' require`, async () => { - tree.create( - 'file.ts', - ` - require('zone.js/testing'); - require('zone.js/dist/zone-testing'); - require("zone.js/dist/zone-testing"); - // require("zone.js/dist/zone-testing"); - `, - ); - - const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise(); - expect(newTree.readContent('file.ts')).toBe(` - require('zone.js/testing'); - require('zone.js/testing'); - require("zone.js/testing"); - // require("zone.js/testing"); - `); - }); -}); diff --git a/tests/legacy-cli/e2e/tests/update/update-multiple-versions.ts b/tests/legacy-cli/e2e/tests/update/update-multiple-versions.ts new file mode 100644 index 000000000000..deffd21ef365 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/update/update-multiple-versions.ts @@ -0,0 +1,35 @@ +import { createProjectFromAsset } from '../../utils/assets'; +import { installWorkspacePackages, setRegistry } from '../../utils/packages'; +import { ng } from '../../utils/process'; +import { isPrereleaseCli } from '../../utils/project'; +import { expectToFail } from '../../utils/utils'; + +export default async function () { + try { + await createProjectFromAsset('9.0-project', true, true); + await setRegistry(false); + await installWorkspacePackages(); + + await setRegistry(true); + const extraArgs = ['--force']; + if (isPrereleaseCli()) { + extraArgs.push('--next'); + } + + const { message } = await expectToFail(() => + ng('update', '@angular/cli', '--force', ...extraArgs), + ); + if ( + !message.includes( + `Updating multiple major versions of '@angular/cli' at once is not supported`, + ) + ) { + console.error(message); + throw new Error( + `Expected error message to include "Updating multiple major versions of '@angular/cli' at once is not supported" but didn't.`, + ); + } + } finally { + await setRegistry(true); + } +}