diff --git a/e2e/ng-add.test.ts b/e2e/ng-add.test.ts index 9ac1497e1a..701030013e 100644 --- a/e2e/ng-add.test.ts +++ b/e2e/ng-add.test.ts @@ -293,22 +293,6 @@ forEachCli('angular', () => { ]); }); - // TODO(FrozenPandaz): reenable after angular 9 - xit('should convert a project with common libraries in the ecosystem', () => { - // create a new AngularCLI app - runNew(); - - // Add some Angular libraries - runNgAdd('add @angular/elements'); - runNgAdd('add @angular/material'); - runNgAdd('add @angular/pwa'); - runNgAdd('add @ngrx/store'); - runNgAdd('add @ngrx/effects'); - - // Add Nx - runNgAdd('add @nrwl/workspace --skip-install'); - }); - it('should handle different types of errors', () => { // create a new AngularCLI app runNew(); @@ -353,6 +337,18 @@ forEachCli('angular', () => { // Put src back runCommand('mv src-bak src'); }); + + it('should support preserveAngularCLILayout', () => { + runNew('', false, false); + runNgAdd('add @nrwl/workspace --preserveAngularCLILayout'); + + const updatedAngularCLIJson = readJson('angular.json'); + expect(updatedAngularCLIJson.projects.proj.root).toEqual(''); + expect(updatedAngularCLIJson.projects.proj.sourceRoot).toEqual('src'); + + const output = runCLI('build'); + expect(output).toContain(`> ng run proj:build`); + }); }); }); diff --git a/packages/workspace/src/schematics/init/init.spec.ts b/packages/workspace/src/schematics/init/init.spec.ts index 5edb99e6a8..302fd59797 100644 --- a/packages/workspace/src/schematics/init/init.spec.ts +++ b/packages/workspace/src/schematics/init/init.spec.ts @@ -9,222 +9,258 @@ describe('workspace', () => { appTree = new UnitTestTree(Tree.empty()); }); - it('should error if no package.json is present', async () => { - try { - await runSchematic('ng-add', { name: 'myApp' }, appTree); - fail('should throw'); - } catch (e) { - expect(e.message).toContain('Cannot find package.json'); - } - }); + describe('move to nx layout', () => { + it('should error if no package.json is present', async () => { + try { + await runSchematic('ng-add', { name: 'myApp' }, appTree); + fail('should throw'); + } catch (e) { + expect(e.message).toContain('Cannot find package.json'); + } + }); - it('should error if no e2e/protractor.conf.js is present', async () => { - appTree.create('/package.json', JSON.stringify({})); - appTree.create( - '/angular.json', - JSON.stringify({ - projects: { - proj1: { - architect: { - e2e: { - options: { - protractorConfig: 'e2e/protractor.conf.js', + it('should error if no e2e/protractor.conf.js is present', async () => { + appTree.create('/package.json', JSON.stringify({})); + appTree.create( + '/angular.json', + JSON.stringify({ + projects: { + proj1: { + architect: { + e2e: { + options: { + protractorConfig: 'e2e/protractor.conf.js', + }, }, }, }, }, - }, - }) - ); - - try { - await runSchematic('ng-add', { name: 'proj1' }, appTree); - } catch (e) { - expect(e.message).toContain( - 'An e2e project was specified but e2e/protractor.conf.js could not be found.' + }) ); - } - }); - it('should error if no angular.json is present', async () => { - try { + try { + await runSchematic('ng-add', { name: 'proj1' }, appTree); + } catch (e) { + expect(e.message).toContain( + 'An e2e project was specified but e2e/protractor.conf.js could not be found.' + ); + } + }); + + it('should error if no angular.json is present', async () => { + try { + appTree.create('/package.json', JSON.stringify({})); + appTree.create('/e2e/protractor.conf.js', ''); + await runSchematic('ng-add', { name: 'myApp' }, appTree); + } catch (e) { + expect(e.message).toContain('Cannot find angular.json'); + } + }); + + it('should error if the angular.json specifies more than one app', async () => { appTree.create('/package.json', JSON.stringify({})); appTree.create('/e2e/protractor.conf.js', ''); - await runSchematic('ng-add', { name: 'myApp' }, appTree); - } catch (e) { - expect(e.message).toContain('Cannot find angular.json'); - } - }); + appTree.create( + '/angular.json', + JSON.stringify({ + projects: { + proj1: {}, + 'proj1-e2e': {}, + proj2: {}, + 'proj2-e2e': {}, + }, + }) + ); + try { + await runSchematic('ng-add', { name: 'myApp' }, appTree); + } catch (e) { + expect(e.message).toContain('Can only convert projects with one app'); + } + }); - it('should error if the angular.json specifies more than one app', async () => { - appTree.create('/package.json', JSON.stringify({})); - appTree.create('/e2e/protractor.conf.js', ''); - appTree.create( - '/angular.json', - JSON.stringify({ - projects: { - proj1: {}, - 'proj1-e2e': {}, - proj2: {}, - 'proj2-e2e': {}, - }, - }) - ); - try { - await runSchematic('ng-add', { name: 'myApp' }, appTree); - } catch (e) { - expect(e.message).toContain('Can only convert projects with one app'); - } - }); - - it('should work without nested tsconfig files', async () => { - appTree.create('/package.json', JSON.stringify({})); - appTree.create( - '/angular.json', - JSON.stringify({ - version: 1, - defaultProject: 'myApp', - projects: { - myApp: { - root: '', - sourceRoot: 'src', - architect: { - build: { - options: { - tsConfig: 'tsconfig.app.json', + it('should work without nested tsconfig files', async () => { + appTree.create('/package.json', JSON.stringify({})); + appTree.create( + '/angular.json', + JSON.stringify({ + version: 1, + defaultProject: 'myApp', + projects: { + myApp: { + root: '', + sourceRoot: 'src', + architect: { + build: { + options: { + tsConfig: 'tsconfig.app.json', + }, + configurations: {}, }, - configurations: {}, - }, - test: { - options: { - tsConfig: 'tsconfig.spec.json', + test: { + options: { + tsConfig: 'tsconfig.spec.json', + }, }, - }, - lint: { - options: { - tsConfig: 'tsconfig.app.json', + lint: { + options: { + tsConfig: 'tsconfig.app.json', + }, }, - }, - e2e: { - options: { - protractorConfig: 'e2e/protractor.conf.js', + e2e: { + options: { + protractorConfig: 'e2e/protractor.conf.js', + }, }, }, }, }, - }, - }) - ); - appTree.create( - '/tsconfig.app.json', - '{"extends": "../tsconfig.json", "compilerOptions": {}}' - ); - appTree.create( - '/tsconfig.spec.json', - '{"extends": "../tsconfig.json", "compilerOptions": {}}' - ); - appTree.create('/tsconfig.json', '{"compilerOptions": {}}'); - appTree.create('/tslint.json', '{"rules": {}}'); - appTree.create('/e2e/protractor.conf.js', '// content'); - appTree.create('/src/app/app.module.ts', '// content'); - const tree = await runSchematic('ng-add', { name: 'myApp' }, appTree); - expect(tree.exists('/apps/myApp/tsconfig.app.json')).toBe(true); - }); + }) + ); + appTree.create( + '/tsconfig.app.json', + '{"extends": "../tsconfig.json", "compilerOptions": {}}' + ); + appTree.create( + '/tsconfig.spec.json', + '{"extends": "../tsconfig.json", "compilerOptions": {}}' + ); + appTree.create('/tsconfig.json', '{"compilerOptions": {}}'); + appTree.create('/tslint.json', '{"rules": {}}'); + appTree.create('/e2e/protractor.conf.js', '// content'); + appTree.create('/src/app/app.module.ts', '// content'); + const tree = await runSchematic('ng-add', { name: 'myApp' }, appTree); + expect(tree.exists('/apps/myApp/tsconfig.app.json')).toBe(true); + }); - it('should work with nested (sub-dir) tsconfig files', async () => { - appTree.create('/package.json', JSON.stringify({})); - appTree.create( - '/angular.json', - JSON.stringify({ - version: 1, - defaultProject: 'myApp', - projects: { - myApp: { - sourceRoot: 'src', - architect: { - build: { - options: { - tsConfig: 'src/tsconfig.app.json', + it('should work with nested (sub-dir) tsconfig files', async () => { + appTree.create('/package.json', JSON.stringify({})); + appTree.create( + '/angular.json', + JSON.stringify({ + version: 1, + defaultProject: 'myApp', + projects: { + myApp: { + sourceRoot: 'src', + architect: { + build: { + options: { + tsConfig: 'src/tsconfig.app.json', + }, + configurations: {}, }, - configurations: {}, - }, - test: { - options: { - tsConfig: 'src/tsconfig.spec.json', + test: { + options: { + tsConfig: 'src/tsconfig.spec.json', + }, }, - }, - lint: { - options: { - tsConfig: 'src/tsconfig.app.json', + lint: { + options: { + tsConfig: 'src/tsconfig.app.json', + }, }, - }, - e2e: { - options: { - protractorConfig: 'e2e/protractor.conf.js', + e2e: { + options: { + protractorConfig: 'e2e/protractor.conf.js', + }, }, }, }, }, - }, - }) - ); - appTree.create( - '/src/tsconfig.app.json', - '{"extends": "../tsconfig.json", "compilerOptions": {}}' - ); - appTree.create( - '/src/tsconfig.spec.json', - '{"extends": "../tsconfig.json", "compilerOptions": {}}' - ); - appTree.create('/tsconfig.json', '{"compilerOptions": {}}'); - appTree.create('/tslint.json', '{"rules": {}}'); - appTree.create('/e2e/protractor.conf.js', '// content'); - appTree.create('/src/app/app.module.ts', '// content'); - const tree = await runSchematic('ng-add', { name: 'myApp' }, appTree); - expect(tree.exists('/apps/myApp/tsconfig.app.json')).toBe(true); - }); + }) + ); + appTree.create( + '/src/tsconfig.app.json', + '{"extends": "../tsconfig.json", "compilerOptions": {}}' + ); + appTree.create( + '/src/tsconfig.spec.json', + '{"extends": "../tsconfig.json", "compilerOptions": {}}' + ); + appTree.create('/tsconfig.json', '{"compilerOptions": {}}'); + appTree.create('/tslint.json', '{"rules": {}}'); + appTree.create('/e2e/protractor.conf.js', '// content'); + appTree.create('/src/app/app.module.ts', '// content'); + const tree = await runSchematic('ng-add', { name: 'myApp' }, appTree); + expect(tree.exists('/apps/myApp/tsconfig.app.json')).toBe(true); + }); - it('should work with missing e2e, lint, or test targets', async () => { - appTree.create('/package.json', JSON.stringify({})); - appTree.create( - '/angular.json', - JSON.stringify({ - version: 1, - defaultProject: 'myApp', - projects: { - myApp: { - root: '', - sourceRoot: 'src', - architect: { - build: { - options: { - tsConfig: 'tsconfig.app.json', + it('should work with missing e2e, lint, or test targets', async () => { + appTree.create('/package.json', JSON.stringify({})); + appTree.create( + '/angular.json', + JSON.stringify({ + version: 1, + defaultProject: 'myApp', + projects: { + myApp: { + root: '', + sourceRoot: 'src', + architect: { + build: { + options: { + tsConfig: 'tsconfig.app.json', + }, + configurations: {}, }, - configurations: {}, }, }, }, - }, - }) - ); - appTree.create( - '/tsconfig.app.json', - '{"extends": "../tsconfig.json", "compilerOptions": {}}' - ); - appTree.create( - '/tsconfig.spec.json', - '{"extends": "../tsconfig.json", "compilerOptions": {}}' - ); - appTree.create('/tsconfig.json', '{"compilerOptions": {}}'); - appTree.create('/tslint.json', '{"rules": {}}'); - appTree.create('/e2e/protractor.conf.js', '// content'); - appTree.create('/src/app/app.module.ts', '// content'); - appTree.create('/karma.conf.js', '// content'); + }) + ); + appTree.create( + '/tsconfig.app.json', + '{"extends": "../tsconfig.json", "compilerOptions": {}}' + ); + appTree.create( + '/tsconfig.spec.json', + '{"extends": "../tsconfig.json", "compilerOptions": {}}' + ); + appTree.create('/tsconfig.json', '{"compilerOptions": {}}'); + appTree.create('/tslint.json', '{"rules": {}}'); + appTree.create('/e2e/protractor.conf.js', '// content'); + appTree.create('/src/app/app.module.ts', '// content'); + appTree.create('/karma.conf.js', '// content'); - const tree = await runSchematic('ng-add', { name: 'myApp' }, appTree); + const tree = await runSchematic('ng-add', { name: 'myApp' }, appTree); - expect(tree.exists('/apps/myApp/tsconfig.app.json')).toBe(true); - expect(tree.exists('/apps/myApp/karma.conf.js')).toBe(true); - expect(tree.exists('/karma.conf.js')).toBe(true); + expect(tree.exists('/apps/myApp/tsconfig.app.json')).toBe(true); + expect(tree.exists('/apps/myApp/karma.conf.js')).toBe(true); + expect(tree.exists('/karma.conf.js')).toBe(true); + }); + }); + + describe('preserve angular cli layout', () => { + beforeEach(() => { + appTree.create('/package.json', JSON.stringify({ devDependencies: {} })); + appTree.create( + '/angular.json', + JSON.stringify({ projects: { myproj: {} } }) + ); + }); + + it('should update package.json', async () => { + const tree = await runSchematic( + 'ng-add', + { preserveAngularCLILayout: true }, + appTree + ); + + const d = JSON.parse(tree.readContent('/package.json')).devDependencies; + expect(d['@nrwl/workspace']).toBeDefined(); + expect(d['@nrwl/angular']).not.toBeDefined(); + }); + + it('should create nx.json', async () => { + const tree = await runSchematic( + 'ng-add', + { preserveAngularCLILayout: true }, + appTree + ); + + const nxJson = JSON.parse(tree.readContent('/nx.json')); + expect(nxJson.projects).toEqual({ myproj: { tags: [] } }); + expect(nxJson.npmScope).toEqual('myproj'); + }); }); }); diff --git a/packages/workspace/src/schematics/init/init.ts b/packages/workspace/src/schematics/init/init.ts index fb2db05caa..9a753a0086 100755 --- a/packages/workspace/src/schematics/init/init.ts +++ b/packages/workspace/src/schematics/init/init.ts @@ -29,6 +29,7 @@ import { renameSyncInTree, renameDirSyncInTree, addInstallTask, + addDepsToPackageJson, } from '@nrwl/workspace'; import { DEFAULT_NRWL_PRETTIER_CONFIG } from '../workspace/workspace'; import { JsonArray } from '@angular-devkit/core'; @@ -557,27 +558,70 @@ function checkCanConvertToWorkspace(options: Schema) { }; } +const createNxJson = (host: Tree) => { + const json = JSON.parse(host.read('angular.json').toString()); + if (Object.keys(json.projects || {}).length !== 1) { + throw new Error( + `The schematic can only be used with Angular CLI workspaces with a single project.` + ); + } + const name = Object.keys(json.projects)[0]; + host.create( + 'nx.json', + serializeJson({ + npmScope: name, + implicitDependencies: { + 'angular.json': '*', + 'package.json': '*', + 'tsconfig.json': '*', + 'tslint.json': '*', + 'nx.json': '*', + }, + projects: { + [name]: { + tags: [], + }, + }, + tasksRunnerOptions: { + default: { + runner: '@nrwl/workspace/tasks-runners/default', + options: { + cacheableOperations: ['build', 'lint', 'test', 'e2e'], + }, + }, + }, + }) + ); +}; + export default function (schema: Schema): Rule { - const options = { - ...schema, - npmScope: toFileName(schema.npmScope || schema.name), - }; - const templateSource = apply(url('./files'), [ - template({ - tmpl: '', - }), - ]); - return chain([ - checkCanConvertToWorkspace(options), - moveExistingFiles(options), - mergeWith(templateSource), - createAdditionalFiles(options), - updatePackageJson(), - updateAngularCLIJson(options), - updateTsLint(), - updateProjectTsLint(options), - updateTsConfig(options), - updateTsConfigsJson(options), - addInstallTask(options), - ]); + if (schema.preserveAngularCLILayout) { + return chain([ + addDepsToPackageJson({}, { '@nrwl/workspace': nxVersion }), + createNxJson, + ]); + } else { + const options = { + ...schema, + npmScope: toFileName(schema.npmScope || schema.name), + }; + const templateSource = apply(url('./files'), [ + template({ + tmpl: '', + }), + ]); + return chain([ + checkCanConvertToWorkspace(options), + moveExistingFiles(options), + mergeWith(templateSource), + createAdditionalFiles(options), + updatePackageJson(), + updateAngularCLIJson(options), + updateTsLint(), + updateProjectTsLint(options), + updateTsConfig(options), + updateTsConfigsJson(options), + addInstallTask(options), + ]); + } } diff --git a/packages/workspace/src/schematics/init/schema.d.ts b/packages/workspace/src/schematics/init/schema.d.ts index a06b0f7215..238fe0b60e 100644 --- a/packages/workspace/src/schematics/init/schema.d.ts +++ b/packages/workspace/src/schematics/init/schema.d.ts @@ -2,4 +2,5 @@ export interface Schema { name: string; skipInstall: boolean; npmScope?: string; + preserveAngularCLILayout: boolean; } diff --git a/packages/workspace/src/schematics/init/schema.json b/packages/workspace/src/schematics/init/schema.json index 70104cefe9..77605b9105 100644 --- a/packages/workspace/src/schematics/init/schema.json +++ b/packages/workspace/src/schematics/init/schema.json @@ -14,6 +14,11 @@ "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.",