From 89360ab4876db6fcf92f5508971da49228543e08 Mon Sep 17 00:00:00 2001 From: Alan Agius Date: Tue, 4 May 2021 09:57:06 +0200 Subject: [PATCH] fix(@schematics/angular): only run `emitDecoratorMetadata` removal migration in safe workspaces Removal of `emitDecoratorMetadata` might cause runtime errors on projects which don't use the Angular Compiler to compile TypeScript code and therefore dependent on Decorators metadata during runtime. One such example of these builders is `@nrwl/jest`. (cherry picked from commit 7521a8727c2586bafd2b0fd725545fb1458e30b4) --- .../remove-emit-decorator-metadata.ts | 22 ++++++- .../remove-emit-decorator-metadata_spec.ts | 64 +++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) 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 index 72dd09cde96a..cc4716f1497e 100644 --- a/packages/schematics/angular/migrations/update-12/remove-emit-decorator-metadata.ts +++ b/packages/schematics/angular/migrations/update-12/remove-emit-decorator-metadata.ts @@ -9,6 +9,7 @@ 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) { @@ -29,7 +30,26 @@ function* visitJsonFiles(directory: DirEntry): IterableIterator { } export default function (): Rule { - return (tree) => { + 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"')) { 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 index 93f1a3a351f5..d2a9a7b4207f 100644 --- 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 @@ -9,6 +9,7 @@ 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'; @@ -26,6 +27,28 @@ describe('Migration to remove "emitDecoratorMetadata" compiler option', () => { 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 () => { @@ -88,4 +111,45 @@ describe('Migration to remove "emitDecoratorMetadata" compiler option', () => { 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(); + }); });