From 7415c2ead508aa7537e4061a6b89291f0bb98413 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leosvel=20P=C3=A9rez=20Espinosa?= Date: Wed, 9 Mar 2022 10:24:45 +0000 Subject: [PATCH] cleanup(angular): move the angular cli migration generator from @nrwl/workspace to @nrwl/angular --- e2e/angular-core/src/ng-add.test.ts | 24 +++- e2e/utils/index.ts | 4 +- packages/angular/generators.json | 14 ++- packages/angular/src/generators/init/init.ts | 16 ++- .../angular/src/generators/init/schema.d.ts | 4 +- .../angular/src/generators/init/schema.json | 5 + .../migrate-from-angular-cli.spec.ts.snap} | 29 ++++- .../angular/src/generators/ng-add/compat.ts | 4 + .../decorate-angular-cli.js__tmpl__ | 69 ++++++++++++ .../files/prettier/__dot__prettierignore | 0 .../ng-add}/files/root/karma.conf.js__tmpl__ | 0 .../ng-add/files/root/libs}/__dot__gitkeep | 0 .../ng-add/files/root/nx.json__tmpl__ | 29 +++++ .../root/tools/schematics/__dot__gitkeep | 0 .../root/tools/tsconfig.tools.json__tmpl__ | 0 .../ng-add/migrate-from-angular-cli.spec.ts} | 64 ++++++----- .../ng-add/migrate-from-angular-cli.ts} | 106 ++++++++---------- .../src/generators/ng-add/ng-add.spec.ts | 36 ++++++ .../angular/src/generators/ng-add/ng-add.ts | 18 +++ .../angular/src/generators/ng-add/schema.d.ts | 19 ++++ .../angular/src/generators/ng-add/schema.json | 71 ++++++++++++ packages/workspace/generators.json | 14 --- .../workspace/src/generators/init/schema.d.ts | 7 -- .../workspace/src/generators/init/schema.json | 35 ------ 24 files changed, 409 insertions(+), 159 deletions(-) rename packages/{workspace/src/generators/init/__snapshots__/init.spec.ts.snap => angular/src/generators/ng-add/__snapshots__/migrate-from-angular-cli.spec.ts.snap} (90%) create mode 100644 packages/angular/src/generators/ng-add/compat.ts create mode 100644 packages/angular/src/generators/ng-add/files/decorate-angular-cli/decorate-angular-cli.js__tmpl__ rename packages/{workspace/src/generators/init => angular/src/generators/ng-add}/files/prettier/__dot__prettierignore (100%) rename packages/{workspace/src/generators/init => angular/src/generators/ng-add}/files/root/karma.conf.js__tmpl__ (100%) rename packages/{workspace/src/generators/init/files/root/tools/schematics => angular/src/generators/ng-add/files/root/libs}/__dot__gitkeep (100%) create mode 100644 packages/angular/src/generators/ng-add/files/root/nx.json__tmpl__ create mode 100644 packages/angular/src/generators/ng-add/files/root/tools/schematics/__dot__gitkeep rename packages/{workspace/src/generators/init => angular/src/generators/ng-add}/files/root/tools/tsconfig.tools.json__tmpl__ (100%) rename packages/{workspace/src/generators/init/init.spec.ts => angular/src/generators/ng-add/migrate-from-angular-cli.spec.ts} (91%) rename packages/{workspace/src/generators/init/init.ts => angular/src/generators/ng-add/migrate-from-angular-cli.ts} (93%) create mode 100644 packages/angular/src/generators/ng-add/ng-add.spec.ts create mode 100644 packages/angular/src/generators/ng-add/ng-add.ts create mode 100644 packages/angular/src/generators/ng-add/schema.d.ts create mode 100644 packages/angular/src/generators/ng-add/schema.json delete mode 100644 packages/workspace/src/generators/init/schema.d.ts delete mode 100644 packages/workspace/src/generators/init/schema.json diff --git a/e2e/angular-core/src/ng-add.test.ts b/e2e/angular-core/src/ng-add.test.ts index b31a8cc289c90f..3a3965f9a90d54 100644 --- a/e2e/angular-core/src/ng-add.test.ts +++ b/e2e/angular-core/src/ng-add.test.ts @@ -156,12 +156,28 @@ describe('convert Angular CLI workspace to an Nx workspace', () => { npmScope: 'projscope', affected: { defaultBase: 'main' }, implicitDependencies: { - 'angular.json': '*', - 'package.json': '*', - 'tslint.json': '*', + 'package.json': { + dependencies: '*', + devDependencies: '*', + }, '.eslintrc.json': '*', 'tsconfig.base.json': '*', - 'nx.json': '*', + }, + tasksRunnerOptions: { + default: { + runner: '@nrwl/workspace/tasks-runners/default', + options: { + cacheableOperations: ['build', 'lint', 'test', 'e2e'], + }, + }, + }, + targetDependencies: { + build: [ + { + target: 'build', + projects: 'dependencies', + }, + ], }, cli: { defaultCollection: '@nrwl/angular', packageManager }, defaultProject: project, diff --git a/e2e/utils/index.ts b/e2e/utils/index.ts index 5182081d3ad209..59fe941b0c30fc 100644 --- a/e2e/utils/index.ts +++ b/e2e/utils/index.ts @@ -422,8 +422,8 @@ export function runNgAdd( } ): string { try { - packageInstall('@nrwl/workspace'); - return execSync(`npx ng add @nrwl/workspace ${command}`, { + packageInstall('@nrwl/angular'); + return execSync(`npx ng add @nrwl/angular ${command}`, { cwd: tmpProjPath(), env: { ...(opts.env || process.env), NX_INVOKED_BY_RUNNER: undefined }, encoding: 'utf-8', diff --git a/packages/angular/generators.json b/packages/angular/generators.json index 57df82b7a1a36c..0e377225da0bd2 100644 --- a/packages/angular/generators.json +++ b/packages/angular/generators.json @@ -42,7 +42,6 @@ "factory": "./src/generators/init/init.compat#initSchematic", "schema": "./src/generators/init/schema.json", "description": "Initializes the @nrwl/angular plugin.", - "aliases": ["ng-add"], "hidden": true }, "karma": { @@ -86,6 +85,12 @@ "aliases": ["host"], "description": "Generate a Host Angular Micro Frontend Application." }, + "ng-add": { + "factory": "./src/generators/ng-add/compat", + "schema": "./src/generators/ng-add/schema.json", + "description": "Migrates an Angular CLI workspace to Nx or adds the Angular plugin to an Nx workspace.", + "hidden": true + }, "ngrx": { "factory": "./src/generators/ngrx/compat", "schema": "./src/generators/ngrx/schema.json", @@ -187,7 +192,6 @@ "factory": "./src/generators/init/init", "schema": "./src/generators/init/schema.json", "description": "Initializes the @nrwl/angular plugin.", - "aliases": ["ng-add"], "hidden": true }, "karma": { @@ -231,6 +235,12 @@ "aliases": ["host"], "description": "Generate a Host Angular Micro Frontend Application." }, + "ng-add": { + "factory": "./src/generators/ng-add/ng-add", + "schema": "./src/generators/ng-add/schema.json", + "description": "Migrates an Angular CLI workspace to Nx or adds the Angular plugin to an Nx workspace.", + "hidden": true + }, "ngrx": { "factory": "./src/generators/ngrx/ngrx", "schema": "./src/generators/ngrx/schema.json", diff --git a/packages/angular/src/generators/init/init.ts b/packages/angular/src/generators/init/init.ts index 4fe20199d2d93d..765ab78e52e04c 100755 --- a/packages/angular/src/generators/init/init.ts +++ b/packages/angular/src/generators/init/init.ts @@ -8,6 +8,7 @@ import { updateWorkspaceConfiguration, } from '@nrwl/devkit'; import { jestInitGenerator } from '@nrwl/jest'; +import { Linter } from '@nrwl/linter'; import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial'; import { setDefaultCollection } from '@nrwl/workspace/src/utilities/set-default-collection'; import { E2eTestRunner, UnitTestRunner } from '../../utils/test-runners'; @@ -22,8 +23,9 @@ import { Schema } from './schema'; export async function angularInitGenerator( host: Tree, - options: Schema + rawOptions: Schema ): Promise { + const options = normalizeOptions(rawOptions); setDefaults(host, options); addPostInstall(host); @@ -41,6 +43,18 @@ export async function angularInitGenerator( return runTasksInSerial(depsTask, unitTestTask, e2eTask); } +function normalizeOptions(options: Schema): Required { + return { + e2eTestRunner: options.e2eTestRunner ?? E2eTestRunner.Cypress, + linter: options.linter ?? Linter.EsLint, + skipFormat: options.skipFormat ?? false, + skipInstall: options.skipInstall ?? false, + skipPackageJson: options.skipPackageJson ?? false, + style: options.style ?? 'css', + unitTestRunner: options.unitTestRunner ?? UnitTestRunner.Jest, + }; +} + function setDefaults(host: Tree, options: Schema) { const workspace = readWorkspaceConfiguration(host); diff --git a/packages/angular/src/generators/init/schema.d.ts b/packages/angular/src/generators/init/schema.d.ts index 7186456f67111c..6564d978857c6a 100644 --- a/packages/angular/src/generators/init/schema.d.ts +++ b/packages/angular/src/generators/init/schema.d.ts @@ -3,11 +3,11 @@ import { E2eTestRunner, UnitTestRunner } from '../../utils/test-runners'; import type { Styles } from '../utils/types'; export interface Schema { - unitTestRunner: UnitTestRunner; + unitTestRunner?: UnitTestRunner; e2eTestRunner?: E2eTestRunner; skipFormat?: boolean; skipInstall?: boolean; style?: Styles; - linter: Exclude; + linter?: Exclude; skipPackageJson?: boolean; } diff --git a/packages/angular/src/generators/init/schema.json b/packages/angular/src/generators/init/schema.json index 35f86a50c32d8a..33eafa35524031 100644 --- a/packages/angular/src/generators/init/schema.json +++ b/packages/angular/src/generators/init/schema.json @@ -38,6 +38,7 @@ "description": "The file extension to be used for style files.", "type": "string", "default": "css", + "enum": ["css", "scss", "sass", "less"], "x-prompt": { "message": "Which stylesheet format would you like to use?", "type": "list", @@ -50,6 +51,10 @@ "value": "scss", "label": "SASS(.scss) [ http://sass-lang.com ]" }, + { + "value": "sass", + "label": "SASS(.sass) [ http://sass-lang.com ]" + }, { "value": "less", "label": "LESS [ http://lesscss.org ]" diff --git a/packages/workspace/src/generators/init/__snapshots__/init.spec.ts.snap b/packages/angular/src/generators/ng-add/__snapshots__/migrate-from-angular-cli.spec.ts.snap similarity index 90% rename from packages/workspace/src/generators/init/__snapshots__/init.spec.ts.snap rename to packages/angular/src/generators/ng-add/__snapshots__/migrate-from-angular-cli.spec.ts.snap index f9bd3a1ea1c714..d2a3cbe1c5c80c 100644 --- a/packages/workspace/src/generators/init/__snapshots__/init.spec.ts.snap +++ b/packages/angular/src/generators/ng-add/__snapshots__/migrate-from-angular-cli.spec.ts.snap @@ -167,13 +167,34 @@ Object { "defaultProject": "myApp", "implicitDependencies": Object { ".eslintrc.json": "*", - "angular.json": "*", - "nx.json": "*", - "package.json": "*", + "package.json": Object { + "dependencies": "*", + "devDependencies": "*", + }, "tsconfig.base.json": "*", - "tslint.json": "*", }, "npmScope": "my-app", + "targetDependencies": Object { + "build": Array [ + Object { + "projects": "dependencies", + "target": "build", + }, + ], + }, + "tasksRunnerOptions": Object { + "default": Object { + "options": Object { + "cacheableOperations": Array [ + "build", + "lint", + "test", + "e2e", + ], + }, + "runner": "@nrwl/workspace/tasks-runners/default", + }, + }, } `; diff --git a/packages/angular/src/generators/ng-add/compat.ts b/packages/angular/src/generators/ng-add/compat.ts new file mode 100644 index 00000000000000..bcc66b52aba73a --- /dev/null +++ b/packages/angular/src/generators/ng-add/compat.ts @@ -0,0 +1,4 @@ +import { convertNxGenerator } from '@nrwl/devkit'; +import { ngAddGenerator } from './ng-add'; + +export default convertNxGenerator(ngAddGenerator); diff --git a/packages/angular/src/generators/ng-add/files/decorate-angular-cli/decorate-angular-cli.js__tmpl__ b/packages/angular/src/generators/ng-add/files/decorate-angular-cli/decorate-angular-cli.js__tmpl__ new file mode 100644 index 00000000000000..bc81a83788969d --- /dev/null +++ b/packages/angular/src/generators/ng-add/files/decorate-angular-cli/decorate-angular-cli.js__tmpl__ @@ -0,0 +1,69 @@ +/** + * This file decorates the Angular CLI with the Nx CLI to enable features such as computation caching + * and faster execution of tasks. + * + * It does this by: + * + * - Patching the Angular CLI to warn you in case you accidentally use the undecorated ng command. + * - Symlinking the ng to nx command, so all commands run through the Nx CLI + * - Updating the package.json postinstall script to give you control over this script + * + * The Nx CLI decorates the Angular CLI, so the Nx CLI is fully compatible with it. + * Every command you run should work the same when using the Nx CLI, except faster. + * + * Because of symlinking you can still type `ng build/test/lint` in the terminal. The ng command, in this case, + * will point to nx, which will perform optimizations before invoking ng. So the Angular CLI is always invoked. + * The Nx CLI simply does some optimizations before invoking the Angular CLI. + * + * To opt out of this patch: + * - Replace occurrences of nx with ng in your package.json + * - Remove the script from your postinstall script in your package.json + * - Delete and reinstall your node_modules + */ + +const fs = require('fs'); +const os = require('os'); +const cp = require('child_process'); +const isWindows = os.platform() === 'win32'; +let output; +try { + output = require('@nrwl/workspace').output; +} catch (e) { + console.warn('Angular CLI could not be decorated to enable computation caching. Please ensure @nrwl/workspace is installed.'); + process.exit(0); +} + +/** + * Symlink of ng to nx, so you can keep using `ng build/test/lint` and still + * invoke the Nx CLI and get the benefits of computation caching. + */ +function symlinkNgCLItoNxCLI() { + try { + const ngPath = './node_modules/.bin/ng'; + const nxPath = './node_modules/.bin/nx'; + if (isWindows) { + /** + * This is the most reliable way to create symlink-like behavior on Windows. + * Such that it works in all shells and works with npx. + */ + ['', '.cmd', '.ps1'].forEach(ext => { + if (fs.existsSync(nxPath + ext)) fs.writeFileSync(ngPath + ext, fs.readFileSync(nxPath + ext)); + }); + } else { + // If unix-based, symlink + cp.execSync(`ln -sf ./nx ${ngPath}`); + } + } + catch(e) { + output.error({ title: 'Unable to create a symlink from the Angular CLI to the Nx CLI:' + e.message }); + throw e; + } +} + +try { + symlinkNgCLItoNxCLI(); + require('@nrwl/cli/lib/decorate-cli').decorateCli(); + output.log({ title: 'Angular CLI has been decorated to enable computation caching.' }); +} catch(e) { + output.error({ title: 'Decoration of the Angular CLI did not complete successfully' }); +} diff --git a/packages/workspace/src/generators/init/files/prettier/__dot__prettierignore b/packages/angular/src/generators/ng-add/files/prettier/__dot__prettierignore similarity index 100% rename from packages/workspace/src/generators/init/files/prettier/__dot__prettierignore rename to packages/angular/src/generators/ng-add/files/prettier/__dot__prettierignore diff --git a/packages/workspace/src/generators/init/files/root/karma.conf.js__tmpl__ b/packages/angular/src/generators/ng-add/files/root/karma.conf.js__tmpl__ similarity index 100% rename from packages/workspace/src/generators/init/files/root/karma.conf.js__tmpl__ rename to packages/angular/src/generators/ng-add/files/root/karma.conf.js__tmpl__ diff --git a/packages/workspace/src/generators/init/files/root/tools/schematics/__dot__gitkeep b/packages/angular/src/generators/ng-add/files/root/libs/__dot__gitkeep similarity index 100% rename from packages/workspace/src/generators/init/files/root/tools/schematics/__dot__gitkeep rename to packages/angular/src/generators/ng-add/files/root/libs/__dot__gitkeep diff --git a/packages/angular/src/generators/ng-add/files/root/nx.json__tmpl__ b/packages/angular/src/generators/ng-add/files/root/nx.json__tmpl__ new file mode 100644 index 00000000000000..848eace22b0216 --- /dev/null +++ b/packages/angular/src/generators/ng-add/files/root/nx.json__tmpl__ @@ -0,0 +1,29 @@ +{ + "npmScope": "<%= npmScope %>", + "affected": { + "defaultBase": "<%= defaultBase %>" + }, + "implicitDependencies": { + "package.json": { + "dependencies": "*", + "devDependencies": "*" + }, + ".eslintrc.json": "*" + }, + "tasksRunnerOptions": { + "default": { + "runner": "@nrwl/workspace/tasks-runners/default", + "options": { + "cacheableOperations": ["build", "lint", "test", "e2e"] + } + } + }, + "targetDependencies": { + "build": [ + { + "target": "build", + "projects": "dependencies" + } + ] + } +} diff --git a/packages/angular/src/generators/ng-add/files/root/tools/schematics/__dot__gitkeep b/packages/angular/src/generators/ng-add/files/root/tools/schematics/__dot__gitkeep new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/packages/workspace/src/generators/init/files/root/tools/tsconfig.tools.json__tmpl__ b/packages/angular/src/generators/ng-add/files/root/tools/tsconfig.tools.json__tmpl__ similarity index 100% rename from packages/workspace/src/generators/init/files/root/tools/tsconfig.tools.json__tmpl__ rename to packages/angular/src/generators/ng-add/files/root/tools/tsconfig.tools.json__tmpl__ diff --git a/packages/workspace/src/generators/init/init.spec.ts b/packages/angular/src/generators/ng-add/migrate-from-angular-cli.spec.ts similarity index 91% rename from packages/workspace/src/generators/init/init.spec.ts rename to packages/angular/src/generators/ng-add/migrate-from-angular-cli.spec.ts index 18675dc38d7c34..ba1b3a72e1cdeb 100644 --- a/packages/workspace/src/generators/init/init.spec.ts +++ b/packages/angular/src/generators/ng-add/migrate-from-angular-cli.spec.ts @@ -6,7 +6,7 @@ import { } from '@nrwl/devkit'; import { createTree } from '@nrwl/devkit/testing'; -import { initGenerator } from './init'; +import { migrateFromAngularCli } from './migrate-from-angular-cli'; describe('workspace', () => { let tree: Tree; @@ -77,7 +77,7 @@ describe('workspace', () => { it('should error if no package.json is present', async () => { tree.delete('package.json'); try { - await initGenerator(tree, { name: 'myApp' }); + await migrateFromAngularCli(tree, { name: 'myApp' }); fail('should throw'); } catch (e) { expect(e.message).toContain('Cannot find package.json'); @@ -88,7 +88,7 @@ describe('workspace', () => { tree.delete('/e2e/protractor.conf.js'); try { - await initGenerator(tree, { name: 'proj1' }); + await migrateFromAngularCli(tree, { name: 'proj1' }); } catch (e) { expect(e.message).toContain( 'An e2e project with Protractor was found but "e2e/protractor.conf.js" could not be found.' @@ -101,7 +101,9 @@ describe('workspace', () => { project.targets.e2e.executor = '@cypress/schematic:cypress'; updateProjectConfiguration(tree, 'myApp', project); - await expect(initGenerator(tree, { name: 'myApp' })).rejects.toThrow( + await expect( + migrateFromAngularCli(tree, { name: 'myApp' }) + ).rejects.toThrow( 'An e2e project with Cypress was found but "cypress.json" could not be found.' ); }); @@ -116,7 +118,9 @@ describe('workspace', () => { }; updateProjectConfiguration(tree, 'myApp', project); - await expect(initGenerator(tree, { name: 'myApp' })).rejects.toThrow( + await expect( + migrateFromAngularCli(tree, { name: 'myApp' }) + ).rejects.toThrow( 'An e2e project with Cypress was found but "cypress.config.json" could not be found.' ); }); @@ -127,7 +131,9 @@ describe('workspace', () => { updateProjectConfiguration(tree, 'myApp', project); tree.write('cypress.json', '{}'); - await expect(initGenerator(tree, { name: 'myApp' })).rejects.toThrow( + await expect( + migrateFromAngularCli(tree, { name: 'myApp' }) + ).rejects.toThrow( 'An e2e project with Cypress was found but the "cypress" directory could not be found.' ); }); @@ -137,7 +143,9 @@ describe('workspace', () => { project.targets.e2e.executor = '@my-org/my-package:my-executor'; updateProjectConfiguration(tree, 'myApp', project); - await expect(initGenerator(tree, { name: 'myApp' })).rejects.toThrow( + await expect( + migrateFromAngularCli(tree, { name: 'myApp' }) + ).rejects.toThrow( `An e2e project was found but it's using an unsupported executor "@my-org/my-package:my-executor".` ); }); @@ -145,7 +153,7 @@ describe('workspace', () => { it('should error if no angular.json is present', async () => { try { tree.delete('angular.json'); - await initGenerator(tree, { name: 'myApp' }); + await migrateFromAngularCli(tree, { name: 'myApp' }); } catch (e) { expect(e.message).toContain('Cannot find angular.json'); } @@ -164,7 +172,7 @@ describe('workspace', () => { }) ); try { - await initGenerator(tree, { name: 'myApp' }); + await migrateFromAngularCli(tree, { name: 'myApp' }); } catch (e) { expect(e.message).toContain('Can only convert projects with one app'); } @@ -183,7 +191,7 @@ describe('workspace', () => { }) ); try { - await initGenerator(tree, { name: 'myApp' }); + await migrateFromAngularCli(tree, { name: 'myApp' }); } catch (e) { expect(e.message).toContain('Can only convert projects with one app'); } @@ -239,7 +247,7 @@ describe('workspace', () => { tree.write('/projects/myApp/e2e/protractor.conf.js', '// content'); tree.write('/projects/myApp/src/app/app.module.ts', '// content'); - await initGenerator(tree, { name: 'myApp' }); + await migrateFromAngularCli(tree, { name: 'myApp' }); const a = readJson(tree, '/angular.json'); @@ -247,30 +255,30 @@ describe('workspace', () => { }); it('should set the default collection to @nrwl/angular', async () => { - await initGenerator(tree, { name: 'myApp' }); + await migrateFromAngularCli(tree, { name: 'myApp' }); expect(readJson(tree, 'nx.json').cli.defaultCollection).toBe( '@nrwl/angular' ); }); it('should create nx.json', async () => { - await initGenerator(tree, { name: 'myApp', defaultBase: 'main' }); + await migrateFromAngularCli(tree, { name: 'myApp', defaultBase: 'main' }); expect(readJson(tree, 'nx.json')).toMatchSnapshot(); }); it('should work if angular-cli workspace had tsconfig.base.json', async () => { tree.rename('tsconfig.json', 'tsconfig.base.json'); - await initGenerator(tree, { name: 'myApp' }); + await migrateFromAngularCli(tree, { name: 'myApp' }); expect(readJson(tree, 'tsconfig.base.json')).toMatchSnapshot(); }); it('should update tsconfig.base.json if present', async () => { - await initGenerator(tree, { name: 'myApp' }); + await migrateFromAngularCli(tree, { name: 'myApp' }); expect(readJson(tree, 'tsconfig.base.json')).toMatchSnapshot(); }); it('should work without nested tsconfig files', async () => { - await initGenerator(tree, { name: 'myApp' }); + await migrateFromAngularCli(tree, { name: 'myApp' }); expect(tree.exists('/apps/myApp/tsconfig.app.json')).toBe(true); }); @@ -321,7 +329,7 @@ describe('workspace', () => { '/src/tsconfig.spec.json', '{"extends": "../tsconfig.json", "compilerOptions": {}}' ); - await initGenerator(tree, { name: 'myApp' }); + await migrateFromAngularCli(tree, { name: 'myApp' }); expect(tree.exists('/apps/myApp/tsconfig.app.json')).toBe(true); }); @@ -374,7 +382,7 @@ describe('workspace', () => { tree.write('/projects/myApp/e2e/protractor.conf.js', '// content'); tree.write('/projects/myApp/src/app/app.module.ts', '// content'); - await initGenerator(tree, { name: 'myApp' }); + await migrateFromAngularCli(tree, { name: 'myApp' }); expect(tree.exists('/tslint.json')).toBe(true); expect(tree.exists('/apps/myApp/tsconfig.app.json')).toBe(true); @@ -406,7 +414,7 @@ describe('workspace', () => { ); tree.write('/karma.conf.js', '// content'); - await initGenerator(tree, { name: 'myApp' }); + await migrateFromAngularCli(tree, { name: 'myApp' }); expect(tree.exists('/apps/myApp/tsconfig.app.json')).toBe(true); expect(tree.exists('/apps/myApp/karma.conf.js')).toBe(true); @@ -416,7 +424,7 @@ describe('workspace', () => { it('should work with existing .prettierignore file', async () => { tree.write('/.prettierignore', '# existing ignore rules'); - await initGenerator(tree, { name: 'myApp' }); + await migrateFromAngularCli(tree, { name: 'myApp' }); const prettierIgnore = tree.read('/.prettierignore').toString(); expect(prettierIgnore).toBe('# existing ignore rules'); @@ -424,7 +432,7 @@ describe('workspace', () => { it('should update tsconfigs', async () => { tree.write('/.prettierignore', '# existing ignore rules'); - await initGenerator(tree, { name: 'myApp' }); + await migrateFromAngularCli(tree, { name: 'myApp' }); const prettierIgnore = tree.read('/.prettierignore').toString(); expect(prettierIgnore).toBe('# existing ignore rules'); @@ -432,7 +440,7 @@ describe('workspace', () => { it('should work with no root tslint.json', async () => { tree.delete('/tslint.json'); - await initGenerator(tree, { name: 'myApp' }); + await migrateFromAngularCli(tree, { name: 'myApp' }); expect(tree.exists('/tslint.json')).toBe(false); }); @@ -518,7 +526,7 @@ describe('workspace', () => { }); it('should migrate e2e tests correctly', async () => { - await initGenerator(tree, { name: 'myApp' }); + await migrateFromAngularCli(tree, { name: 'myApp' }); expect(tree.exists('cypress.json')).toBe(false); expect(tree.exists('cypress')).toBe(false); @@ -550,7 +558,7 @@ describe('workspace', () => { updateProjectConfiguration(tree, 'myApp', project); tree.delete('cypress.json'); - await initGenerator(tree, { name: 'myApp' }); + await migrateFromAngularCli(tree, { name: 'myApp' }); expect(tree.exists('cypress.json')).toBe(false); expect(tree.exists('cypress')).toBe(false); @@ -569,7 +577,7 @@ describe('workspace', () => { delete project.targets['cypress-open']; updateProjectConfiguration(tree, 'myApp', project); - await initGenerator(tree, { name: 'myApp' }); + await migrateFromAngularCli(tree, { name: 'myApp' }); expect(tree.exists('cypress.json')).toBe(false); expect(tree.exists('cypress')).toBe(false); @@ -588,7 +596,7 @@ describe('workspace', () => { }); it('should update package.json', async () => { - await initGenerator(tree, { + await migrateFromAngularCli(tree, { name: 'myApp', preserveAngularCliLayout: true, }); @@ -599,7 +607,7 @@ describe('workspace', () => { }); it('should create nx.json', async () => { - await initGenerator(tree, { + await migrateFromAngularCli(tree, { name: 'myApp', preserveAngularCliLayout: true, }); @@ -609,7 +617,7 @@ describe('workspace', () => { }); it('should create decorate-angular-cli.js', async () => { - await initGenerator(tree, { + await migrateFromAngularCli(tree, { name: 'myApp', preserveAngularCliLayout: true, }); diff --git a/packages/workspace/src/generators/init/init.ts b/packages/angular/src/generators/ng-add/migrate-from-angular-cli.ts similarity index 93% rename from packages/workspace/src/generators/init/init.ts rename to packages/angular/src/generators/ng-add/migrate-from-angular-cli.ts index f683805a22e1e5..38898247b79008 100755 --- a/packages/workspace/src/generators/init/init.ts +++ b/packages/angular/src/generators/ng-add/migrate-from-angular-cli.ts @@ -1,7 +1,6 @@ import { addDependenciesToPackageJson, addProjectConfiguration, - convertNxGenerator, formatFiles, generateFiles, getProjects, @@ -12,28 +11,24 @@ import { normalizePath, NxJsonConfiguration, offsetFromRoot, + ProjectConfiguration, readJson, readProjectConfiguration, readWorkspaceConfiguration, + TargetConfiguration, Tree, updateJson, updateProjectConfiguration, updateWorkspaceConfiguration, visitNotIgnoredFiles, writeJson, - ProjectConfiguration, - TargetConfiguration, } from '@nrwl/devkit'; -import { readFileSync } from 'fs'; +import { DEFAULT_NRWL_PRETTIER_CONFIG } from '@nrwl/workspace/src/generators/workspace/workspace'; +import { deduceDefaultBase } from '@nrwl/workspace/src/utilities/default-base'; +import { resolveUserExistingPrettierConfig } from '@nrwl/workspace/src/utilities/prettier'; +import { prettierVersion } from '@nrwl/workspace/src/utils/versions'; import { basename, relative } from 'path'; -import { resolveUserExistingPrettierConfig } from '../../utilities/prettier'; -import { deduceDefaultBase } from '../../utilities/default-base'; -import { - angularCliVersion, - nxVersion, - prettierVersion, -} from '../../utils/versions'; -import { DEFAULT_NRWL_PRETTIER_CONFIG } from '../workspace/workspace'; +import { angularDevkitVersion, nxVersion } from '../../utils/versions'; import { Schema } from './schema'; function updatePackageJson(tree) { @@ -65,12 +60,17 @@ function updatePackageJson(tree) { if (!packageJson.dependencies['@nrwl/angular']) { packageJson.dependencies['@nrwl/angular'] = nxVersion; } - delete packageJson.dependencies['@nrwl/workspace']; + if (!packageJson.dependencies['@nrwl/cli']) { + packageJson.dependencies['@nrwl/cli'] = nxVersion; + } + if (!packageJson.dependencies['@nrwl/tao']) { + packageJson.dependencies['@nrwl/tao'] = nxVersion; + } if (!packageJson.devDependencies['@nrwl/workspace']) { packageJson.devDependencies['@nrwl/workspace'] = nxVersion; } if (!packageJson.devDependencies['@angular/cli']) { - packageJson.devDependencies['@angular/cli'] = angularCliVersion; + packageJson.devDependencies['@angular/cli'] = angularDevkitVersion; } if (!packageJson.devDependencies['prettier']) { packageJson.devDependencies['prettier'] = prettierVersion; @@ -628,25 +628,7 @@ function replaceCypressGlobConfig( ); } -async function createAdditionalFiles(host: Tree, options: Schema) { - const nxJson: NxJsonConfiguration = { - npmScope: options.npmScope, - affected: { - defaultBase: options.defaultBase - ? `${options.defaultBase}` - : deduceDefaultBase(), - }, - implicitDependencies: { - 'angular.json': '*', - 'package.json': '*', - 'tslint.json': '*', - '.eslintrc.json': '*', - 'nx.json': '*', - }, - }; - writeJson(host, 'nx.json', nxJson); - host.write('libs/.gitkeep', ''); - +async function createAdditionalFiles(host: Tree) { const recommendations = [ 'nrwl.angular-console', 'angular.ng-template', @@ -753,7 +735,7 @@ function checkCanConvertToWorkspace(host: Tree) { } } -function createNxJson(host: Tree) { +function createNxJson(host: Tree, newProjectRoot: string) { const json = JSON.parse(host.read('angular.json').toString()); const projects = json.projects || {}; const hasLibraries = Object.keys(projects).find( @@ -787,19 +769,17 @@ function createNxJson(host: Tree) { }, }, }, + workspaceLayout: { appsDir: newProjectRoot, libsDir: newProjectRoot }, }); } -function decorateAngularClI(host: Tree) { - const decorateCli = readFileSync( - joinPathFragments( - __dirname as any, - '..', - 'utils', - 'decorate-angular-cli.js__tmpl__' - ) - ).toString(); - host.write('decorate-angular-cli.js', decorateCli); +function decorateAngularCli(host: Tree) { + generateFiles( + host, + joinPathFragments(__dirname, 'files', 'decorate-angular-cli'), + '.', + { tmpl: '' } + ); updateJson(host, 'package.json', (json) => { if ( json.scripts && @@ -820,10 +800,12 @@ function decorateAngularClI(host: Tree) { }); } -function addFiles(host: Tree) { +function addFiles(host: Tree, options: Schema) { generateFiles(host, joinPathFragments(__dirname, './files/root'), '.', { tmpl: '', dot: '.', + npmScope: options.npmScope, + defaultBase: options.defaultBase ?? deduceDefaultBase(), }); if (!host.exists('.prettierignore')) { @@ -861,15 +843,23 @@ function renameDirSyncInTree(tree: Tree, from: string, to: string) { }); } -export async function initGenerator(tree: Tree, schema: Schema) { +export async function migrateFromAngularCli(tree: Tree, schema: Schema) { if (schema.preserveAngularCliLayout) { - updateJson(tree, 'package.json', (json) => { - delete json.dependencies?.['@nrwl/workspace']; - return json; - }); - addDependenciesToPackageJson(tree, {}, { '@nrwl/workspace': nxVersion }); - createNxJson(tree); - decorateAngularClI(tree); + addDependenciesToPackageJson( + tree, + {}, + { + '@nrwl/cli': nxVersion, + '@nrwl/tao': nxVersion, + '@nrwl/workspace': nxVersion, + } + ); + const angularJson = readJson(tree, 'angular.json'); + const { newProjectRoot = '' } = angularJson; + delete angularJson.newProjectRoot; + writeJson(tree, 'angular.json', angularJson); + createNxJson(tree, newProjectRoot); + decorateAngularCli(tree); } else { const options = { ...schema, @@ -878,15 +868,15 @@ export async function initGenerator(tree: Tree, schema: Schema) { checkCanConvertToWorkspace(tree); moveExistingFiles(tree, options); - addFiles(tree); - await createAdditionalFiles(tree, options); + addFiles(tree, options); + await createAdditionalFiles(tree); updatePackageJson(tree); updateAngularCLIJson(tree, options); updateTsLint(tree); updateProjectTsLint(tree, options); updateTsConfig(tree); updateTsConfigsJson(tree, options); - decorateAngularClI(tree); + decorateAngularCli(tree); await formatFiles(tree); } if (!schema.skipInstall) { @@ -895,7 +885,3 @@ export async function initGenerator(tree: Tree, schema: Schema) { }; } } - -export const initSchematic = convertNxGenerator(initGenerator); - -export default initGenerator; diff --git a/packages/angular/src/generators/ng-add/ng-add.spec.ts b/packages/angular/src/generators/ng-add/ng-add.spec.ts new file mode 100644 index 00000000000000..96519e570d536e --- /dev/null +++ b/packages/angular/src/generators/ng-add/ng-add.spec.ts @@ -0,0 +1,36 @@ +import * as angularCliMigrator from './migrate-from-angular-cli'; +import * as initGenerator from '../init/init'; +import { ngAddGenerator } from './ng-add'; +import { Tree } from '@nrwl/devkit'; +import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; + +describe('ngAdd generator', () => { + let tree: Tree; + + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(2); + jest + .spyOn(angularCliMigrator, 'migrateFromAngularCli') + .mockImplementation(() => Promise.resolve(() => {})); + jest + .spyOn(initGenerator, 'angularInitGenerator') + .mockImplementation(() => Promise.resolve(() => {})); + jest.clearAllMocks(); + }); + + it('should initialize the Angular plugin when in an Nx workspace', async () => { + await ngAddGenerator(tree, {}); + + expect(initGenerator.angularInitGenerator).toHaveBeenCalled(); + expect(angularCliMigrator.migrateFromAngularCli).not.toHaveBeenCalled(); + }); + + it('should perform a migration when in an Angular CLI workspace', async () => { + tree.delete('nx.json'); + + await ngAddGenerator(tree, {}); + + expect(angularCliMigrator.migrateFromAngularCli).toHaveBeenCalled(); + expect(initGenerator.angularInitGenerator).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/angular/src/generators/ng-add/ng-add.ts b/packages/angular/src/generators/ng-add/ng-add.ts new file mode 100644 index 00000000000000..765b76ee8b0c8c --- /dev/null +++ b/packages/angular/src/generators/ng-add/ng-add.ts @@ -0,0 +1,18 @@ +import { Tree } from '@nrwl/devkit'; +import { migrateFromAngularCli } from './migrate-from-angular-cli'; +import { angularInitGenerator } from '../init/init'; +import { Schema } from './schema'; + +function getWorkspaceType(tree: Tree): 'angular' | 'nx' { + return tree.exists('nx.json') ? 'nx' : 'angular'; +} + +export async function ngAddGenerator(tree: Tree, options: Schema) { + if (getWorkspaceType(tree) === 'angular') { + return await migrateFromAngularCli(tree, options); + } + + return angularInitGenerator(tree, options); +} + +export default ngAddGenerator; diff --git a/packages/angular/src/generators/ng-add/schema.d.ts b/packages/angular/src/generators/ng-add/schema.d.ts new file mode 100644 index 00000000000000..e4c7d86b7cf53a --- /dev/null +++ b/packages/angular/src/generators/ng-add/schema.d.ts @@ -0,0 +1,19 @@ +import { Linter } from '@nrwl/linter'; +import { E2eTestRunner, UnitTestRunner } from '../../utils/test-runners'; +import type { Styles } from '../utils/types'; + +export interface Schema { + name?: string; + skipInstall?: boolean; + npmScope?: string; + preserveAngularCliLayout?: boolean; + defaultBase?: string; + + unitTestRunner?: UnitTestRunner; + e2eTestRunner?: E2eTestRunner; + skipFormat?: boolean; + skipInstall?: boolean; + style?: Styles; + linter?: Exclude; + skipPackageJson?: boolean; +} diff --git a/packages/angular/src/generators/ng-add/schema.json b/packages/angular/src/generators/ng-add/schema.json new file mode 100644 index 00000000000000..affe661f287c29 --- /dev/null +++ b/packages/angular/src/generators/ng-add/schema.json @@ -0,0 +1,71 @@ +{ + "$schema": "http://json-schema.org/schema", + "$id": "NxAngularNgAddGenerator", + "title": "Angular plugin initialization", + "cli": "nx", + "description": "Migrates an Angular CLI workspace to Nx or adds the Angular plugin to an Nx workspace. NOTE: Does not work in the `--dry-run` mode.", + "type": "object", + "properties": { + "npmScope": { + "type": "string", + "description": "Npm scope for importing libs. NOTE: only used if running the generator in an Angular CLI workspace." + }, + "defaultBase": { + "type": "string", + "description": "Default base branch for affected. NOTE: only used if running the generator in an Angular CLI workspace." + }, + "skipInstall": { + "type": "boolean", + "description": "Skip installing added packages.", + "default": false + }, + "preserveAngularCliLayout": { + "type": "boolean", + "description": "Preserve the Angular CLI layout instead of moving the app into apps. NOTE: only used if running the generator in an Angular CLI workspace.", + "default": false + }, + "name": { + "type": "string", + "description": "Project name. NOTE: only used if running the generator in an Angular CLI workspace.", + "$default": { + "$source": "projectName" + } + }, + + "unitTestRunner": { + "type": "string", + "enum": ["karma", "jest", "none"], + "description": "Test runner to use for unit tests. NOTE: only used if running the generator in an Nx workspace.", + "default": "jest" + }, + "e2eTestRunner": { + "type": "string", + "enum": ["protractor", "cypress", "none"], + "description": "Test runner to use for end to end (e2e) tests. NOTE: only used if running the generator in an Nx workspace.", + "default": "cypress" + }, + "skipFormat": { + "description": "Skip formatting files. NOTE: only used if running the generator in an Nx workspace.", + "type": "boolean", + "default": false + }, + "linter": { + "description": "The tool to use for running lint checks. NOTE: only used if running the generator in an Nx workspace.", + "type": "string", + "enum": ["eslint", "none"], + "default": "eslint" + }, + "style": { + "description": "The file extension to be used for style files. NOTE: only used if running the generator in an Nx workspace.", + "type": "string", + "default": "css", + "enum": ["css", "scss", "sass", "less"] + }, + "skipPackageJson": { + "type": "boolean", + "default": false, + "description": "Do not add dependencies to `package.json`. NOTE: only used if running the generator in an Nx workspace." + } + }, + "additionalProperties": false +} diff --git a/packages/workspace/generators.json b/packages/workspace/generators.json index 7ce1f216bd3788..6c41ef4f4e65b8 100644 --- a/packages/workspace/generators.json +++ b/packages/workspace/generators.json @@ -9,13 +9,6 @@ "hidden": true }, - "ng-add": { - "factory": "./src/generators/init/init#initSchematic", - "schema": "./src/generators/init/schema.json", - "description": "Convert an existing Angular CLI project into an Nx Workspace", - "hidden": true - }, - "preset": { "factory": "./src/generators/preset/preset#presetSchematic", "schema": "./src/generators/preset/schema.json", @@ -87,13 +80,6 @@ "hidden": true }, - "ng-add": { - "factory": "./src/generators/init/init#initGenerator", - "schema": "./src/generators/init/schema.json", - "description": "Convert an existing Angular CLI project into an Nx Workspace", - "hidden": true - }, - "preset": { "factory": "./src/generators/preset/preset#presetGenerator", "schema": "./src/generators/preset/schema.json", diff --git a/packages/workspace/src/generators/init/schema.d.ts b/packages/workspace/src/generators/init/schema.d.ts deleted file mode 100644 index 4b839c9eae4489..00000000000000 --- a/packages/workspace/src/generators/init/schema.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface Schema { - name: string; - skipInstall?: boolean; - npmScope?: string; - preserveAngularCliLayout?: boolean; - defaultBase?: string; -} diff --git a/packages/workspace/src/generators/init/schema.json b/packages/workspace/src/generators/init/schema.json deleted file mode 100644 index d34ec116694f5c..00000000000000 --- a/packages/workspace/src/generators/init/schema.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "$schema": "http://json-schema.org/schema", - "$id": "SchematicsNxNgAdd", - "title": "Init Workspace", - "cli": "ng", - "description": "NOTE: Does not work in the --dry-run mode", - "type": "object", - "properties": { - "npmScope": { - "type": "string", - "description": "Npm scope for importing libs." - }, - "defaultBase": { - "type": "string", - "description": "Default base branch for affected." - }, - "skipInstall": { - "type": "boolean", - "description": "Skip installing after adding @nrwl/workspace", - "default": false - }, - "preserveAngularCliLayout": { - "type": "boolean", - "description": "Preserve the Angular CLI layout instead of moving the app into apps.", - "default": false - }, - "name": { - "type": "string", - "description": "Project name.", - "$default": { - "$source": "projectName" - } - } - } -}