fix(angular): fix root-project support for Angular (#13534)
This commit is contained in:
parent
92df716b66
commit
22e70d614e
@ -442,7 +442,7 @@ export function tslibC(): string {
|
||||
describe('Root projects migration', () => {
|
||||
afterEach(() => cleanupProject());
|
||||
|
||||
it('should set root project config to app and e2e app and migrate when another lib is added', () => {
|
||||
it('(React standalone) should set root project config to app and e2e app and migrate when another lib is added', () => {
|
||||
const myapp = uniq('myapp');
|
||||
const mylib = uniq('mylib');
|
||||
|
||||
@ -506,6 +506,76 @@ export function tslibC(): string {
|
||||
expect(libEslint.overrides[1].extends).toBeUndefined();
|
||||
expect(libEslint.overrides[1].extends).toBeUndefined();
|
||||
});
|
||||
|
||||
it('(Angular standalone) should set root project config to app and e2e app and migrate when another lib is added', () => {
|
||||
const myapp = uniq('myapp');
|
||||
const mylib = uniq('mylib');
|
||||
|
||||
newProject();
|
||||
runCLI(`generate @nrwl/angular:app ${myapp} --rootProject=true`);
|
||||
|
||||
let rootEslint = readJson('.eslintrc.json');
|
||||
let e2eEslint = readJson('e2e/.eslintrc.json');
|
||||
expect(() => checkFilesExist(`.eslintrc.base.json`)).toThrow();
|
||||
|
||||
// should directly refer to nx plugin
|
||||
expect(rootEslint.plugins).toEqual(['@nrwl/nx']);
|
||||
expect(e2eEslint.plugins).toEqual(['@nrwl/nx']);
|
||||
// should only extend framework plugin
|
||||
expect(e2eEslint.extends).toEqual(['plugin:cypress/recommended']);
|
||||
// should have plugin extends
|
||||
expect(rootEslint.overrides[0].files).toEqual(['*.ts']);
|
||||
expect(rootEslint.overrides[0].extends).toEqual([
|
||||
'plugin:@nrwl/nx/typescript',
|
||||
'plugin:@nrwl/nx/angular',
|
||||
'plugin:@angular-eslint/template/process-inline-templates',
|
||||
]);
|
||||
expect(Object.keys(rootEslint.overrides[0].rules)).toEqual([
|
||||
'@angular-eslint/directive-selector',
|
||||
'@angular-eslint/component-selector',
|
||||
]);
|
||||
expect(rootEslint.overrides[1].files).toEqual(['*.html']);
|
||||
expect(rootEslint.overrides[1].extends).toEqual([
|
||||
'plugin:@nrwl/nx/angular-template',
|
||||
]);
|
||||
expect(e2eEslint.overrides[0].extends).toEqual([
|
||||
'plugin:@nrwl/nx/typescript',
|
||||
]);
|
||||
expect(e2eEslint.overrides[1].extends).toEqual([
|
||||
'plugin:@nrwl/nx/javascript',
|
||||
]);
|
||||
|
||||
runCLI(`generate @nrwl/angular:lib ${mylib}`);
|
||||
// should add new tslint
|
||||
expect(() => checkFilesExist(`.eslintrc.base.json`)).not.toThrow();
|
||||
const appEslint = readJson(`.eslintrc.json`);
|
||||
rootEslint = readJson('.eslintrc.base.json');
|
||||
e2eEslint = readJson('e2e/.eslintrc.json');
|
||||
const libEslint = readJson(`libs/${mylib}/.eslintrc.json`);
|
||||
|
||||
// should directly refer to nx plugin only in the root
|
||||
expect(rootEslint.plugins).toEqual(['@nrwl/nx']);
|
||||
expect(appEslint.plugins).toBeUndefined();
|
||||
expect(e2eEslint.plugins).toBeUndefined();
|
||||
// should extend framework plugin and root config
|
||||
expect(appEslint.extends).toEqual(['./.eslintrc.base.json']);
|
||||
expect(e2eEslint.extends).toEqual([
|
||||
'plugin:cypress/recommended',
|
||||
'../.eslintrc.base.json',
|
||||
]);
|
||||
expect(libEslint.extends).toEqual(['../../.eslintrc.base.json']);
|
||||
// should have no plugin extends
|
||||
expect(appEslint.overrides[0].extends).toEqual([
|
||||
'plugin:@nrwl/nx/angular',
|
||||
'plugin:@angular-eslint/template/process-inline-templates',
|
||||
]);
|
||||
expect(e2eEslint.overrides[0].extends).toBeUndefined();
|
||||
expect(e2eEslint.overrides[1].extends).toBeUndefined();
|
||||
expect(libEslint.overrides[0].extends).toEqual([
|
||||
'plugin:@nrwl/nx/angular',
|
||||
'plugin:@angular-eslint/template/process-inline-templates',
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -25,8 +25,8 @@ describe('addLinting generator', () => {
|
||||
} as ProjectConfiguration);
|
||||
});
|
||||
|
||||
it('should invoke the lint init generator', async () => {
|
||||
jest.spyOn(linter, 'lintInitGenerator');
|
||||
it('should invoke the lintProjectGenerator', async () => {
|
||||
jest.spyOn(linter, 'lintProjectGenerator');
|
||||
|
||||
await addLintingGenerator(tree, {
|
||||
prefix: 'myOrg',
|
||||
@ -34,7 +34,7 @@ describe('addLinting generator', () => {
|
||||
projectRoot: appProjectRoot,
|
||||
});
|
||||
|
||||
expect(linter.lintInitGenerator).toHaveBeenCalled();
|
||||
expect(linter.lintProjectGenerator).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should add the Angular specific EsLint devDependencies', async () => {
|
||||
@ -79,18 +79,7 @@ describe('addLinting generator', () => {
|
||||
`${appProjectRoot}/**/*.html`,
|
||||
],
|
||||
},
|
||||
outputs: ['{options.outputFile}'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should format files', async () => {
|
||||
jest.spyOn(devkit, 'formatFiles');
|
||||
|
||||
await addLintingGenerator(tree, {
|
||||
prefix: 'myOrg',
|
||||
projectName: appProjectName,
|
||||
projectRoot: appProjectRoot,
|
||||
});
|
||||
|
||||
expect(devkit.formatFiles).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,33 +1,51 @@
|
||||
import type { GeneratorCallback, Tree } from '@nrwl/devkit';
|
||||
import { formatFiles } from '@nrwl/devkit';
|
||||
import { Linter, lintInitGenerator } from '@nrwl/linter';
|
||||
import {
|
||||
GeneratorCallback,
|
||||
joinPathFragments,
|
||||
Tree,
|
||||
updateJson,
|
||||
} from '@nrwl/devkit';
|
||||
import { Linter, lintProjectGenerator } from '@nrwl/linter';
|
||||
import { mapLintPattern } from '@nrwl/linter/src/generators/lint-project/lint-project';
|
||||
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';
|
||||
import { addAngularEsLintDependencies } from './lib/add-angular-eslint-dependencies';
|
||||
import { addProjectLintTarget } from './lib/add-project-lint-target';
|
||||
import { createEsLintConfiguration } from './lib/create-eslint-configuration';
|
||||
import { extendAngularEslintJson } from './lib/create-eslint-configuration';
|
||||
import type { AddLintingGeneratorSchema } from './schema';
|
||||
|
||||
export async function addLintingGenerator(
|
||||
tree: Tree,
|
||||
options: AddLintingGeneratorSchema
|
||||
): Promise<GeneratorCallback> {
|
||||
const installTask = lintInitGenerator(tree, {
|
||||
const tasks: GeneratorCallback[] = [];
|
||||
const rootProject = options.projectRoot === '.' || options.projectRoot === '';
|
||||
const lintTask = await lintProjectGenerator(tree, {
|
||||
linter: Linter.EsLint,
|
||||
project: options.projectName,
|
||||
tsConfigPaths: [
|
||||
joinPathFragments(options.projectRoot, 'tsconfig.app.json'),
|
||||
],
|
||||
unitTestRunner: options.unitTestRunner,
|
||||
skipPackageJson: options.skipPackageJson,
|
||||
eslintFilePatterns: [
|
||||
mapLintPattern(options.projectRoot, 'ts', rootProject),
|
||||
mapLintPattern(options.projectRoot, 'html', rootProject),
|
||||
],
|
||||
setParserOptionsProject: options.setParserOptionsProject,
|
||||
skipFormat: true,
|
||||
rootProject: rootProject,
|
||||
});
|
||||
tasks.push(lintTask);
|
||||
|
||||
updateJson(
|
||||
tree,
|
||||
joinPathFragments(options.projectRoot, '.eslintrc.json'),
|
||||
(json) => extendAngularEslintJson(json, options)
|
||||
);
|
||||
|
||||
if (!options.skipPackageJson) {
|
||||
addAngularEsLintDependencies(tree);
|
||||
const installTask = await addAngularEsLintDependencies(tree);
|
||||
tasks.push(installTask);
|
||||
}
|
||||
|
||||
createEsLintConfiguration(tree, options);
|
||||
addProjectLintTarget(tree, options);
|
||||
|
||||
if (!options.skipFormat) {
|
||||
await formatFiles(tree);
|
||||
}
|
||||
|
||||
return installTask;
|
||||
return runTasksInSerial(...tasks);
|
||||
}
|
||||
|
||||
export default addLintingGenerator;
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import type { Tree } from '@nrwl/devkit';
|
||||
import type { GeneratorCallback, Tree } from '@nrwl/devkit';
|
||||
import { addDependenciesToPackageJson } from '@nrwl/devkit';
|
||||
import { angularEslintVersion } from '../../../utils/versions';
|
||||
|
||||
export function addAngularEsLintDependencies(tree: Tree): void {
|
||||
addDependenciesToPackageJson(
|
||||
export function addAngularEsLintDependencies(tree: Tree): GeneratorCallback {
|
||||
return addDependenciesToPackageJson(
|
||||
tree,
|
||||
{},
|
||||
{
|
||||
|
||||
@ -3,6 +3,7 @@ import {
|
||||
readProjectConfiguration,
|
||||
updateProjectConfiguration,
|
||||
} from '@nrwl/devkit';
|
||||
import { mapLintPattern } from '@nrwl/linter/src/generators/lint-project/lint-project';
|
||||
import type { AddLintingGeneratorSchema } from '../schema';
|
||||
|
||||
export function addProjectLintTarget(
|
||||
@ -10,12 +11,13 @@ export function addProjectLintTarget(
|
||||
options: AddLintingGeneratorSchema
|
||||
): void {
|
||||
const project = readProjectConfiguration(tree, options.projectName);
|
||||
const rootProject = options.projectRoot === '.' || options.projectRoot === '';
|
||||
project.targets.lint = {
|
||||
executor: '@nrwl/linter:eslint',
|
||||
options: {
|
||||
lintFilePatterns: [
|
||||
`${options.projectRoot}/**/*.ts`,
|
||||
`${options.projectRoot}/**/*.html`,
|
||||
mapLintPattern(options.projectRoot, 'ts', rootProject),
|
||||
mapLintPattern(options.projectRoot, 'html', rootProject),
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
@ -1,8 +1,65 @@
|
||||
import type { Tree } from '@nrwl/devkit';
|
||||
import { joinPathFragments, offsetFromRoot, writeJson } from '@nrwl/devkit';
|
||||
import { camelize, dasherize } from '@nrwl/workspace/src/utils/strings';
|
||||
import type { Linter } from 'eslint';
|
||||
import type { AddLintingGeneratorSchema } from '../schema';
|
||||
|
||||
type EslintExtensionSchema = {
|
||||
prefix: string;
|
||||
};
|
||||
|
||||
export const extendAngularEslintJson = (
|
||||
json: Linter.Config,
|
||||
options: EslintExtensionSchema
|
||||
) => {
|
||||
const overrides = [
|
||||
{
|
||||
...json.overrides[0],
|
||||
files: ['*.ts'],
|
||||
extends: [
|
||||
...(json.overrides[0].extends || []),
|
||||
'plugin:@nrwl/nx/angular',
|
||||
'plugin:@angular-eslint/template/process-inline-templates',
|
||||
],
|
||||
rules: {
|
||||
'@angular-eslint/directive-selector': [
|
||||
'error',
|
||||
{
|
||||
type: 'attribute',
|
||||
prefix: camelize(options.prefix),
|
||||
style: 'camelCase',
|
||||
},
|
||||
],
|
||||
'@angular-eslint/component-selector': [
|
||||
'error',
|
||||
{
|
||||
type: 'element',
|
||||
prefix: dasherize(options.prefix),
|
||||
style: 'kebab-case',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['*.html'],
|
||||
extends: ['plugin:@nrwl/nx/angular-template'],
|
||||
/**
|
||||
* Having an empty rules object present makes it more obvious to the user where they would
|
||||
* extend things from if they needed to
|
||||
*/
|
||||
rules: {},
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
...json,
|
||||
overrides,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link extendAngularEslintJson} instead
|
||||
*/
|
||||
export function createEsLintConfiguration(
|
||||
tree: Tree,
|
||||
options: AddLintingGeneratorSchema
|
||||
|
||||
@ -191,6 +191,9 @@ Object {
|
||||
"apps/my-dir/my-app/**/*.html",
|
||||
],
|
||||
},
|
||||
"outputs": Array [
|
||||
"{options.outputFile}",
|
||||
],
|
||||
},
|
||||
"serve": Object {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
@ -359,6 +362,9 @@ Object {
|
||||
"apps/my-app/**/*.html",
|
||||
],
|
||||
},
|
||||
"outputs": Array [
|
||||
"{options.outputFile}",
|
||||
],
|
||||
},
|
||||
"serve": Object {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
|
||||
@ -544,6 +544,9 @@ describe('app', () => {
|
||||
"apps/my-app/**/*.html",
|
||||
],
|
||||
},
|
||||
"outputs": Array [
|
||||
"{options.outputFile}",
|
||||
],
|
||||
}
|
||||
`);
|
||||
expect(workspaceJson.projects['my-app-e2e'].architect.lint)
|
||||
@ -578,6 +581,9 @@ describe('app', () => {
|
||||
"apps/my-app/**/*.html",
|
||||
],
|
||||
},
|
||||
"outputs": Array [
|
||||
"{options.outputFile}",
|
||||
],
|
||||
}
|
||||
`);
|
||||
expect(appTree.exists('apps/my-app-e2e/.eslintrc.json')).toBeTruthy();
|
||||
|
||||
@ -104,7 +104,7 @@ export async function applicationGenerator(
|
||||
addRouterRootConfiguration(host, options);
|
||||
}
|
||||
|
||||
addLinting(host, options);
|
||||
await addLinting(host, options);
|
||||
await addUnitTestRunner(host, options);
|
||||
await addE2e(host, options);
|
||||
updateEditorTsConfig(host, options);
|
||||
|
||||
@ -624,6 +624,9 @@ Object {
|
||||
"apps/angular-app-1/**/*.html",
|
||||
],
|
||||
},
|
||||
"outputs": Array [
|
||||
"{options.outputFile}",
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -993,6 +996,9 @@ Object {
|
||||
"libs/angular-lib-1/**/*.html",
|
||||
],
|
||||
},
|
||||
"outputs": Array [
|
||||
"{options.outputFile}",
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -351,3 +351,24 @@ import { myLibRoutes } from '@proj/my-lib';
|
||||
export const appRoutes: Route[] = [
|
||||
{ path: 'my-lib', children: myLibRoutes },]"
|
||||
`;
|
||||
|
||||
exports[`lib --standalone should generate a library with a standalone component as entry point with routing setup and attach it to standalone parent routes as a lazy child 1`] = `
|
||||
"import { Route } from '@angular/router';
|
||||
import { MyLibComponent } from './my-lib/my-lib.component';
|
||||
|
||||
export const myLibRoutes: Route[] = [
|
||||
{path: 'second', loadChildren: () => import('@proj/second').then(m => m.secondRoutes)},
|
||||
{path: '', component: MyLibComponent}
|
||||
]"
|
||||
`;
|
||||
|
||||
exports[`lib --standalone should generate a library with a standalone component as entry point with routing setup and attach it to standalone parent routes as direct child 1`] = `
|
||||
"import { Route } from '@angular/router';
|
||||
import { MyLibComponent } from './my-lib/my-lib.component';
|
||||
import { secondRoutes } from '@proj/second';
|
||||
|
||||
export const myLibRoutes: Route[] = [
|
||||
{ path: 'second', children: secondRoutes },
|
||||
{path: '', component: MyLibComponent}
|
||||
]"
|
||||
`;
|
||||
|
||||
@ -1307,6 +1307,9 @@ describe('lib', () => {
|
||||
"libs/my-lib/**/*.html",
|
||||
],
|
||||
},
|
||||
"outputs": Array [
|
||||
"{options.outputFile}",
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
@ -1661,17 +1664,9 @@ describe('lib', () => {
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
expect(tree.read('libs/my-lib/src/lib/lib.routes.ts', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { Route } from '@angular/router';
|
||||
import { MyLibComponent } from './my-lib/my-lib.component';
|
||||
import { secondRoutes } from '@proj/second';
|
||||
|
||||
export const myLibRoutes: Route[] = [
|
||||
{ path: 'second', children: secondRoutes },
|
||||
{path: '', component: MyLibComponent}
|
||||
]"
|
||||
`);
|
||||
expect(
|
||||
tree.read('libs/my-lib/src/lib/lib.routes.ts', 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should generate a library with a standalone component as entry point with routing setup and attach it to standalone parent routes as a lazy child', async () => {
|
||||
@ -1691,16 +1686,9 @@ describe('lib', () => {
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
expect(tree.read('libs/my-lib/src/lib/lib.routes.ts', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { Route } from '@angular/router';
|
||||
import { MyLibComponent } from './my-lib/my-lib.component';
|
||||
|
||||
export const myLibRoutes: Route[] = [
|
||||
{path: 'second', loadChildren: () => import('@proj/second').then(m => m.secondRoutes)},
|
||||
{path: '', component: MyLibComponent}
|
||||
]"
|
||||
`);
|
||||
expect(
|
||||
tree.read('libs/my-lib/src/lib/lib.routes.ts', 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should generate a library with a standalone component as entry point following SFC pattern', async () => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user