fix(angular): add tsconfig.editor.json to catch misc files" (#3810)

This commit is contained in:
Jason Jean 2020-09-28 13:23:56 -04:00 committed by GitHub
parent 01d8c98a90
commit 6885507567
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 680 additions and 31 deletions

View File

@ -6,6 +6,7 @@ node_modules
packages/workspace/src/schematics/**/files/**/*.json
packages/workspace/src/core/dep-graph/vendor.js
packages/angular/src/schematics/**/files/**/*.json
packages/angular/src/migrations/**/files/**/*.json
packages/web/src/schematics/**/files/**/*.json
packages/node/src/schematics/**/files/**/*.json
packages/express/src/schematics/**/files/**/*.json

View File

@ -38,7 +38,7 @@
},
"update-10-3-0": {
"version": "10.3.0-beta.1",
"description": "Update jest-preset-angular library to version 8.3.1",
"description": "Add tsconfig.editor.json to angular apps and update jest-angular-preset",
"factory": "./src/migrations/update-10-3-0/update-10-3-0"
}
},

View File

@ -0,0 +1,11 @@
{<% if (baseConfigPath) { %>
"extends": "<%= baseConfigPath %>",<% } %>
"include": ["**/*.ts"],
"compilerOptions": {
"types": [
<% if (usesJest) { %>"jest", <% } %>
<% if (usesKarma) { %>"jasmine", <% } %>
"node"
]
}
}

View File

@ -0,0 +1,189 @@
import { callRule, runMigration } from '../../utils/testing';
import { chain, Tree } from '@angular-devkit/schematics';
import {
readJsonInTree,
updateJsonInTree,
updateWorkspace,
} from '@nrwl/workspace';
import { createEmptyWorkspace } from '@nrwl/workspace/testing';
describe('update-10.3.0', () => {
describe('tsconfig.editor.json migration', () => {
let tree: Tree;
beforeAll(async () => {
tree = Tree.empty();
tree = createEmptyWorkspace(tree);
tree = await callRule(
updateWorkspace((workspace) => {
workspace.projects.add({
name: 'app1',
root: 'apps/app1',
targets: {
build: {
builder: '@angular-devkit/build-angular:browser',
options: {
tsConfig: 'apps/app1/tsconfig.app.json',
},
},
test: {
builder: '@angular-devkit/build-angular:karma',
},
},
});
workspace.projects.add({
name: 'app2',
root: 'apps/app2',
targets: {
build: {
builder: '@angular-devkit/build-angular:browser',
options: {
tsConfig: 'apps/app2/tsconfig.app.json',
},
},
test: {
builder: '@nrwl/jest:jest',
},
},
});
workspace.projects.add({
name: 'app3',
root: 'apps/app3',
targets: {
build: {
builder: '@angular-devkit/build-angular:browser',
options: {
tsConfig: 'apps/app3/tsconfig.app.json',
},
},
test: {
builder: '@angular-devkit/build-angular:karma',
},
test2: {
builder: '@nrwl/jest:jest',
},
},
});
workspace.projects.add({
name: 'app4',
root: 'apps/app4',
targets: {
build: {
builder: '@angular-devkit/build-angular:browser',
options: {
tsConfig: 'apps/app4/tsconfig.app.json',
},
},
},
});
workspace.projects.add({
name: 'app5',
root: 'apps/app5',
targets: {},
});
}),
tree
);
tree = await callRule(
chain([
updateJsonInTree('apps/app1/tsconfig.app.json', () => ({
extends: './tsconfig.json',
})),
updateJsonInTree('apps/app1/tsconfig.json', () => ({
references: [],
})),
updateJsonInTree('apps/app2/tsconfig.app.json', () => ({
extends: './tsconfig.json',
})),
updateJsonInTree('apps/app2/tsconfig.json', () => ({
references: [],
})),
updateJsonInTree('apps/app3/tsconfig.app.json', () => ({
extends: './tsconfig.json',
})),
updateJsonInTree('apps/app3/tsconfig.json', () => ({
references: [],
})),
updateJsonInTree('apps/app4/tsconfig.app.json', () => ({
extends: './tsconfig.json',
})),
updateJsonInTree('apps/app4/tsconfig.json', () => ({
references: [],
})),
]),
tree
);
tree = await runMigration('update-10-3-0', {}, tree);
});
it('should create an tsconfig.editor.json for karma', async () => {
const tsconfig = readJsonInTree(tree, 'apps/app1/tsconfig.editor.json');
expect(tsconfig).toEqual({
compilerOptions: {
types: ['jasmine', 'node'],
},
extends: './tsconfig.json',
include: ['**/*.ts'],
});
});
it('should add references to base config', () => {
expect(
readJsonInTree(tree, 'apps/app1/tsconfig.json').references
).toContainEqual({
path: './tsconfig.editor.json',
});
expect(
readJsonInTree(tree, 'apps/app2/tsconfig.json').references
).toContainEqual({
path: './tsconfig.editor.json',
});
expect(
readJsonInTree(tree, 'apps/app3/tsconfig.json').references
).toContainEqual({
path: './tsconfig.editor.json',
});
expect(
readJsonInTree(tree, 'apps/app4/tsconfig.json').references
).toContainEqual({
path: './tsconfig.editor.json',
});
});
it('should create an tsconfig.editor.json for jest', async () => {
const tsconfig = readJsonInTree(tree, 'apps/app2/tsconfig.editor.json');
expect(tsconfig).toEqual({
compilerOptions: {
types: ['jest', 'node'],
},
extends: './tsconfig.json',
include: ['**/*.ts'],
});
});
it('should create an tsconfig.editor.json for jest and karma', async () => {
const tsconfig = readJsonInTree(tree, 'apps/app3/tsconfig.editor.json');
expect(tsconfig).toEqual({
compilerOptions: {
types: ['jest', 'jasmine', 'node'],
},
extends: './tsconfig.json',
include: ['**/*.ts'],
});
});
it('should create an tsconfig.editor.json for no unit test runners', async () => {
const tsconfig = readJsonInTree(tree, 'apps/app4/tsconfig.editor.json');
expect(tsconfig).toEqual({
compilerOptions: {
types: ['node'],
},
extends: './tsconfig.json',
include: ['**/*.ts'],
});
});
it('should not create a tsconfig.editor.json for non-angular projects', async () => {
expect(tree.exists('apps/app5/tsconfig.editor.json')).toBeFalsy();
});
});
});

View File

@ -1,13 +1,119 @@
import { chain } from '@angular-devkit/schematics';
import { formatFiles, updatePackagesInPackageJson } from '@nrwl/workspace';
import { join } from 'path';
import {
apply,
chain,
mergeWith,
move,
Rule,
template,
Tree,
url,
} from '@angular-devkit/schematics';
import {
formatFiles,
getWorkspace,
readJsonInTree,
updateJsonInTree,
updatePackagesInPackageJson,
} from '@nrwl/workspace';
import { ProjectDefinition } from '@angular-devkit/core/src/workspace';
import { join as pathJoin } from 'path';
import {
dirname,
join,
normalize,
Path,
relative,
resolve,
} from '@angular-devkit/core';
function relativePath(path1: Path, path2: Path) {
let path = relative(
resolve(normalize('/'), normalize(path1)),
resolve(normalize('/'), path2)
);
if (!path.startsWith('../')) {
path = ('./' + path) as Path;
}
return path;
}
function createEditorTsConfig(
project: ProjectDefinition,
baseConfig: Path | null
): Rule {
let usesJest = false;
let usesKarma = false;
for (let [_, targetConfig] of project.targets) {
if (targetConfig.builder === '@nrwl/jest:jest') {
usesJest = usesJest || true;
} else if (targetConfig.builder === '@angular-devkit/build-angular:karma') {
usesKarma = usesKarma || true;
}
}
let relativeBaseConfigPath = baseConfig
? relativePath(normalize(project.root), baseConfig)
: null;
return mergeWith(
apply(url('./files'), [
template({
usesJest,
usesKarma,
baseConfigPath: relativeBaseConfigPath,
}),
move(project.root),
])
);
}
function updateBaseConfig(project: ProjectDefinition, baseConfig: Path): Rule {
return updateJsonInTree(baseConfig, (json) => {
json.references.push({
path: relativePath(
dirname(baseConfig),
join(normalize(project.root), 'tsconfig.editor.json')
),
});
return json;
});
}
async function createEditorTsConfigs(host: Tree) {
const workspace = await getWorkspace(host);
const rules = [];
workspace.projects.forEach((project) => {
project.targets.forEach((target) => {
if (target.builder === '@angular-devkit/build-angular:browser') {
const baseConfig = target.options?.tsConfig
? resolve(
dirname(normalize(target.options.tsConfig as string)),
readJsonInTree(host, target.options.tsConfig as string).extends
)
: null;
if (
baseConfig &&
!Array.isArray(readJsonInTree(host, baseConfig).references)
) {
return;
}
rules.push(createEditorTsConfig(project, baseConfig));
if (baseConfig) {
rules.push(updateBaseConfig(project, baseConfig));
}
}
});
});
return chain(rules);
}
export default function () {
return chain([
updatePackagesInPackageJson(
join(__dirname, '../../../migrations.json'),
pathJoin(__dirname, '../../../migrations.json'),
'10.3.0'
),
createEditorTsConfigs,
formatFiles(),
]);
}

View File

@ -0,0 +1,303 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`app --linter eslint should add an architect target for lint 1`] = `
Object {
"builder": "@nrwl/linter:lint",
"options": Object {
"exclude": Array [
"**/node_modules/**",
"!apps/my-app/**/*",
],
"linter": "eslint",
"tsConfig": Array [
"apps/my-app/tsconfig.app.json",
"apps/my-app/tsconfig.spec.json",
"apps/my-app/tsconfig.editor.json",
],
},
}
`;
exports[`app --linter eslint should add an architect target for lint 2`] = `
Object {
"builder": "@nrwl/linter:lint",
"options": Object {
"exclude": Array [
"**/node_modules/**",
"!apps/my-app-e2e/**/*",
],
"linter": "eslint",
"tsConfig": Array [
"apps/my-app-e2e/tsconfig.e2e.json",
],
},
}
`;
exports[`app nested should update workspace.json 1`] = `
Object {
"architect": Object {
"build": Object {
"builder": "@angular-devkit/build-angular:browser",
"configurations": Object {
"production": Object {
"budgets": Array [
Object {
"maximumError": "5mb",
"maximumWarning": "2mb",
"type": "initial",
},
Object {
"maximumError": "10kb",
"maximumWarning": "6kb",
"type": "anyComponentStyle",
},
],
"buildOptimizer": true,
"extractCss": true,
"extractLicenses": true,
"fileReplacements": Array [
Object {
"replace": "apps/my-dir/my-app/src/environments/environment.ts",
"with": "apps/my-dir/my-app/src/environments/environment.prod.ts",
},
],
"namedChunks": false,
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"vendorChunk": false,
},
},
"options": Object {
"aot": true,
"assets": Array [
"apps/my-dir/my-app/src/favicon.ico",
"apps/my-dir/my-app/src/assets",
],
"index": "apps/my-dir/my-app/src/index.html",
"main": "apps/my-dir/my-app/src/main.ts",
"outputPath": "dist/apps/my-dir/my-app",
"polyfills": "apps/my-dir/my-app/src/polyfills.ts",
"scripts": Array [],
"styles": Array [
"apps/my-dir/my-app/src/styles.css",
],
"tsConfig": "apps/my-dir/my-app/tsconfig.app.json",
},
},
"extract-i18n": Object {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": Object {
"browserTarget": "my-dir-my-app:build",
},
},
"lint": Object {
"builder": "@angular-devkit/build-angular:tslint",
"options": Object {
"exclude": Array [
"**/node_modules/**",
"!apps/my-dir/my-app/**/*",
],
"tsConfig": Array [
"apps/my-dir/my-app/tsconfig.app.json",
"apps/my-dir/my-app/tsconfig.spec.json",
"apps/my-dir/my-app/tsconfig.editor.json",
],
},
},
"serve": Object {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": Object {
"production": Object {
"browserTarget": "my-dir-my-app:build:production",
},
},
"options": Object {
"browserTarget": "my-dir-my-app:build",
},
},
"test": Object {
"builder": "@nrwl/jest:jest",
"options": Object {
"jestConfig": "apps/my-dir/my-app/jest.config.js",
"passWithNoTests": true,
},
},
},
"prefix": "proj",
"projectType": "application",
"root": "apps/my-dir/my-app",
"schematics": Object {},
"sourceRoot": "apps/my-dir/my-app/src",
}
`;
exports[`app nested should update workspace.json 2`] = `
Object {
"architect": Object {
"e2e": Object {
"builder": "@nrwl/cypress:cypress",
"configurations": Object {
"production": Object {
"devServerTarget": "my-dir-my-app:serve:production",
},
},
"options": Object {
"cypressConfig": "apps/my-dir/my-app-e2e/cypress.json",
"devServerTarget": "my-dir-my-app:serve",
"tsConfig": "apps/my-dir/my-app-e2e/tsconfig.e2e.json",
},
},
"lint": Object {
"builder": "@angular-devkit/build-angular:tslint",
"options": Object {
"exclude": Array [
"**/node_modules/**",
"!apps/my-dir/my-app-e2e/**/*",
],
"tsConfig": Array [
"apps/my-dir/my-app-e2e/tsconfig.e2e.json",
],
},
},
},
"projectType": "application",
"root": "apps/my-dir/my-app-e2e",
"sourceRoot": "apps/my-dir/my-app-e2e/src",
}
`;
exports[`app not nested should update workspace.json 1`] = `
Object {
"architect": Object {
"build": Object {
"builder": "@angular-devkit/build-angular:browser",
"configurations": Object {
"production": Object {
"budgets": Array [
Object {
"maximumError": "5mb",
"maximumWarning": "2mb",
"type": "initial",
},
Object {
"maximumError": "10kb",
"maximumWarning": "6kb",
"type": "anyComponentStyle",
},
],
"buildOptimizer": true,
"extractCss": true,
"extractLicenses": true,
"fileReplacements": Array [
Object {
"replace": "apps/my-app/src/environments/environment.ts",
"with": "apps/my-app/src/environments/environment.prod.ts",
},
],
"namedChunks": false,
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"vendorChunk": false,
},
},
"options": Object {
"aot": true,
"assets": Array [
"apps/my-app/src/favicon.ico",
"apps/my-app/src/assets",
],
"index": "apps/my-app/src/index.html",
"main": "apps/my-app/src/main.ts",
"outputPath": "dist/apps/my-app",
"polyfills": "apps/my-app/src/polyfills.ts",
"scripts": Array [],
"styles": Array [
"apps/my-app/src/styles.css",
],
"tsConfig": "apps/my-app/tsconfig.app.json",
},
},
"extract-i18n": Object {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": Object {
"browserTarget": "my-app:build",
},
},
"lint": Object {
"builder": "@angular-devkit/build-angular:tslint",
"options": Object {
"exclude": Array [
"**/node_modules/**",
"!apps/my-app/**/*",
],
"tsConfig": Array [
"apps/my-app/tsconfig.app.json",
"apps/my-app/tsconfig.spec.json",
"apps/my-app/tsconfig.editor.json",
],
},
},
"serve": Object {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": Object {
"production": Object {
"browserTarget": "my-app:build:production",
},
},
"options": Object {
"browserTarget": "my-app:build",
},
},
"test": Object {
"builder": "@nrwl/jest:jest",
"options": Object {
"jestConfig": "apps/my-app/jest.config.js",
"passWithNoTests": true,
},
},
},
"prefix": "proj",
"projectType": "application",
"root": "apps/my-app",
"schematics": Object {},
"sourceRoot": "apps/my-app/src",
}
`;
exports[`app not nested should update workspace.json 2`] = `
Object {
"architect": Object {
"e2e": Object {
"builder": "@nrwl/cypress:cypress",
"configurations": Object {
"production": Object {
"devServerTarget": "my-app:serve:production",
},
},
"options": Object {
"cypressConfig": "apps/my-app-e2e/cypress.json",
"devServerTarget": "my-app:serve",
"tsConfig": "apps/my-app-e2e/tsconfig.e2e.json",
},
},
"lint": Object {
"builder": "@angular-devkit/build-angular:tslint",
"options": Object {
"exclude": Array [
"**/node_modules/**",
"!apps/my-app-e2e/**/*",
],
"tsConfig": Array [
"apps/my-app-e2e/tsconfig.e2e.json",
],
},
},
},
"projectType": "application",
"root": "apps/my-app-e2e",
"sourceRoot": "apps/my-app-e2e/src",
}
`;

View File

@ -17,17 +17,8 @@ describe('app', () => {
const tree = await runSchematic('app', { name: 'myApp' }, appTree);
const workspaceJson = readJsonInTree(tree, '/workspace.json');
expect(workspaceJson.projects['my-app'].root).toEqual('apps/my-app');
expect(workspaceJson.projects['my-app-e2e'].root).toEqual(
'apps/my-app-e2e'
);
expect(
workspaceJson.projects['my-app'].architect.lint.options.exclude
).toEqual(['**/node_modules/**', '!apps/my-app/**/*']);
expect(
workspaceJson.projects['my-app-e2e'].architect.lint.options.exclude
).toEqual(['**/node_modules/**', '!apps/my-app-e2e/**/*']);
expect(workspaceJson.projects['my-app']).toMatchSnapshot();
expect(workspaceJson.projects['my-app-e2e']).toMatchSnapshot();
});
it('should remove the e2e target on the application', async () => {
@ -71,6 +62,9 @@ describe('app', () => {
expect(tsconfig.references).toContainEqual({
path: './tsconfig.spec.json',
});
expect(tsconfig.references).toContainEqual({
path: './tsconfig.editor.json',
});
const tsconfigApp = JSON.parse(
stripJsonComments(getFileContent(tree, 'apps/my-app/tsconfig.app.json'))
@ -170,20 +164,8 @@ describe('app', () => {
);
const workspaceJson = readJsonInTree(tree, '/workspace.json');
expect(workspaceJson.projects['my-dir-my-app'].root).toEqual(
'apps/my-dir/my-app'
);
expect(workspaceJson.projects['my-dir-my-app-e2e'].root).toEqual(
'apps/my-dir/my-app-e2e'
);
expect(
workspaceJson.projects['my-dir-my-app'].architect.lint.options.exclude
).toEqual(['**/node_modules/**', '!apps/my-dir/my-app/**/*']);
expect(
workspaceJson.projects['my-dir-my-app-e2e'].architect.lint.options
.exclude
).toEqual(['**/node_modules/**', '!apps/my-dir/my-app-e2e/**/*']);
expect(workspaceJson.projects['my-dir-my-app']).toMatchSnapshot();
expect(workspaceJson.projects['my-dir-my-app-e2e']).toMatchSnapshot();
});
it('should update nx.json', async () => {
@ -344,6 +326,25 @@ describe('app', () => {
});
});
describe('--linter', () => {
describe('eslint', () => {
it('should add an architect target for lint', async () => {
const tree = await runSchematic(
'app',
{ name: 'myApp', linter: 'eslint' },
appTree
);
const workspaceJson = readJsonInTree(tree, 'workspace.json');
expect(
workspaceJson.projects['my-app'].architect.lint
).toMatchSnapshot();
expect(
workspaceJson.projects['my-app-e2e'].architect.lint
).toMatchSnapshot();
});
});
});
describe('--unit-test-runner', () => {
describe('default (jest)', () => {
it('should generate jest.config.js with serializers', async () => {
@ -380,6 +381,7 @@ describe('app', () => {
).toEqual([
'apps/my-app/tsconfig.app.json',
'apps/my-app/tsconfig.spec.json',
'apps/my-app/tsconfig.editor.json',
]);
const tsconfigAppJson = readJsonInTree(
tree,
@ -407,7 +409,10 @@ describe('app', () => {
expect(workspaceJson.projects['my-app'].architect.test).toBeUndefined();
expect(
workspaceJson.projects['my-app'].architect.lint.options.tsConfig
).toEqual(['apps/my-app/tsconfig.app.json']);
).toEqual([
'apps/my-app/tsconfig.app.json',
'apps/my-app/tsconfig.editor.json',
]);
});
});
});

View File

@ -634,6 +634,28 @@ function updateE2eProject(options: NormalizedSchema): Rule {
};
}
function addEditorTsConfigReference(options: NormalizedSchema): Rule {
// This should be the last tsconfig reference so it's not in the template.
return chain([
updateJsonInTree(
join(normalize(options.appProjectRoot), 'tsconfig.json'),
(json) => {
json.references.push({
path: './tsconfig.editor.json',
});
return json;
}
),
updateWorkspace((workspace) => {
const tsConfigs = workspace.projects.get(options.name).targets.get('lint')
.options.tsConfig as string[];
tsConfigs.push(
join(normalize(options.appProjectRoot), 'tsconfig.editor.json')
);
}),
]);
}
function addProxyConfig(options: NormalizedSchema): Rule {
return (host: Tree, context: SchematicContext) => {
const projectConfig = getProjectConfig(host, options.name);
@ -800,6 +822,7 @@ export default function (schema: Schema): Rule {
linter: options.linter,
})
: noop(),
addEditorTsConfigReference(options),
options.backendProject ? addProxyConfig(options) : noop(),
options.strict ? enableStrictTypeChecking(options) : noop(),
formatFiles(options),

View File

@ -0,0 +1,11 @@
{
"extends": "./tsconfig.json",
"include": ["**/*.ts"],
"compilerOptions": {
"types": [
<% if (unitTestRunner === 'jest') { %>"jest",<% } %>
<% if (unitTestRunner === 'karma') { %>"jasmine",<% } %>
"node"
]
}
}