feat(core): enable linter on root projects (#13347)
This commit is contained in:
parent
8200870c8e
commit
110b5f2867
@ -438,6 +438,78 @@ 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', () => {
|
||||||
|
const myapp = uniq('myapp');
|
||||||
|
const mylib = uniq('mylib');
|
||||||
|
|
||||||
|
newProject();
|
||||||
|
runCLI(`generate @nrwl/react:app ${myapp} --rootProject=true`);
|
||||||
|
|
||||||
|
let rootEslint = readJson('.eslintrc.json');
|
||||||
|
let e2eEslint = readJson('e2e/.eslintrc.json');
|
||||||
|
expect(() => checkFilesExist(`.eslintrc.${myapp}.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(rootEslint.extends).toEqual(['plugin:@nrwl/nx/react']);
|
||||||
|
expect(e2eEslint.extends).toEqual(['plugin:cypress/recommended']);
|
||||||
|
// should have plugin extends
|
||||||
|
expect(rootEslint.overrides[0].extends).toEqual([
|
||||||
|
'plugin:@nrwl/nx/typescript',
|
||||||
|
]);
|
||||||
|
expect(rootEslint.overrides[1].extends).toEqual([
|
||||||
|
'plugin:@nrwl/nx/javascript',
|
||||||
|
]);
|
||||||
|
expect(e2eEslint.overrides[0].extends).toEqual([
|
||||||
|
'plugin:@nrwl/nx/typescript',
|
||||||
|
]);
|
||||||
|
expect(e2eEslint.overrides[1].extends).toEqual([
|
||||||
|
'plugin:@nrwl/nx/javascript',
|
||||||
|
]);
|
||||||
|
|
||||||
|
console.log(JSON.stringify(rootEslint, null, 2));
|
||||||
|
console.log(JSON.stringify(e2eEslint, null, 2));
|
||||||
|
|
||||||
|
runCLI(`generate @nrwl/react:lib ${mylib}`);
|
||||||
|
// should add new tslint
|
||||||
|
expect(() => checkFilesExist(`.eslintrc.${myapp}.json`)).not.toThrow();
|
||||||
|
const appEslint = readJson(`.eslintrc.${myapp}.json`);
|
||||||
|
rootEslint = readJson('.eslintrc.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([
|
||||||
|
'plugin:@nrwl/nx/react',
|
||||||
|
'./.eslintrc.json',
|
||||||
|
]);
|
||||||
|
expect(e2eEslint.extends).toEqual([
|
||||||
|
'plugin:cypress/recommended',
|
||||||
|
'../.eslintrc.json',
|
||||||
|
]);
|
||||||
|
expect(libEslint.extends).toEqual([
|
||||||
|
'plugin:@nrwl/nx/react',
|
||||||
|
'../../.eslintrc.json',
|
||||||
|
]);
|
||||||
|
// should have no plugin extends
|
||||||
|
expect(appEslint.overrides[0].extends).toBeUndefined();
|
||||||
|
expect(appEslint.overrides[1].extends).toBeUndefined();
|
||||||
|
expect(e2eEslint.overrides[0].extends).toBeUndefined();
|
||||||
|
expect(e2eEslint.overrides[1].extends).toBeUndefined();
|
||||||
|
expect(libEslint.overrides[1].extends).toBeUndefined();
|
||||||
|
expect(libEslint.overrides[1].extends).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -19,6 +19,10 @@ import {
|
|||||||
import { Linter, lintProjectGenerator } from '@nrwl/linter';
|
import { Linter, lintProjectGenerator } from '@nrwl/linter';
|
||||||
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';
|
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';
|
||||||
import { getRelativePathToRootTsConfig } from '@nrwl/workspace/src/utilities/typescript';
|
import { getRelativePathToRootTsConfig } from '@nrwl/workspace/src/utilities/typescript';
|
||||||
|
import {
|
||||||
|
globalJavaScriptOverrides,
|
||||||
|
globalTypeScriptOverrides,
|
||||||
|
} from '@nrwl/linter/src/generators/init/global-eslint-config';
|
||||||
|
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { installedCypressVersion } from '../../utils/cypress-version';
|
import { installedCypressVersion } from '../../utils/cypress-version';
|
||||||
@ -177,6 +181,7 @@ export async function addLinter(host: Tree, options: CypressProjectSchema) {
|
|||||||
],
|
],
|
||||||
setParserOptionsProject: options.setParserOptionsProject,
|
setParserOptionsProject: options.setParserOptionsProject,
|
||||||
skipPackageJson: options.skipPackageJson,
|
skipPackageJson: options.skipPackageJson,
|
||||||
|
rootProject: options.rootProject,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!options.linter || options.linter !== Linter.EsLint) {
|
if (!options.linter || options.linter !== Linter.EsLint) {
|
||||||
@ -192,8 +197,16 @@ export async function addLinter(host: Tree, options: CypressProjectSchema) {
|
|||||||
: () => {};
|
: () => {};
|
||||||
|
|
||||||
updateJson(host, join(options.projectRoot, '.eslintrc.json'), (json) => {
|
updateJson(host, join(options.projectRoot, '.eslintrc.json'), (json) => {
|
||||||
json.extends = ['plugin:cypress/recommended', ...json.extends];
|
if (options.rootProject) {
|
||||||
|
json.plugins = ['@nrwl/nx'];
|
||||||
|
json.extends = ['plugin:cypress/recommended'];
|
||||||
|
} else {
|
||||||
|
json.extends = ['plugin:cypress/recommended', ...json.extends];
|
||||||
|
}
|
||||||
json.overrides = [
|
json.overrides = [
|
||||||
|
...(options.rootProject
|
||||||
|
? [globalTypeScriptOverrides, globalJavaScriptOverrides]
|
||||||
|
: []),
|
||||||
/**
|
/**
|
||||||
* In order to ensure maximum efficiency when typescript-eslint generates TypeScript Programs
|
* In order to ensure maximum efficiency when typescript-eslint generates TypeScript Programs
|
||||||
* behind the scenes during lint runs, we need to make sure the project is configured to use its
|
* behind the scenes during lint runs, we need to make sure the project is configured to use its
|
||||||
|
|||||||
@ -30,7 +30,8 @@
|
|||||||
"builders": "./executors.json",
|
"builders": "./executors.json",
|
||||||
"schematics": "./generators.json",
|
"schematics": "./generators.json",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"eslint": "^8.0.0"
|
"eslint": "^8.0.0",
|
||||||
|
"js-yaml": "4.1.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nrwl/devkit": "file:../devkit",
|
"@nrwl/devkit": "file:../devkit",
|
||||||
|
|||||||
80
packages/linter/src/generators/init/global-eslint-config.ts
Normal file
80
packages/linter/src/generators/init/global-eslint-config.ts
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import { ESLint, Linter as LinterType } from 'eslint';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This configuration is intended to apply to all TypeScript source files.
|
||||||
|
* See the eslint-plugin-nx package for what is in the referenced shareable config.
|
||||||
|
*/
|
||||||
|
export const globalTypeScriptOverrides = {
|
||||||
|
files: ['*.ts', '*.tsx'],
|
||||||
|
extends: ['plugin:@nrwl/nx/typescript'],
|
||||||
|
/**
|
||||||
|
* Having an empty rules object present makes it more obvious to the user where they would
|
||||||
|
* extend things from if they needed to
|
||||||
|
*/
|
||||||
|
rules: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This configuration is intended to apply to all JavaScript source files.
|
||||||
|
* See the eslint-plugin-nx package for what is in the referenced shareable config.
|
||||||
|
*/
|
||||||
|
export const globalJavaScriptOverrides = {
|
||||||
|
files: ['*.js', '*.jsx'],
|
||||||
|
extends: ['plugin:@nrwl/nx/javascript'],
|
||||||
|
/**
|
||||||
|
* Having an empty rules object present makes it more obvious to the user where they would
|
||||||
|
* extend things from if they needed to
|
||||||
|
*/
|
||||||
|
rules: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This configuration is intended to apply to all "source code" (but not
|
||||||
|
* markup like HTML, or other custom file types like GraphQL)
|
||||||
|
*/
|
||||||
|
export const moduleBoundariesOverride = {
|
||||||
|
files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
|
||||||
|
rules: {
|
||||||
|
'@nrwl/nx/enforce-module-boundaries': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
enforceBuildableLibDependency: true,
|
||||||
|
allow: [],
|
||||||
|
depConstraints: [{ sourceTag: '*', onlyDependOnLibsWithTags: ['*'] }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as LinterType.RulesRecord,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getGlobalEsLintConfiguration = (
|
||||||
|
unitTestRunner?: string,
|
||||||
|
rootProject?: boolean
|
||||||
|
) => {
|
||||||
|
const config: ESLint.ConfigData = {
|
||||||
|
root: true,
|
||||||
|
ignorePatterns: rootProject ? ['!**/*'] : ['**/*'],
|
||||||
|
plugins: ['@nrwl/nx'],
|
||||||
|
/**
|
||||||
|
* We leverage ESLint's "overrides" capability so that we can set up a root config which will support
|
||||||
|
* all permutations of Nx workspaces across all frameworks, libraries and tools.
|
||||||
|
*
|
||||||
|
* The key point is that we need entirely different ESLint config to apply to different types of files,
|
||||||
|
* but we still want to share common config where possible.
|
||||||
|
*/
|
||||||
|
overrides: [
|
||||||
|
...(rootProject ? [] : [moduleBoundariesOverride]),
|
||||||
|
globalTypeScriptOverrides,
|
||||||
|
globalJavaScriptOverrides,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
if (unitTestRunner === 'jest') {
|
||||||
|
config.overrides.push({
|
||||||
|
files: ['*.spec.ts', '*.spec.tsx', '*.spec.js', '*.spec.jsx'],
|
||||||
|
env: {
|
||||||
|
jest: true,
|
||||||
|
},
|
||||||
|
rules: {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
};
|
||||||
116
packages/linter/src/generators/init/init-migration.ts
Normal file
116
packages/linter/src/generators/init/init-migration.ts
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
import {
|
||||||
|
joinPathFragments,
|
||||||
|
offsetFromRoot,
|
||||||
|
ProjectConfiguration,
|
||||||
|
TargetConfiguration,
|
||||||
|
Tree,
|
||||||
|
updateJson,
|
||||||
|
updateProjectConfiguration,
|
||||||
|
writeJson,
|
||||||
|
} from '@nrwl/devkit';
|
||||||
|
import { basename, dirname } from 'path';
|
||||||
|
import { findEslintFile } from '../utils/eslint-file';
|
||||||
|
import { getGlobalEsLintConfiguration } from './global-eslint-config';
|
||||||
|
|
||||||
|
const FILE_EXTENSION_REGEX = /(?<!(^|\/))(\.[^/.]+)$/;
|
||||||
|
|
||||||
|
export function migrateConfigToMonorepoStyle(
|
||||||
|
projects: ProjectConfiguration[],
|
||||||
|
tree: Tree,
|
||||||
|
unitTestRunner: string
|
||||||
|
): void {
|
||||||
|
// copy the root's .eslintrc.json to new name
|
||||||
|
const rootProject = projects.find((p) => p.root === '.');
|
||||||
|
const eslintPath =
|
||||||
|
rootProject.targets?.lint?.options?.eslintConfig || findEslintFile(tree);
|
||||||
|
const pathSegments = eslintPath.split(FILE_EXTENSION_REGEX).filter(Boolean);
|
||||||
|
const rootProjEslintPath =
|
||||||
|
pathSegments.length > 1
|
||||||
|
? pathSegments.join(`.${rootProject.name}`)
|
||||||
|
: `.${rootProject.name}.${rootProject.name}`;
|
||||||
|
tree.write(rootProjEslintPath, tree.read(eslintPath));
|
||||||
|
|
||||||
|
// update root project's configuration
|
||||||
|
const lintTarget = findLintTarget(rootProject);
|
||||||
|
lintTarget.options.eslintConfig = rootProjEslintPath;
|
||||||
|
updateProjectConfiguration(tree, rootProject.name, rootProject);
|
||||||
|
|
||||||
|
// replace root eslint with default global
|
||||||
|
tree.delete(eslintPath);
|
||||||
|
writeJson(
|
||||||
|
tree,
|
||||||
|
'.eslintrc.json',
|
||||||
|
getGlobalEsLintConfiguration(unitTestRunner)
|
||||||
|
);
|
||||||
|
|
||||||
|
// update extens in all projects' eslint configs
|
||||||
|
projects.forEach((project) => {
|
||||||
|
const lintTarget = findLintTarget(project);
|
||||||
|
if (lintTarget) {
|
||||||
|
const projectEslintPath = joinPathFragments(
|
||||||
|
project.root,
|
||||||
|
lintTarget.options.eslintConfig || findEslintFile(tree, project.root)
|
||||||
|
);
|
||||||
|
migrateEslintFile(projectEslintPath, tree);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findLintTarget(
|
||||||
|
project: ProjectConfiguration
|
||||||
|
): TargetConfiguration {
|
||||||
|
return Object.entries(project.targets).find(
|
||||||
|
([name, target]) =>
|
||||||
|
name === 'lint' || target.executor === '@nrwl/linter:eslint'
|
||||||
|
)?.[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
function migrateEslintFile(projectEslintPath: string, tree: Tree) {
|
||||||
|
if (projectEslintPath.endsWith('.json')) {
|
||||||
|
updateJson(tree, projectEslintPath, (json) => {
|
||||||
|
// we have a new root now
|
||||||
|
delete json.root;
|
||||||
|
// remove nrwl/nx plugins
|
||||||
|
if (json.plugins) {
|
||||||
|
json.plugins = json.plugins.filter((p) => p !== '@nrwl/nx');
|
||||||
|
if (json.plugins.length === 0) {
|
||||||
|
delete json.plugins;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// add extends
|
||||||
|
json.extends = json.extends || [];
|
||||||
|
const pathToRootConfig = `${offsetFromRoot(
|
||||||
|
dirname(projectEslintPath)
|
||||||
|
)}.eslintrc.json`;
|
||||||
|
if (json.extends.indexOf(pathToRootConfig) === -1) {
|
||||||
|
json.extends.push(pathToRootConfig);
|
||||||
|
}
|
||||||
|
// cleanup overrides
|
||||||
|
if (json.overrides) {
|
||||||
|
json.overrides.forEach((override) => {
|
||||||
|
if (override.extends) {
|
||||||
|
override.extends = override.extends.filter(
|
||||||
|
(ext) =>
|
||||||
|
ext !== 'plugin:@nrwl/nx/typescript' &&
|
||||||
|
ext !== 'plugin:@nrwl/nx/javascript'
|
||||||
|
);
|
||||||
|
if (override.extends.length === 0) {
|
||||||
|
delete override.extends;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return json;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
projectEslintPath.endsWith('.yml') ||
|
||||||
|
projectEslintPath.endsWith('.yaml')
|
||||||
|
) {
|
||||||
|
console.warn('YAML eslint config is not supported yet for migration');
|
||||||
|
}
|
||||||
|
if (projectEslintPath.endsWith('.js') || projectEslintPath.endsWith('.cjs')) {
|
||||||
|
console.warn('YAML eslint config is not supported yet for migration');
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -16,88 +16,15 @@ import {
|
|||||||
|
|
||||||
import { Linter } from '../utils/linter';
|
import { Linter } from '../utils/linter';
|
||||||
import { findEslintFile } from '../utils/eslint-file';
|
import { findEslintFile } from '../utils/eslint-file';
|
||||||
import { ESLint } from 'eslint';
|
import { getGlobalEsLintConfiguration } from './global-eslint-config';
|
||||||
|
|
||||||
export interface LinterInitOptions {
|
export interface LinterInitOptions {
|
||||||
linter?: Linter;
|
linter?: Linter;
|
||||||
unitTestRunner?: string;
|
unitTestRunner?: string;
|
||||||
skipPackageJson?: boolean;
|
skipPackageJson?: boolean;
|
||||||
|
rootProject?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getGlobalEsLintConfiguration = (unitTestRunner?: string) => {
|
|
||||||
const config: ESLint.ConfigData = {
|
|
||||||
root: true,
|
|
||||||
ignorePatterns: ['**/*'],
|
|
||||||
plugins: ['@nrwl/nx'],
|
|
||||||
/**
|
|
||||||
* We leverage ESLint's "overrides" capability so that we can set up a root config which will support
|
|
||||||
* all permutations of Nx workspaces across all frameworks, libraries and tools.
|
|
||||||
*
|
|
||||||
* The key point is that we need entirely different ESLint config to apply to different types of files,
|
|
||||||
* but we still want to share common config where possible.
|
|
||||||
*/
|
|
||||||
overrides: [
|
|
||||||
/**
|
|
||||||
* This configuration is intended to apply to all "source code" (but not
|
|
||||||
* markup like HTML, or other custom file types like GraphQL)
|
|
||||||
*/
|
|
||||||
{
|
|
||||||
files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
|
|
||||||
rules: {
|
|
||||||
'@nrwl/nx/enforce-module-boundaries': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
enforceBuildableLibDependency: true,
|
|
||||||
allow: [],
|
|
||||||
depConstraints: [
|
|
||||||
{ sourceTag: '*', onlyDependOnLibsWithTags: ['*'] },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This configuration is intended to apply to all TypeScript source files.
|
|
||||||
* See the eslint-plugin-nx package for what is in the referenced shareable config.
|
|
||||||
*/
|
|
||||||
{
|
|
||||||
files: ['*.ts', '*.tsx'],
|
|
||||||
extends: ['plugin:@nrwl/nx/typescript'],
|
|
||||||
/**
|
|
||||||
* Having an empty rules object present makes it more obvious to the user where they would
|
|
||||||
* extend things from if they needed to
|
|
||||||
*/
|
|
||||||
rules: {},
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This configuration is intended to apply to all JavaScript source files.
|
|
||||||
* See the eslint-plugin-nx package for what is in the referenced shareable config.
|
|
||||||
*/
|
|
||||||
{
|
|
||||||
files: ['*.js', '*.jsx'],
|
|
||||||
extends: ['plugin:@nrwl/nx/javascript'],
|
|
||||||
/**
|
|
||||||
* Having an empty rules object present makes it more obvious to the user where they would
|
|
||||||
* extend things from if they needed to
|
|
||||||
*/
|
|
||||||
rules: {},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
if (unitTestRunner === 'jest') {
|
|
||||||
config.overrides.push({
|
|
||||||
files: ['*.spec.ts', '*.spec.tsx', '*.spec.js', '*.spec.jsx'],
|
|
||||||
env: {
|
|
||||||
jest: true,
|
|
||||||
},
|
|
||||||
rules: {},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
};
|
|
||||||
|
|
||||||
function addTargetDefaults(tree: Tree) {
|
function addTargetDefaults(tree: Tree) {
|
||||||
const workspaceConfiguration = readWorkspaceConfiguration(tree);
|
const workspaceConfiguration = readWorkspaceConfiguration(tree);
|
||||||
|
|
||||||
@ -133,7 +60,7 @@ function initEsLint(tree: Tree, options: LinterInitOptions): GeneratorCallback {
|
|||||||
writeJson(
|
writeJson(
|
||||||
tree,
|
tree,
|
||||||
'.eslintrc.json',
|
'.eslintrc.json',
|
||||||
getGlobalEsLintConfiguration(options.unitTestRunner)
|
getGlobalEsLintConfiguration(options.unitTestRunner, options.rootProject)
|
||||||
);
|
);
|
||||||
addTargetDefaults(tree);
|
addTargetDefaults(tree);
|
||||||
|
|
||||||
|
|||||||
@ -11,6 +11,11 @@ import { Linter } from '../utils/linter';
|
|||||||
import { findEslintFile } from '../utils/eslint-file';
|
import { findEslintFile } from '../utils/eslint-file';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { lintInitGenerator } from '../init/init';
|
import { lintInitGenerator } from '../init/init';
|
||||||
|
import {
|
||||||
|
findLintTarget,
|
||||||
|
migrateConfigToMonorepoStyle,
|
||||||
|
} from '../init/init-migration';
|
||||||
|
import { readWorkspace } from 'nx/src/generators/utils/project-configuration';
|
||||||
|
|
||||||
interface LintProjectOptions {
|
interface LintProjectOptions {
|
||||||
project: string;
|
project: string;
|
||||||
@ -21,6 +26,7 @@ interface LintProjectOptions {
|
|||||||
setParserOptionsProject?: boolean;
|
setParserOptionsProject?: boolean;
|
||||||
skipPackageJson?: boolean;
|
skipPackageJson?: boolean;
|
||||||
unitTestRunner?: string;
|
unitTestRunner?: string;
|
||||||
|
rootProject?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createEsLintConfiguration(
|
function createEsLintConfiguration(
|
||||||
@ -74,6 +80,15 @@ function createEsLintConfiguration(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function mapLintPattern(
|
||||||
|
projectRoot: string,
|
||||||
|
extension: string,
|
||||||
|
rootProject?: boolean
|
||||||
|
) {
|
||||||
|
const infix = rootProject ? 'src/' : '';
|
||||||
|
return `${projectRoot}/${infix}**/*.${extension}`;
|
||||||
|
}
|
||||||
|
|
||||||
export async function lintProjectGenerator(
|
export async function lintProjectGenerator(
|
||||||
tree: Tree,
|
tree: Tree,
|
||||||
options: LintProjectOptions
|
options: LintProjectOptions
|
||||||
@ -82,6 +97,7 @@ export async function lintProjectGenerator(
|
|||||||
linter: options.linter,
|
linter: options.linter,
|
||||||
unitTestRunner: options.unitTestRunner,
|
unitTestRunner: options.unitTestRunner,
|
||||||
skipPackageJson: options.skipPackageJson,
|
skipPackageJson: options.skipPackageJson,
|
||||||
|
rootProject: options.rootProject,
|
||||||
});
|
});
|
||||||
const projectConfig = readProjectConfiguration(tree, options.project);
|
const projectConfig = readProjectConfiguration(tree, options.project);
|
||||||
|
|
||||||
@ -92,11 +108,38 @@ export async function lintProjectGenerator(
|
|||||||
lintFilePatterns: options.eslintFilePatterns,
|
lintFilePatterns: options.eslintFilePatterns,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
createEsLintConfiguration(
|
|
||||||
tree,
|
// we are adding new project which is not the root project or
|
||||||
projectConfig,
|
// companion e2e app so we should check if migration to
|
||||||
options.setParserOptionsProject
|
// monorepo style is needed
|
||||||
);
|
if (!options.rootProject) {
|
||||||
|
const projects = readWorkspace(tree).projects;
|
||||||
|
if (isMigrationToMonorepoNeeded(projects, tree)) {
|
||||||
|
// we only migrate project configurations that have been created
|
||||||
|
const filteredProjects = [];
|
||||||
|
Object.entries(projects).forEach(([name, project]) => {
|
||||||
|
if (name !== options.project) {
|
||||||
|
filteredProjects.push(project);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
migrateConfigToMonorepoStyle(
|
||||||
|
filteredProjects,
|
||||||
|
tree,
|
||||||
|
options.unitTestRunner
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// our root `.eslintrc` is already the project config, so we should not override it
|
||||||
|
// additionally, the companion e2e app would have `rootProject: true`
|
||||||
|
// so we need to check for the root path as well
|
||||||
|
if (!options.rootProject || projectConfig.root !== '.') {
|
||||||
|
createEsLintConfiguration(
|
||||||
|
tree,
|
||||||
|
projectConfig,
|
||||||
|
options.setParserOptionsProject
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
updateProjectConfiguration(tree, options.project, projectConfig);
|
updateProjectConfiguration(tree, options.project, projectConfig);
|
||||||
|
|
||||||
@ -106,3 +149,40 @@ export async function lintProjectGenerator(
|
|||||||
|
|
||||||
return installTask;
|
return installTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect based on the state of lint target configuration of the root project
|
||||||
|
* if we should migrate eslint configs to monorepo style
|
||||||
|
*
|
||||||
|
* @param tree
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function isMigrationToMonorepoNeeded(
|
||||||
|
projects: Record<string, ProjectConfiguration>,
|
||||||
|
tree: Tree
|
||||||
|
): boolean {
|
||||||
|
const configs = Object.values(projects);
|
||||||
|
if (configs.length === 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// get root project
|
||||||
|
const rootProject = configs.find((p) => p.root === '.');
|
||||||
|
if (!rootProject || !rootProject.targets) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// find if root project has lint target
|
||||||
|
const lintTarget = findLintTarget(rootProject);
|
||||||
|
if (!lintTarget) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// if there is no override for `eslintConfig` we should migrate
|
||||||
|
if (!lintTarget.options.eslintConfig) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// check if target has `eslintConfig` override and if it's not pointing to the source .eslintrc
|
||||||
|
const rootEslintrc = findEslintFile(tree);
|
||||||
|
return (
|
||||||
|
lintTarget.options.eslintConfig === rootEslintrc ||
|
||||||
|
lintTarget.options.eslintConfig === `./${rootEslintrc}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Tree } from '@nrwl/devkit';
|
import { joinPathFragments, Tree } from '@nrwl/devkit';
|
||||||
|
|
||||||
export const eslintConfigFileWhitelist = [
|
export const eslintConfigFileWhitelist = [
|
||||||
'.eslintrc',
|
'.eslintrc',
|
||||||
@ -9,9 +9,9 @@ export const eslintConfigFileWhitelist = [
|
|||||||
'.eslintrc.json',
|
'.eslintrc.json',
|
||||||
];
|
];
|
||||||
|
|
||||||
export function findEslintFile(tree: Tree): string | null {
|
export function findEslintFile(tree: Tree, projectRoot = ''): string | null {
|
||||||
for (const file of eslintConfigFileWhitelist) {
|
for (const file of eslintConfigFileWhitelist) {
|
||||||
if (tree.exists(file)) {
|
if (tree.exists(joinPathFragments(projectRoot, file))) {
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
createReactEslintJson,
|
extendReactEslintJson,
|
||||||
extraEslintDependencies,
|
extraEslintDependencies,
|
||||||
} from '../../utils/lint';
|
} from '../../utils/lint';
|
||||||
import { NormalizedSchema, Schema } from './schema';
|
import { NormalizedSchema, Schema } from './schema';
|
||||||
@ -27,6 +27,7 @@ import { Linter, lintProjectGenerator } from '@nrwl/linter';
|
|||||||
import { swcCoreVersion } from '@nrwl/js/src/utils/versions';
|
import { swcCoreVersion } from '@nrwl/js/src/utils/versions';
|
||||||
import { swcLoaderVersion } from '@nrwl/webpack/src/utils/versions';
|
import { swcLoaderVersion } from '@nrwl/webpack/src/utils/versions';
|
||||||
import { viteConfigurationGenerator, vitestGenerator } from '@nrwl/vite';
|
import { viteConfigurationGenerator, vitestGenerator } from '@nrwl/vite';
|
||||||
|
import { mapLintPattern } from '@nrwl/linter/src/generators/lint-project/lint-project';
|
||||||
|
|
||||||
async function addLinting(host: Tree, options: NormalizedSchema) {
|
async function addLinting(host: Tree, options: NormalizedSchema) {
|
||||||
const tasks: GeneratorCallback[] = [];
|
const tasks: GeneratorCallback[] = [];
|
||||||
@ -38,20 +39,22 @@ async function addLinting(host: Tree, options: NormalizedSchema) {
|
|||||||
joinPathFragments(options.appProjectRoot, 'tsconfig.app.json'),
|
joinPathFragments(options.appProjectRoot, 'tsconfig.app.json'),
|
||||||
],
|
],
|
||||||
unitTestRunner: options.unitTestRunner,
|
unitTestRunner: options.unitTestRunner,
|
||||||
eslintFilePatterns: [`${options.appProjectRoot}/**/*.{ts,tsx,js,jsx}`],
|
eslintFilePatterns: [
|
||||||
|
mapLintPattern(
|
||||||
|
options.appProjectRoot,
|
||||||
|
'{ts,tsx,js,jsx}',
|
||||||
|
options.rootProject
|
||||||
|
),
|
||||||
|
],
|
||||||
skipFormat: true,
|
skipFormat: true,
|
||||||
|
rootProject: options.rootProject,
|
||||||
});
|
});
|
||||||
tasks.push(lintTask);
|
tasks.push(lintTask);
|
||||||
|
|
||||||
const reactEslintJson = createReactEslintJson(
|
|
||||||
options.appProjectRoot,
|
|
||||||
options.setParserOptionsProject
|
|
||||||
);
|
|
||||||
|
|
||||||
updateJson(
|
updateJson(
|
||||||
host,
|
host,
|
||||||
joinPathFragments(options.appProjectRoot, '.eslintrc.json'),
|
joinPathFragments(options.appProjectRoot, '.eslintrc.json'),
|
||||||
() => reactEslintJson
|
extendReactEslintJson
|
||||||
);
|
);
|
||||||
|
|
||||||
const installTask = await addDependenciesToPackageJson(
|
const installTask = await addDependenciesToPackageJson(
|
||||||
|
|||||||
@ -12,5 +12,6 @@ export async function addCypress(host: Tree, options: NormalizedSchema) {
|
|||||||
name: options.e2eProjectName,
|
name: options.e2eProjectName,
|
||||||
directory: options.directory,
|
directory: options.directory,
|
||||||
project: options.projectName,
|
project: options.projectName,
|
||||||
|
rootProject: options.rootProject,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,15 @@ export const extraEslintDependencies = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const extendReactEslintJson = (json: Linter.Config) => {
|
||||||
|
const { extends: pluginExtends, ...config } = json;
|
||||||
|
|
||||||
|
return {
|
||||||
|
extends: ['plugin:@nrwl/nx/react', ...(pluginExtends || [])],
|
||||||
|
...config,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const createReactEslintJson = (
|
export const createReactEslintJson = (
|
||||||
projectRoot: string,
|
projectRoot: string,
|
||||||
setParserOptionsProject: boolean
|
setParserOptionsProject: boolean
|
||||||
|
|||||||
@ -67,7 +67,7 @@ async function createPreset(tree: Tree, options: Schema) {
|
|||||||
await reactApplicationGenerator(tree, {
|
await reactApplicationGenerator(tree, {
|
||||||
name: options.name,
|
name: options.name,
|
||||||
style: options.style,
|
style: options.style,
|
||||||
linter: 'none',
|
linter: options.linter,
|
||||||
unitTestRunner: 'none',
|
unitTestRunner: 'none',
|
||||||
standaloneConfig: options.standaloneConfig,
|
standaloneConfig: options.standaloneConfig,
|
||||||
rootProject: true,
|
rootProject: true,
|
||||||
|
|||||||
@ -36,6 +36,7 @@
|
|||||||
"@nrwl/js": ["packages/js/src"],
|
"@nrwl/js": ["packages/js/src"],
|
||||||
"@nrwl/js/*": ["packages/js/*"],
|
"@nrwl/js/*": ["packages/js/*"],
|
||||||
"@nrwl/linter": ["packages/linter"],
|
"@nrwl/linter": ["packages/linter"],
|
||||||
|
"@nrwl/linter/*": ["packages/linter/*"],
|
||||||
"@nrwl/nest": ["packages/nest"],
|
"@nrwl/nest": ["packages/nest"],
|
||||||
"@nrwl/next": ["packages/next"],
|
"@nrwl/next": ["packages/next"],
|
||||||
"@nrwl/node": ["packages/node"],
|
"@nrwl/node": ["packages/node"],
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user