From cd2e311a32cf4d025749bd5a080378a2c7922f9c Mon Sep 17 00:00:00 2001 From: Jason Jean Date: Fri, 23 Nov 2018 01:50:32 -0500 Subject: [PATCH] feat(schematics): create local project tsconfigs for typings --- .../schematics/migrations/migrations.json | 5 + .../update-7-2-0/update-7-2-0.spec.ts | 398 ++++++++++++++++++ .../migrations/update-7-2-0/update-7-2-0.ts | 160 +++++++ .../application/application.spec.ts | 18 +- .../application/files/tsconfig.json | 7 + .../src/collection/application/index.ts | 92 +++- .../cypress-project/cypress-project.spec.ts | 3 +- .../files/src/integration/app.spec.ts__tmpl__ | 2 - .../files/src/support/app.po.ts__tmpl__ | 2 - .../files/src/support/commands.ts__tmpl__ | 2 - .../cypress-project/files/tsconfig.e2e.json | 2 +- .../src/collection/cypress-project/index.ts | 17 +- .../jest-project/files/tsconfig.spec.json | 2 +- .../src/collection/jest-project/index.ts | 16 + .../jest-project/jest-project.spec.ts | 15 +- .../collection/library/files/tsconfig.json | 7 + .../src/collection/library/index.ts | 32 +- .../src/collection/library/library.spec.ts | 67 ++- .../files/app/tsconfig.app.json | 2 +- .../node-application/files/app/tsconfig.json | 7 + .../node-application/node-application.spec.ts | 13 +- 21 files changed, 826 insertions(+), 43 deletions(-) create mode 100644 packages/schematics/migrations/update-7-2-0/update-7-2-0.spec.ts create mode 100644 packages/schematics/migrations/update-7-2-0/update-7-2-0.ts create mode 100644 packages/schematics/src/collection/application/files/tsconfig.json create mode 100644 packages/schematics/src/collection/library/files/tsconfig.json create mode 100644 packages/schematics/src/collection/node-application/files/app/tsconfig.json diff --git a/packages/schematics/migrations/migrations.json b/packages/schematics/migrations/migrations.json index 50183bbd9c..7d1dc748e8 100644 --- a/packages/schematics/migrations/migrations.json +++ b/packages/schematics/migrations/migrations.json @@ -44,6 +44,11 @@ "version": "7.1.0", "description": "Add generic affected command", "factory": "./update-7-1-0/update-7-1-0" + }, + "update-7.2.0": { + "version": "7.2.0", + "description": "Create tsconfig.jsons in project roots", + "factory": "./update-7-2-0/update-7-2-0" } } } diff --git a/packages/schematics/migrations/update-7-2-0/update-7-2-0.spec.ts b/packages/schematics/migrations/update-7-2-0/update-7-2-0.spec.ts new file mode 100644 index 0000000000..667f50155f --- /dev/null +++ b/packages/schematics/migrations/update-7-2-0/update-7-2-0.spec.ts @@ -0,0 +1,398 @@ +import { Tree } from '@angular-devkit/schematics'; +import { SchematicTestRunner } from '@angular-devkit/schematics/testing'; +import { serializeJson } from '../../src/utils/fileutils'; + +import * as path from 'path'; +import { readJsonInTree, updateJsonInTree } from '../../src/utils/ast-utils'; + +describe('Update 7.2.0', () => { + let initialTree: Tree; + let schematicRunner: SchematicTestRunner; + + beforeEach(() => { + initialTree = Tree.empty(); + initialTree.create( + 'package.json', + serializeJson({ + scripts: {} + }) + ); + createJson('tsconfig.json', {}); + createJson('angular.json', { + projects: { + app1: { + root: 'apps/app1', + architect: { + build: { + builder: '@angular-devkit/build-angular:browser', + options: { + tsConfig: 'apps/app1/tsconfig.app.json' + } + }, + test: { + builder: '@angular-devkit/build-angular:karma', + options: { + tsConfig: 'apps/app1/tsconfig.spec.json' + } + }, + lint: { + builder: '@angular-devkit/build-angular:tslint', + options: { + tsConfig: [ + 'apps/app1/tsconfig.app.json', + 'apps/app1/tsconfig.spec.json' + ] + } + } + } + }, + 'app1-e2e': { + root: 'apps/app1-e2e', + architect: { + e2e: { + builder: '@angular-devkit/build-angular:protractor', + options: { + tsConfig: 'apps/app1-e2e/tsconfig.e2e.json' + } + }, + lint: { + builder: '@angular-devkit/build-angular:tslint', + options: { + tsConfig: 'apps/app1-e2e/tsconfig.e2e.json' + } + } + } + }, + app2: { + root: 'apps/app2', + architect: { + build: { + builder: '@angular-devkit/build-angular:browser', + options: { + tsConfig: 'apps/app2/tsconfig.app.json' + } + }, + test: { + builder: '@nrwl/schematics:jest', + options: { + tsConfig: 'apps/app2/tsconfig.spec.json' + } + }, + lint: { + builder: '@angular-devkit/build-angular:tslint', + options: { + tsConfig: [ + 'apps/app2/tsconfig.app.json', + 'apps/app2/tsconfig.spec.json' + ] + } + } + } + }, + 'app2-e2e': { + root: 'apps/app2-e2e', + architect: { + e2e: { + builder: '@nrwl/builders:cypress', + options: { + tsConfig: 'apps/app2-e2e/tsconfig.e2e.json' + } + } + } + }, + 'node-app': { + root: 'apps/node-app', + architect: { + build: { + builder: '@nrwl/builders:node-build', + options: { + tsConfig: 'apps/node-app/tsconfig.app.json' + } + }, + test: { + builder: '@nrwl/schematics:jest', + options: { + tsConfig: 'apps/node-app/tsconfig.spec.json' + } + }, + lint: { + builder: '@angular-devkit/build-angular:tslint', + options: { + tsConfig: [ + 'apps/node-app/tsconfig.app.json', + 'apps/node-app/tsconfig.spec.json' + ] + } + } + } + }, + 'weird-app': { + root: 'apps/weird/app', + architect: { + build: { + builder: '@nrwl/builders:node-build', + options: { + tsConfig: 'apps/weird/app/src/tsconfig.app.json' + } + }, + test: { + builder: '@nrwl/schematics:jest', + options: { + tsConfig: 'apps/weird/app/src/tsconfig.spec.json' + } + }, + lint: { + builder: '@angular-devkit/build-angular:tslint', + options: { + tsConfig: [ + 'apps/weird/app/src/tsconfig.app.json', + 'apps/weird/app/src/tsconfig.spec.json' + ] + } + } + } + }, + lib1: { + root: 'libs/lib1', + architect: { + test: { + builder: '@angular-devkit/build-angular:karma', + options: { + tsConfig: 'libs/lib1/tsconfig.spec.json' + } + }, + lint: { + builder: '@angular-devkit/build-angular:tslint', + options: { + tsConfig: [ + 'libs/lib1/tsconfig.lib.json', + 'libs/lib1/tsconfig.spec.json' + ] + } + } + } + }, + lib2: { + root: 'libs/lib2', + architect: { + test: { + builder: '@angular-devkit/build-angular:jest', + options: { + tsConfig: 'libs/lib2/tsconfig.spec.json' + } + }, + lint: { + builder: '@angular-devkit/build-angular:tslint', + options: { + tsConfig: [ + 'libs/lib2/tsconfig.lib.json', + 'libs/lib2/tsconfig.spec.json' + ] + } + } + } + } + } + }); + createJson('apps/app1/tsconfig.app.json', { + extends: '../../tsconfig.json', + compilerOptions: { + types: ['jquery'] + } + }); + createJson('apps/app1/tsconfig.spec.json', { + extends: '../../tsconfig.json', + compilerOptions: { + types: ['jasmine', 'node', 'sinon'] + } + }); + createJson('apps/app1-e2e/tsconfig.e2e.json', { + extends: '../../tsconfig.json', + compilerOptions: { + types: ['jasmine', 'jasminewd2', 'node'] + } + }); + createJson('apps/app2/tsconfig.app.json', { + extends: '../../tsconfig.json', + compilerOptions: { + types: [] + } + }); + createJson('apps/app2/tsconfig.spec.json', { + extends: '../../tsconfig.json', + compilerOptions: { + types: ['jest', 'node'] + } + }); + createJson('apps/app2-e2e/tsconfig.e2e.json', { + extends: '../../tsconfig.json', + compilerOptions: { + types: ['cypress', 'node'] + } + }); + createJson('apps/node-app/tsconfig.app.json', { + extends: '../../tsconfig.json', + compilerOptions: { + types: ['node'] + } + }); + createJson('apps/node-app/tsconfig.spec.json', { + extends: '../../tsconfig.json', + compilerOptions: { + types: ['jest', 'node'] + } + }); + createJson('apps/weird/app/src/tsconfig.app.json', { + extends: '../../../tsconfig.json', + compilerOptions: {} + }); + createJson('apps/weird/app/src/tsconfig.spec.json', { + extends: '../../../tsconfig.json', + compilerOptions: {} + }); + createJson('libs/lib1/tsconfig.lib.json', { + extends: '../../tsconfig.json', + compilerOptions: { + types: [] + } + }); + createJson('libs/lib1/tsconfig.spec.json', { + extends: '../../tsconfig.json', + compilerOptions: { + types: ['jasmine', 'node'] + } + }); + createJson('libs/lib2/tsconfig.lib.json', { + extends: '../../tsconfig.json', + compilerOptions: { + types: [] + } + }); + createJson('libs/lib2/tsconfig.spec.json', { + extends: '../../tsconfig.json', + compilerOptions: { + types: ['jest', 'node'] + } + }); + + function createJson(path: string, value: any) { + initialTree.create(path, serializeJson(value)); + } + + schematicRunner = new SchematicTestRunner( + '@nrwl/schematics', + path.join(__dirname, '../migrations.json') + ); + }); + + it('should create tsconfigs for existing projects', async () => { + const result = await schematicRunner + .runSchematicAsync('update-7.2.0', {}, initialTree) + .toPromise(); + expect(result.files).toContain('/tsconfig.json'); + expect(result.files).toContain('/apps/app1/tsconfig.json'); + expect(result.files).toContain('/apps/app1-e2e/tsconfig.json'); + expect(result.files).toContain('/apps/app2/tsconfig.json'); + expect(result.files).toContain('/apps/app2-e2e/tsconfig.json'); + expect(result.files).toContain('/apps/node-app/tsconfig.json'); + expect(result.files).toContain('/apps/weird/app/tsconfig.json'); + expect(result.files).toContain('/libs/lib1/tsconfig.json'); + expect(result.files).toContain('/libs/lib2/tsconfig.json'); + [ + '/apps/app1/tsconfig.json', + '/apps/app1-e2e/tsconfig.json', + '/apps/app2/tsconfig.json', + '/apps/app2-e2e/tsconfig.json', + '/apps/node-app/tsconfig.json', + '/libs/lib1/tsconfig.json', + '/libs/lib2/tsconfig.json' + ].forEach(tsConfig => { + const value = readJsonInTree(result, tsConfig); + expect(value.extends).toEqual('../../tsconfig.json'); + }); + expect( + readJsonInTree(result, 'apps/weird/app/tsconfig.json').extends + ).toEqual('../../../tsconfig.json'); + }); + + it('should edit existing tsconfigs to extend the new one', async () => { + const result = await schematicRunner + .runSchematicAsync('update-7.2.0', {}, initialTree) + .toPromise(); + [ + '/apps/app1/tsconfig.app.json', + '/apps/app1/tsconfig.spec.json', + '/apps/app1-e2e/tsconfig.e2e.json', + '/apps/app2/tsconfig.app.json', + '/apps/app2/tsconfig.spec.json', + '/apps/app2-e2e/tsconfig.e2e.json', + '/apps/node-app/tsconfig.app.json', + '/apps/node-app/tsconfig.spec.json', + '/libs/lib1/tsconfig.lib.json', + '/libs/lib1/tsconfig.spec.json', + '/libs/lib2/tsconfig.lib.json', + '/libs/lib2/tsconfig.spec.json' + ].forEach(tsConfig => { + const value = readJsonInTree(result, tsConfig); + expect(value.extends).toEqual('./tsconfig.json'); + }); + expect( + readJsonInTree(result, 'apps/weird/app/src/tsconfig.app.json').extends + ).toEqual('../tsconfig.json'); + expect( + readJsonInTree(result, 'apps/weird/app/src/tsconfig.spec.json').extends + ).toEqual('../tsconfig.json'); + }); + + it('should edit existing tsconfigs to have a union of all types being used', async () => { + const result = await schematicRunner + .runSchematicAsync('update-7.2.0', {}, initialTree) + .toPromise(); + + function getTypes(path: string) { + return readJsonInTree(result, path).compilerOptions.types; + } + + expect(getTypes('apps/app1/tsconfig.json')).toEqual([ + 'jquery', + 'jasmine', + 'node', + 'sinon' + ]); + expect(getTypes('apps/app1-e2e/tsconfig.json')).toEqual([ + 'jasmine', + 'jasminewd2', + 'node' + ]); + expect(getTypes('apps/app2/tsconfig.json')).toEqual(['jest', 'node']); + expect(getTypes('apps/app2-e2e/tsconfig.json')).toEqual([ + 'cypress', + 'node' + ]); + expect(getTypes('apps/node-app/tsconfig.json')).toEqual(['node', 'jest']); + expect(getTypes('apps/weird/app/tsconfig.json')).toBeUndefined(); + expect(getTypes('libs/lib1/tsconfig.json')).toEqual(['jasmine', 'node']); + expect(getTypes('libs/lib2/tsconfig.json')).toEqual(['jest', 'node']); + }); + + it("should not set types if one of the project's tsconfigs do not have types defined", async () => { + initialTree = await schematicRunner + .callRule( + updateJsonInTree('apps/app1/tsconfig.app.json', json => { + delete json.compilerOptions.types; + return json; + }), + initialTree + ) + .toPromise(); + const result = await schematicRunner + .runSchematicAsync('update-7.2.0', {}, initialTree) + .toPromise(); + + function getTypes(path: string) { + return readJsonInTree(result, path).compilerOptions.types; + } + + expect(getTypes('apps/app1/tsconfig.json')).toBeUndefined(); + }); +}); diff --git a/packages/schematics/migrations/update-7-2-0/update-7-2-0.ts b/packages/schematics/migrations/update-7-2-0/update-7-2-0.ts new file mode 100644 index 0000000000..0dce6f6566 --- /dev/null +++ b/packages/schematics/migrations/update-7-2-0/update-7-2-0.ts @@ -0,0 +1,160 @@ +import { + Rule, + chain, + noop, + SchematicContext, + Tree +} from '@angular-devkit/schematics'; +import { normalize, join, Path, dirname } from '@angular-devkit/core'; + +import { relative } from 'path'; + +import { updateJsonInTree, readJsonInTree } from '../../src/utils/ast-utils'; +import { getWorkspacePath } from '../../src/utils/cli-config-utils'; +import { offsetFromRoot } from '../../src/utils/common'; +import { stripIndents } from '@angular-devkit/core/src/utils/literals'; + +function getBuilders(project: any): string[] { + return Array.from( + new Set(Object.values(project.architect).map(target => target.builder)) + ); +} + +const builderTypes: { [key: string]: string[] } = { + '@angular-devkit/build-angular:karma': ['jasmine'], + '@angular-devkit/build-angular:protractor': ['jasmine', 'jasminewd2'], + '@nrwl/builders:jest': ['jest', 'node'], + '@nrwl/builers:cypress': ['cypress'] +}; + +function getTypes(host: Tree, project: any, context: SchematicContext) { + let types = []; + + const tsConfigs = getTsConfigs(project).map(tsconfigPath => + readJsonInTree(host, tsconfigPath) + ); + + const tsConfigsWithNoTypes = getTsConfigs(project).filter(tsconfigPath => { + const tsconfig = readJsonInTree(host, tsconfigPath); + return !tsconfig.compilerOptions.types; + }); + + if (tsConfigsWithNoTypes.length > 0) { + context.logger.warn( + stripIndents`The following tsconfigs had no types defined: ${tsConfigsWithNoTypes.join( + ',' + )}` + ); + return undefined; + } + + types = types.concat( + ...tsConfigs.map(tsconfig => tsconfig.compilerOptions.types || []) + ); + + types = types.concat( + ...getBuilders(project) + .filter(builder => builder in builderTypes) + .map(builder => builderTypes[builder]) + ); + + return types.filter((type, i, arr) => arr.indexOf(type) === i); // dedupe the array; +} + +function createTsConfig(project: any): Rule { + return (host: Tree, context: SchematicContext) => { + const tsConfigPath = join(normalize(project.root), 'tsconfig.json'); + if (host.exists(tsConfigPath)) { + return noop(); + } + host.create(tsConfigPath, '{}'); + const types = getTypes(host, project, context); + if (types === undefined) { + context.logger.warn( + stripIndents`No types array was added to ${tsConfigPath} meaning the editor might encounter conflicts for types.}` + ); + } + return updateJsonInTree(tsConfigPath, () => { + return { + extends: `${offsetFromRoot(project.root)}tsconfig.json`, + compilerOptions: { + types + } + }; + }); + }; +} + +function getTsConfigs(project: any): Path[] { + return Array.from( + new Set( + Object.values(project.architect) + .reduce( + (arr: any[], target) => { + return [ + ...arr, + ...(target.options ? [target.options] : []), + ...Object.values(target.configurations || {}) + ] as any[]; + }, + [] + ) + .reduce((arr: string[], options) => { + if (!options.tsConfig) { + return arr; + } + if (!Array.isArray(options.tsConfig)) { + return arr.includes(options.tsConfig) + ? arr + : [...arr, options.tsConfig]; + } + return [ + ...arr, + ...options.tsConfig.filter(tsconfig => !arr.includes(tsconfig)) + ]; + }, []) + .map(tsconfig => { + return normalize(tsconfig); + }) + ) + ); +} + +function updateTsConfig(project: any, tsconfig: Path): Rule { + return updateJsonInTree(tsconfig, json => { + json.extends = + dirname(tsconfig) === normalize(project.root) + ? './tsconfig.json' + : relative(dirname(tsconfig), join(project.root, 'tsconfig.json')); + return json; + }); +} + +function updateTsConfigs(project: any): Rule { + return (host: Tree, context: SchematicContext) => { + return chain( + getTsConfigs(project).map(tsconfig => updateTsConfig(project, tsconfig)) + ); + }; +} + +function updateProjects(host: Tree) { + const { projects } = readJsonInTree(host, getWorkspacePath(host)); + return chain( + Object.values(projects).map(project => { + return chain([createTsConfig(project), updateTsConfigs(project)]); + }) + ); +} + +function displayInformation(host: Tree, context: SchematicContext) { + context.logger + .info(stripIndents`With this update, we are changing the structure of the tsconfig files. + A tsconfig.json has been added to all project roots which is used by editors to provide intellisense. + The tsconfig.(app|lib|spec|e2e).json files now all extend off of the tsconfig.json in the project root. + To find out more, visit our wiki: https://github.com/nrwl/nx/wiki/Workspace-Organization#tsconfigs`); +} + +export default function(): Rule { + return chain([updateProjects, displayInformation]); +} diff --git a/packages/schematics/src/collection/application/application.spec.ts b/packages/schematics/src/collection/application/application.spec.ts index d38b1f5f86..bcd51a41f3 100644 --- a/packages/schematics/src/collection/application/application.spec.ts +++ b/packages/schematics/src/collection/application/application.spec.ts @@ -68,13 +68,17 @@ describe('app', () => { getFileContent(tree, 'apps/my-app/src/app/app.module.ts') ).toContain('class AppModule'); + const tsconfig = readJsonInTree(tree, 'apps/my-app/tsconfig.json'); + expect(tsconfig.extends).toEqual('../../tsconfig.json'); + expect(tsconfig.compilerOptions.types).toContain('jasmine'); + const tsconfigApp = JSON.parse( stripJsonComments(getFileContent(tree, 'apps/my-app/tsconfig.app.json')) ); expect(tsconfigApp.compilerOptions.outDir).toEqual( '../../dist/out-tsc/apps/my-app' ); - expect(tsconfigApp.extends).toEqual('../../tsconfig.json'); + expect(tsconfigApp.extends).toEqual('./tsconfig.json'); const tslintJson = JSON.parse( stripJsonComments(getFileContent(tree, 'apps/my-app/tslint.json')) @@ -90,7 +94,7 @@ describe('app', () => { expect(tsconfigE2E.compilerOptions.outDir).toEqual( '../../dist/out-tsc/apps/my-app-e2e' ); - expect(tsconfigE2E.extends).toEqual('../../tsconfig.json'); + expect(tsconfigE2E.extends).toEqual('./tsconfig.json'); }); it('should default the prefix to npmScope', () => { @@ -216,11 +220,21 @@ describe('app', () => { // Make sure these have properties [ + { + path: 'apps/my-dir/my-app/tsconfig.json', + lookupFn: json => json.extends, + expectedValue: '../../../tsconfig.json' + }, { path: 'apps/my-dir/my-app/tsconfig.app.json', lookupFn: json => json.compilerOptions.outDir, expectedValue: '../../../dist/out-tsc/apps/my-dir/my-app' }, + { + path: 'apps/my-dir/my-app-e2e/tsconfig.json', + lookupFn: json => json.extends, + expectedValue: '../../../tsconfig.json' + }, { path: 'apps/my-dir/my-app-e2e/tsconfig.e2e.json', lookupFn: json => json.compilerOptions.outDir, diff --git a/packages/schematics/src/collection/application/files/tsconfig.json b/packages/schematics/src/collection/application/files/tsconfig.json new file mode 100644 index 0000000000..af7fb82ae3 --- /dev/null +++ b/packages/schematics/src/collection/application/files/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "<%= offsetFromRoot %>tsconfig.json", + "compilerOptions": { + "types": [] + }, + "include": ["**/*.ts"] +} diff --git a/packages/schematics/src/collection/application/index.ts b/packages/schematics/src/collection/application/index.ts index 961c36d797..ebf9b8e194 100644 --- a/packages/schematics/src/collection/application/index.ts +++ b/packages/schematics/src/collection/application/index.ts @@ -5,7 +5,12 @@ import { Rule, Tree, SchematicContext, - schematic + schematic, + mergeWith, + apply, + template, + move as devkitMove, + url } from '@angular-devkit/schematics'; import { Schema } from './schema'; import * as ts from 'typescript'; @@ -153,6 +158,27 @@ Nx is designed to help you create and build enterprise grade Angular application }; } +function addTsconfigs(options: NormalizedSchema): Rule { + return chain([ + mergeWith( + apply(url('./files'), [ + template({ + offsetFromRoot: offsetFromRoot(options.appProjectRoot) + }), + devkitMove(options.appProjectRoot) + ]) + ), + mergeWith( + apply(url('./files'), [ + template({ + offsetFromRoot: offsetFromRoot(options.e2eProjectRoot) + }), + devkitMove(options.e2eProjectRoot) + ]) + ) + ]); +} + function updateProject(options: NormalizedSchema): Rule { return (host: Tree) => { return chain([ @@ -193,7 +219,7 @@ function updateProject(options: NormalizedSchema): Rule { updateJsonInTree(`${options.appProjectRoot}/tsconfig.app.json`, json => { return { ...json, - extends: `${offsetFromRoot(options.appProjectRoot)}tsconfig.json`, + extends: `./tsconfig.json`, compilerOptions: { ...json.compilerOptions, outDir: `${offsetFromRoot(options.appProjectRoot)}dist/out-tsc/${ @@ -208,23 +234,35 @@ function updateProject(options: NormalizedSchema): Rule { }; }), options.unitTestRunner === 'karma' - ? updateJsonInTree( - `${options.appProjectRoot}/tsconfig.spec.json`, - json => { - return { - ...json, - extends: `${offsetFromRoot( - options.appProjectRoot - )}tsconfig.json`, - compilerOptions: { - ...json.compilerOptions, - outDir: `${offsetFromRoot( - options.appProjectRoot - )}dist/out-tsc/${options.appProjectRoot}` - } - }; - } - ) + ? chain([ + updateJsonInTree( + `${options.appProjectRoot}/tsconfig.json`, + json => { + return { + ...json, + compilerOptions: { + ...json.compilerOptions, + types: [...json.compilerOptions.types, 'jasmine'] + } + }; + } + ), + updateJsonInTree( + `${options.appProjectRoot}/tsconfig.spec.json`, + json => { + return { + ...json, + extends: `./tsconfig.json`, + compilerOptions: { + ...json.compilerOptions, + outDir: `${offsetFromRoot( + options.appProjectRoot + )}dist/out-tsc/${options.appProjectRoot}` + } + }; + } + ) + ]) : host => { host.delete(`${options.appProjectRoot}/tsconfig.spec.json`); return host; @@ -271,7 +309,7 @@ function updateProject(options: NormalizedSchema): Rule { host.delete(`${options.e2eProjectRoot}/protractor.conf.js`); } } - ])(host, null); + ]); }; } @@ -302,10 +340,19 @@ function updateE2eProject(options: NormalizedSchema): Rule { json.projects[options.e2eProjectName] = project; return json; }), + updateJsonInTree(`${options.e2eProjectRoot}/tsconfig.json`, json => { + return { + ...json, + compilerOptions: { + ...json.compilerOptions, + types: [...json.compilerOptions.types, 'jasmine', 'jasminewd2'] + } + }; + }), updateJsonInTree(`${options.e2eProjectRoot}/tsconfig.e2e.json`, json => { return { ...json, - extends: `${offsetFromRoot(options.e2eProjectRoot)}tsconfig.json`, + extends: `./tsconfig.json`, compilerOptions: { ...json.compilerOptions, outDir: `${offsetFromRoot(options.e2eProjectRoot)}dist/out-tsc/${ @@ -314,7 +361,7 @@ function updateE2eProject(options: NormalizedSchema): Rule { } }; }) - ])(host, null); + ]); }; } @@ -343,6 +390,7 @@ export default function(schema: Schema): Rule { viewEncapsulation: options.viewEncapsulation, routing: false }), + addTsconfigs(options), move(e2eProjectRoot, options.e2eProjectRoot), diff --git a/packages/schematics/src/collection/cypress-project/cypress-project.spec.ts b/packages/schematics/src/collection/cypress-project/cypress-project.spec.ts index 610c9cab46..4f5fd9823d 100644 --- a/packages/schematics/src/collection/cypress-project/cypress-project.spec.ts +++ b/packages/schematics/src/collection/cypress-project/cypress-project.spec.ts @@ -4,7 +4,7 @@ import { createEmptyWorkspace } from '@nrwl/schematics/src/utils/testing-utils'; import { readJsonInTree } from '@nrwl/schematics/src/utils/ast-utils'; import * as path from 'path'; -describe('schematic:cypres-project', () => { +describe('schematic:cypress-project', () => { const schematicRunner = new SchematicTestRunner( '@nrwl/schematics', path.join(__dirname, '../../collection.json') @@ -123,6 +123,7 @@ describe('schematic:cypres-project', () => { 'apps/my-app-e2e/tsconfig.e2e.json' ); + expect(tsconfigJson.extends).toEqual('./tsconfig.json'); expect(tsconfigJson.compilerOptions.outDir).toEqual( '../../dist/out-tsc/apps/my-app-e2e/src' ); diff --git a/packages/schematics/src/collection/cypress-project/files/src/integration/app.spec.ts__tmpl__ b/packages/schematics/src/collection/cypress-project/files/src/integration/app.spec.ts__tmpl__ index 506f0ea56a..8a389aec02 100644 --- a/packages/schematics/src/collection/cypress-project/files/src/integration/app.spec.ts__tmpl__ +++ b/packages/schematics/src/collection/cypress-project/files/src/integration/app.spec.ts__tmpl__ @@ -1,5 +1,3 @@ -/// - import { getGreeting } from '../support/app.po'; describe('Hello Nx', () => { diff --git a/packages/schematics/src/collection/cypress-project/files/src/support/app.po.ts__tmpl__ b/packages/schematics/src/collection/cypress-project/files/src/support/app.po.ts__tmpl__ index abcdbbb05a..3293424696 100644 --- a/packages/schematics/src/collection/cypress-project/files/src/support/app.po.ts__tmpl__ +++ b/packages/schematics/src/collection/cypress-project/files/src/support/app.po.ts__tmpl__ @@ -1,3 +1 @@ -/// - export const getGreeting = () => cy.get('h1'); diff --git a/packages/schematics/src/collection/cypress-project/files/src/support/commands.ts__tmpl__ b/packages/schematics/src/collection/cypress-project/files/src/support/commands.ts__tmpl__ index cf62f9b907..c1f5a772e2 100644 --- a/packages/schematics/src/collection/cypress-project/files/src/support/commands.ts__tmpl__ +++ b/packages/schematics/src/collection/cypress-project/files/src/support/commands.ts__tmpl__ @@ -1,5 +1,3 @@ -/// - // *********************************************** // This example commands.js shows you how to // create various custom commands and overwrite diff --git a/packages/schematics/src/collection/cypress-project/files/tsconfig.e2e.json b/packages/schematics/src/collection/cypress-project/files/tsconfig.e2e.json index 4e8f7033f2..830949e34d 100644 --- a/packages/schematics/src/collection/cypress-project/files/tsconfig.e2e.json +++ b/packages/schematics/src/collection/cypress-project/files/tsconfig.e2e.json @@ -1,5 +1,5 @@ { - "extends": "<%= offsetFromRoot %>tsconfig.json", + "extends": "./tsconfig.json", "compilerOptions": { "sourceMap": false, "outDir": "<%= offsetFromRoot %>dist/out-tsc/<%= projectRoot %>/src", diff --git a/packages/schematics/src/collection/cypress-project/index.ts b/packages/schematics/src/collection/cypress-project/index.ts index 029c719e0b..9b1f1b1417 100644 --- a/packages/schematics/src/collection/cypress-project/index.ts +++ b/packages/schematics/src/collection/cypress-project/index.ts @@ -19,7 +19,6 @@ import { updateJsonInTree } from '../../utils/ast-utils'; import { cypressVersion, nxVersion } from '../../lib-versions'; -import { replaceAppNameWithPath } from '../../utils/cli-config-utils'; import { offsetFromRoot } from '../../utils/common'; import { Schema } from '../application/schema'; @@ -107,6 +106,21 @@ function generateFiles(options: CypressProjectSchema): Rule { }; } +function updateTsConfig(options: CypressProjectSchema): Rule { + return updateJsonInTree( + join(normalize(options.e2eProjectRoot), 'tsconfig.json'), + json => { + return { + ...json, + compilerOptions: { + ...json.compilerOptions, + types: [...json.compilerOptions.types, 'cypress'] + } + }; + } + ); +} + function updateAngularJson(options: CypressProjectSchema): Rule { return updateJsonInTree('angular.json', json => { const projectConfig = json.projects[options.e2eProjectName]; @@ -139,6 +153,7 @@ export default function(options: CypressProjectSchema): Rule { checkArchitectTarget(options), checkDependenciesInstalled(), updateAngularJson(options), + updateTsConfig(options), generateFiles(options) ]); } diff --git a/packages/schematics/src/collection/jest-project/files/tsconfig.spec.json b/packages/schematics/src/collection/jest-project/files/tsconfig.spec.json index c5ed826d2a..ed4c425591 100644 --- a/packages/schematics/src/collection/jest-project/files/tsconfig.spec.json +++ b/packages/schematics/src/collection/jest-project/files/tsconfig.spec.json @@ -1,5 +1,5 @@ { - "extends": "<%= offsetFromRoot %>tsconfig.json", + "extends": "./tsconfig.json", "compilerOptions": { "outDir": "<%= offsetFromRoot %>dist/out-tsc/<%= projectRoot %>", "module": "commonjs", diff --git a/packages/schematics/src/collection/jest-project/index.ts b/packages/schematics/src/collection/jest-project/index.ts index 9149625936..3cb5eb32c2 100644 --- a/packages/schematics/src/collection/jest-project/index.ts +++ b/packages/schematics/src/collection/jest-project/index.ts @@ -45,6 +45,21 @@ function generateFiles(options: JestProjectSchema): Rule { }; } +function updateTsConfig(options: JestProjectSchema): Rule { + return (host: Tree, context: SchematicContext) => { + const projectConfig = getProjectConfig(host, options.project); + return updateJsonInTree(join(projectConfig.root, 'tsconfig.json'), json => { + return { + ...json, + compilerOptions: { + ...json.compilerOptions, + types: [...json.compilerOptions.types, 'node', 'jest'] + } + }; + }); + }; +} + function updateAngularJson(options: JestProjectSchema): Rule { return updateJsonInTree('angular.json', json => { const projectConfig = json.projects[options.project]; @@ -90,6 +105,7 @@ export default function(options: JestProjectSchema): Rule { return chain([ check(options), generateFiles(options), + updateTsConfig(options), updateAngularJson(options) ]); } diff --git a/packages/schematics/src/collection/jest-project/jest-project.spec.ts b/packages/schematics/src/collection/jest-project/jest-project.spec.ts index 51de2c17dc..ce7cda56be 100644 --- a/packages/schematics/src/collection/jest-project/jest-project.spec.ts +++ b/packages/schematics/src/collection/jest-project/jest-project.spec.ts @@ -77,6 +77,19 @@ describe('lib', () => { `); }); + it('should update the local tsconfig.json', () => { + const resultTree = schematicRunner.runSchematic( + 'jest-project', + { + project: 'lib1' + }, + appTree + ); + const tsConfig = readJsonInTree(resultTree, 'libs/lib1/tsconfig.json'); + expect(tsConfig.compilerOptions.types).toContain('jest'); + expect(tsConfig.compilerOptions.types).toContain('node'); + }); + it('should create a tsconfig.spec.json', () => { const resultTree = schematicRunner.runSchematic( 'jest-project', @@ -87,7 +100,7 @@ describe('lib', () => { ); const tsConfig = readJsonInTree(resultTree, 'libs/lib1/tsconfig.spec.json'); expect(tsConfig).toEqual({ - extends: '../../tsconfig.json', + extends: './tsconfig.json', compilerOptions: { module: 'commonjs', outDir: '../../dist/out-tsc/libs/lib1', diff --git a/packages/schematics/src/collection/library/files/tsconfig.json b/packages/schematics/src/collection/library/files/tsconfig.json new file mode 100644 index 0000000000..af7fb82ae3 --- /dev/null +++ b/packages/schematics/src/collection/library/files/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "<%= offsetFromRoot %>tsconfig.json", + "compilerOptions": { + "types": [] + }, + "include": ["**/*.ts"] +} diff --git a/packages/schematics/src/collection/library/index.ts b/packages/schematics/src/collection/library/index.ts index 4f3163e8c5..b3d914853b 100644 --- a/packages/schematics/src/collection/library/index.ts +++ b/packages/schematics/src/collection/library/index.ts @@ -5,7 +5,12 @@ import { Rule, Tree, SchematicContext, - schematic + schematic, + url, + apply, + mergeWith, + move as devkitMove, + template } from '@angular-devkit/schematics'; import { Schema } from './schema'; import * as path from 'path'; @@ -224,7 +229,7 @@ function updateNgPackage(options: NormalizedSchema): Rule { } function updateProject(options: NormalizedSchema): Rule { - return (host: Tree) => { + return (host: Tree, context: SchematicContext) => { const libRoot = `${options.projectRoot}/src/lib/`; host.delete(path.join(libRoot, `${options.name}.service.ts`)); @@ -295,6 +300,14 @@ function updateProject(options: NormalizedSchema): Rule { } return chain([ + mergeWith( + apply(url('./files'), [ + template({ + offsetFromRoot: offsetFromRoot(options.projectRoot) + }), + devkitMove(options.projectRoot) + ]) + ), updateJsonInTree(getWorkspacePath(host), json => { const project = json.projects[options.name]; const fixedProject = replaceAppNameWithPath( @@ -334,7 +347,7 @@ function updateProject(options: NormalizedSchema): Rule { json.exclude = json.exclude || []; return { ...json, - extends: `${offsetFromRoot(options.projectRoot)}tsconfig.json`, + extends: `./tsconfig.json`, compilerOptions: { ...json.compilerOptions, outDir: `${offsetFromRoot(options.projectRoot)}dist/out-tsc/${ @@ -360,7 +373,7 @@ function updateProject(options: NormalizedSchema): Rule { }), updateNgPackage(options), options.unitTestRunner === 'karma' ? updateKarmaConfig(options) : noop() - ])(host, null); + ])(host, context); }; } @@ -378,10 +391,19 @@ function updateKarmaConfig(options: NormalizedSchema) { ) ); }, + updateJsonInTree(`${options.projectRoot}/tsconfig.json`, json => { + return { + ...json, + compilerOptions: { + ...json.compilerOptions, + types: [...json.compilerOptions.types, 'jasmine'] + } + }; + }), updateJsonInTree(`${options.projectRoot}/tsconfig.spec.json`, json => { return { ...json, - extends: `${offsetFromRoot(options.projectRoot)}tsconfig.json`, + extends: `./tsconfig.json`, compilerOptions: { ...json.compilerOptions, outDir: `${offsetFromRoot(options.projectRoot)}dist/out-tsc/${ diff --git a/packages/schematics/src/collection/library/library.spec.ts b/packages/schematics/src/collection/library/library.spec.ts index ef9f3c0c2a..f9ffca0141 100644 --- a/packages/schematics/src/collection/library/library.spec.ts +++ b/packages/schematics/src/collection/library/library.spec.ts @@ -115,7 +115,7 @@ describe('lib', () => { }); }); - it('should update tsconfig.json', () => { + it('should update root tsconfig.json', () => { const tree = schematicRunner.runSchematic( 'lib', { name: 'myLib' }, @@ -127,6 +127,51 @@ describe('lib', () => { ]); }); + it('should create a local tsconfig.json', () => { + const tree = schematicRunner.runSchematic( + 'lib', + { name: 'myLib' }, + appTree + ); + + const tsconfigJson = readJsonInTree(tree, 'libs/my-lib/tsconfig.json'); + expect(tsconfigJson).toEqual({ + extends: '../../tsconfig.json', + compilerOptions: { + types: ['jasmine'] + }, + include: ['**/*.ts'] + }); + }); + + it('should extend the local tsconfig.json with tsconfig.spec.json', () => { + const tree = schematicRunner.runSchematic( + 'lib', + { name: 'myLib' }, + appTree + ); + + const tsconfigJson = readJsonInTree( + tree, + 'libs/my-lib/tsconfig.spec.json' + ); + expect(tsconfigJson.extends).toEqual('./tsconfig.json'); + }); + + it('should extend the local tsconfig.json with tsconfig.lib.json', () => { + const tree = schematicRunner.runSchematic( + 'lib', + { name: 'myLib' }, + appTree + ); + + const tsconfigJson = readJsonInTree( + tree, + 'libs/my-lib/tsconfig.lib.json' + ); + expect(tsconfigJson.extends).toEqual('./tsconfig.json'); + }); + it('should generate files', () => { const tree = schematicRunner.runSchematic( 'lib', @@ -345,6 +390,26 @@ describe('lib', () => { ).toBeUndefined(); }); + it('should create a local tsconfig.json', () => { + const tree = schematicRunner.runSchematic( + 'lib', + { name: 'myLib', directory: 'myDir' }, + appTree + ); + + const tsconfigJson = readJsonInTree( + tree, + 'libs/my-dir/my-lib/tsconfig.json' + ); + expect(tsconfigJson).toEqual({ + extends: '../../../tsconfig.json', + compilerOptions: { + types: ['jasmine'] + }, + include: ['**/*.ts'] + }); + }); + it('should not generate a module for --module false', () => { const tree = schematicRunner.runSchematic( 'lib', diff --git a/packages/schematics/src/collection/node-application/files/app/tsconfig.app.json b/packages/schematics/src/collection/node-application/files/app/tsconfig.app.json index 73975fa8e4..d071717839 100644 --- a/packages/schematics/src/collection/node-application/files/app/tsconfig.app.json +++ b/packages/schematics/src/collection/node-application/files/app/tsconfig.app.json @@ -1,5 +1,5 @@ { - "extends": "<%= offset %>tsconfig.json", + "extends": "./tsconfig.json", "compilerOptions": { "outDir": "<%= offset %>dist/out-tsc/<%= root %>", "types": ["node"] diff --git a/packages/schematics/src/collection/node-application/files/app/tsconfig.json b/packages/schematics/src/collection/node-application/files/app/tsconfig.json new file mode 100644 index 0000000000..9a3276426f --- /dev/null +++ b/packages/schematics/src/collection/node-application/files/app/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "<%= offset %>tsconfig.json", + "compilerOptions": { + "types": ["node", "express"] + }, + "include": ["**/*.ts"] +} diff --git a/packages/schematics/src/collection/node-application/node-application.spec.ts b/packages/schematics/src/collection/node-application/node-application.spec.ts index 804c7e89de..ab852354cc 100644 --- a/packages/schematics/src/collection/node-application/node-application.spec.ts +++ b/packages/schematics/src/collection/node-application/node-application.spec.ts @@ -97,6 +97,12 @@ describe('node-app', () => { 'res.send(`Welcome to my-node-app!`);' ); + const tsconfig = readJsonInTree(tree, 'apps/my-node-app/tsconfig.json'); + expect(tsconfig.extends).toEqual('../../tsconfig.json'); + expect(tsconfig.compilerOptions.types).toContain('node'); + expect(tsconfig.compilerOptions.types).toContain('express'); + expect(tsconfig.compilerOptions.types).toContain('jest'); + const tsconfigApp = JSON.parse( stripJsonComments( getFileContent(tree, 'apps/my-node-app/tsconfig.app.json') @@ -105,7 +111,7 @@ describe('node-app', () => { expect(tsconfigApp.compilerOptions.outDir).toEqual( '../../dist/out-tsc/apps/my-node-app' ); - expect(tsconfigApp.extends).toEqual('../../tsconfig.json'); + expect(tsconfigApp.extends).toEqual('./tsconfig.json'); const tslintJson = JSON.parse( stripJsonComments(getFileContent(tree, 'apps/my-node-app/tslint.json')) @@ -180,6 +186,11 @@ describe('node-app', () => { // Make sure these have properties [ + { + path: 'apps/my-dir/my-node-app/tsconfig.json', + lookupFn: json => json.extends, + expectedValue: '../../../tsconfig.json' + }, { path: 'apps/my-dir/my-node-app/tsconfig.app.json', lookupFn: json => json.compilerOptions.outDir,