feat(schematics): create local project tsconfigs for typings

This commit is contained in:
Jason Jean 2018-11-23 01:50:32 -05:00 committed by Victor Savkin
parent d0c9c5e05d
commit cd2e311a32
21 changed files with 826 additions and 43 deletions

View File

@ -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"
}
}
}

View File

@ -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();
});
});

View File

@ -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<any>(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<Path>(
Object.values<any>(project.architect)
.reduce(
(arr: any[], target) => {
return [
...arr,
...(target.options ? [target.options] : []),
...Object.values<any>(target.configurations || {})
] as any[];
},
<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<any>(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]);
}

View File

@ -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,

View File

@ -0,0 +1,7 @@
{
"extends": "<%= offsetFromRoot %>tsconfig.json",
"compilerOptions": {
"types": []
},
"include": ["**/*.ts"]
}

View File

@ -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,14 +234,25 @@ function updateProject(options: NormalizedSchema): Rule {
};
}),
options.unitTestRunner === 'karma'
? updateJsonInTree(
? 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: `${offsetFromRoot(
options.appProjectRoot
)}tsconfig.json`,
extends: `./tsconfig.json`,
compilerOptions: {
...json.compilerOptions,
outDir: `${offsetFromRoot(
@ -225,6 +262,7 @@ function updateProject(options: NormalizedSchema): Rule {
};
}
)
])
: 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),

View File

@ -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'
);

View File

@ -1,5 +1,3 @@
/// <reference types="cypress" />
import { getGreeting } from '../support/app.po';
describe('Hello Nx', () => {

View File

@ -1,3 +1 @@
/// <reference types="cypress" />
export const getGreeting = () => cy.get('h1');

View File

@ -1,5 +1,3 @@
/// <reference types="cypress" />
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite

View File

@ -1,5 +1,5 @@
{
"extends": "<%= offsetFromRoot %>tsconfig.json",
"extends": "./tsconfig.json",
"compilerOptions": {
"sourceMap": false,
"outDir": "<%= offsetFromRoot %>dist/out-tsc/<%= projectRoot %>/src",

View File

@ -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)
]);
}

View File

@ -1,5 +1,5 @@
{
"extends": "<%= offsetFromRoot %>tsconfig.json",
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "<%= offsetFromRoot %>dist/out-tsc/<%= projectRoot %>",
"module": "commonjs",

View File

@ -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)
]);
}

View File

@ -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',

View File

@ -0,0 +1,7 @@
{
"extends": "<%= offsetFromRoot %>tsconfig.json",
"compilerOptions": {
"types": []
},
"include": ["**/*.ts"]
}

View File

@ -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/${

View File

@ -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',

View File

@ -1,5 +1,5 @@
{
"extends": "<%= offset %>tsconfig.json",
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "<%= offset %>dist/out-tsc/<%= root %>",
"types": ["node"]

View File

@ -0,0 +1,7 @@
{
"extends": "<%= offset %>tsconfig.json",
"compilerOptions": {
"types": ["node", "express"]
},
"include": ["**/*.ts"]
}

View File

@ -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,