Skip to content

Commit e9560f5

Browse files
authored
feat(plugin-eslint): add exclude option for Nx projects
1 parent d8a7eb2 commit e9560f5

File tree

8 files changed

+193
-47
lines changed

8 files changed

+193
-47
lines changed

packages/plugin-eslint/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,12 @@ Detected ESLint rules are mapped to Code PushUp audits. Audit reports are calcul
7272
};
7373
```
7474

75+
You can also exclude specific projects if needed by passing their names in the `exclude` option:
76+
77+
```js
78+
await eslintConfigFromAllNxProjects({ exclude: ['server'] });
79+
```
80+
7581
- If you wish to target a specific project along with other projects it depends on, use the `eslintConfigFromNxProjectAndDeps` helper and pass in in your project name:
7682

7783
```js

packages/plugin-eslint/src/lib/nx.integration.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,25 @@ describe('Nx helpers', () => {
8888
},
8989
] satisfies ESLintTarget[]);
9090
});
91+
92+
it('should exclude specified projects and return only eslintrc and patterns of a remaining project', async () => {
93+
await expect(
94+
eslintConfigFromAllNxProjects({ exclude: ['cli', 'core', 'utils'] }),
95+
).resolves.toEqual([
96+
{
97+
eslintrc: './packages/nx-plugin/.eslintrc.json',
98+
patterns: [
99+
'packages/nx-plugin/**/*.ts',
100+
'packages/nx-plugin/package.json',
101+
'packages/nx-plugin/generators.json',
102+
'packages/nx-plugin/src/*.spec.ts',
103+
'packages/nx-plugin/src/*.cy.ts',
104+
'packages/nx-plugin/src/*.stories.ts',
105+
'packages/nx-plugin/src/.storybook/main.ts',
106+
],
107+
},
108+
] satisfies ESLintTarget[]);
109+
});
91110
});
92111

93112
describe('create config from target Nx project and its dependencies', () => {
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import type {
2+
ProjectGraph,
3+
ProjectGraphDependency,
4+
ProjectGraphProjectNode,
5+
} from '@nx/devkit';
6+
7+
export function filterProjectGraph(
8+
projectGraph: ProjectGraph,
9+
exclude: string[] = [],
10+
): ProjectGraph {
11+
const filteredNodes: Record<string, ProjectGraphProjectNode> = Object.entries(
12+
projectGraph.nodes,
13+
).reduce(
14+
(acc, [projectName, projectNode]) =>
15+
exclude.includes(projectName)
16+
? acc
17+
: { ...acc, [projectName]: projectNode },
18+
{},
19+
);
20+
const filteredDependencies: Record<string, ProjectGraphDependency[]> =
21+
Object.entries(projectGraph.dependencies).reduce(
22+
(acc, [key, deps]) =>
23+
exclude.includes(key) ? acc : { ...acc, [key]: deps },
24+
{},
25+
);
26+
return {
27+
nodes: filteredNodes,
28+
dependencies: filteredDependencies,
29+
version: projectGraph.version,
30+
};
31+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { toProjectGraph } from '@code-pushup/test-utils';
3+
import { filterProjectGraph } from './filter-project-graph';
4+
5+
describe('filterProjectGraph', () => {
6+
it('should exclude specified projects from nodes', () => {
7+
const projectGraph = toProjectGraph([
8+
{ name: 'client', type: 'app', data: { root: 'apps/client' } },
9+
{ name: 'server', type: 'app', data: { root: 'apps/server' } },
10+
{ name: 'models', type: 'lib', data: { root: 'libs/models' } },
11+
]);
12+
13+
const filteredGraph = filterProjectGraph(projectGraph, ['client']);
14+
15+
expect(Object.keys(filteredGraph.nodes)).not.toContain('client');
16+
expect(Object.keys(filteredGraph.nodes)).toContain('server');
17+
expect(Object.keys(filteredGraph.nodes)).toContain('models');
18+
});
19+
20+
it('should exclude dependencies of excluded projects', () => {
21+
const projectGraph = toProjectGraph(
22+
[
23+
{ name: 'client', type: 'app', data: { root: 'apps/client' } },
24+
{ name: 'server', type: 'app', data: { root: 'apps/server' } },
25+
{ name: 'models', type: 'lib', data: { root: 'libs/models' } },
26+
],
27+
{
28+
client: ['server'],
29+
server: ['models'],
30+
},
31+
);
32+
33+
const filteredGraph = filterProjectGraph(projectGraph, ['client']);
34+
35+
expect(Object.keys(filteredGraph.dependencies)).not.toContain('client');
36+
expect(filteredGraph.dependencies['server']).toEqual([
37+
{ source: 'server', target: 'models', type: 'static' },
38+
]);
39+
});
40+
41+
it('should include all projects if exclude list is empty', () => {
42+
const projectGraph = toProjectGraph([
43+
{ name: 'client', type: 'app', data: { root: 'apps/client' } },
44+
{ name: 'server', type: 'app', data: { root: 'apps/server' } },
45+
{ name: 'models', type: 'lib', data: { root: 'libs/models' } },
46+
]);
47+
48+
const filteredGraph = filterProjectGraph(projectGraph, []);
49+
50+
expect(Object.keys(filteredGraph.nodes)).toContain('client');
51+
expect(Object.keys(filteredGraph.nodes)).toContain('server');
52+
expect(Object.keys(filteredGraph.nodes)).toContain('models');
53+
});
54+
55+
it('should ignore non-existent projects in the exclude list', () => {
56+
const projectGraph = toProjectGraph([
57+
{ name: 'client', type: 'app', data: { root: 'apps/client' } },
58+
{ name: 'server', type: 'app', data: { root: 'apps/server' } },
59+
{ name: 'models', type: 'lib', data: { root: 'libs/models' } },
60+
]);
61+
62+
const filteredGraph = filterProjectGraph(projectGraph, ['non-existent']);
63+
64+
expect(Object.keys(filteredGraph.nodes)).toContain('client');
65+
expect(Object.keys(filteredGraph.nodes)).toContain('server');
66+
expect(Object.keys(filteredGraph.nodes)).toContain('models');
67+
});
68+
});

packages/plugin-eslint/src/lib/nx/find-all-projects.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import type { ESLintTarget } from '../config';
2+
import { filterProjectGraph } from './filter-project-graph';
23
import { nxProjectsToConfig } from './projects-to-config';
34

45
/**
56
* Finds all Nx projects in workspace and converts their lint configurations to Code PushUp ESLint plugin parameters.
67
*
8+
* Allows excluding certain projects from the configuration using the `options.exclude` parameter.
9+
*
710
* Use when you wish to automatically include every Nx project in a single Code PushUp project.
8-
* If you prefer to only include a subset of your Nx monorepo, refer to {@link eslintConfigFromNxProjectAndDeps} instead.
11+
* If you prefer to include only a subset of your Nx monorepo, specify projects to exclude using the `exclude` option
12+
* or consider using {@link eslintConfigFromNxProjectAndDeps} for finer control.
913
*
1014
* @example
1115
* import eslintPlugin, {
@@ -15,17 +19,25 @@ import { nxProjectsToConfig } from './projects-to-config';
1519
* export default {
1620
* plugins: [
1721
* await eslintPlugin(
18-
* await eslintConfigFromAllNxProjects()
22+
* await eslintConfigFromAllNxProjects({ exclude: ['server'] })
1923
* )
2024
* ]
2125
* }
2226
*
27+
* @param options - Configuration options to filter projects
28+
* @param options.exclude - Array of project names to exclude from the ESLint configuration
2329
* @returns ESLint config and patterns, intended to be passed to {@link eslintPlugin}
2430
*/
25-
export async function eslintConfigFromAllNxProjects(): Promise<ESLintTarget[]> {
31+
export async function eslintConfigFromAllNxProjects(
32+
options: { exclude?: string[] } = {},
33+
): Promise<ESLintTarget[]> {
2634
const { createProjectGraphAsync } = await import('@nx/devkit');
2735
const projectGraph = await createProjectGraphAsync({ exitOnError: false });
28-
return nxProjectsToConfig(projectGraph);
36+
const filteredProjectGraph = filterProjectGraph(
37+
projectGraph,
38+
options.exclude,
39+
);
40+
return nxProjectsToConfig(filteredProjectGraph);
2941
}
3042

3143
/**

packages/plugin-eslint/src/lib/nx/projects-to-config.unit.test.ts

Lines changed: 1 addition & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,10 @@
1-
import type {
2-
ProjectGraph,
3-
ProjectGraphDependency,
4-
ProjectGraphProjectNode,
5-
} from '@nx/devkit';
61
import { vol } from 'memfs';
72
import type { MockInstance } from 'vitest';
8-
import { MEMFS_VOLUME } from '@code-pushup/test-utils';
3+
import { MEMFS_VOLUME, toProjectGraph } from '@code-pushup/test-utils';
94
import type { ESLintPluginConfig, ESLintTarget } from '../config';
105
import { nxProjectsToConfig } from './projects-to-config';
116

127
describe('nxProjectsToConfig', () => {
13-
const toProjectGraph = (
14-
nodes: ProjectGraphProjectNode[],
15-
dependencies?: Record<string, string[]>,
16-
): ProjectGraph => ({
17-
nodes: Object.fromEntries(
18-
nodes.map(node => [
19-
node.name,
20-
{
21-
...node,
22-
data: {
23-
targets: {
24-
lint: {
25-
options: {
26-
lintFilePatterns: `${node.data.root}/**/*.ts`,
27-
},
28-
},
29-
},
30-
sourceRoot: `${node.data.root}/src`,
31-
...node.data,
32-
},
33-
},
34-
]),
35-
),
36-
dependencies: Object.fromEntries(
37-
nodes.map(node => [
38-
node.name,
39-
dependencies?.[node.name]?.map(
40-
(target): ProjectGraphDependency => ({
41-
source: node.name,
42-
target,
43-
type: 'static',
44-
}),
45-
) ?? [],
46-
]),
47-
),
48-
});
49-
508
let cwdSpy: MockInstance<[], string>;
519

5210
beforeAll(() => {

testing/test-utils/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export * from './lib/utils/string';
88
export * from './lib/utils/file-system';
99
export * from './lib/utils/create-npm-workshpace';
1010
export * from './lib/utils/omit-report-data';
11+
export * from './lib/utils/project-graph';
1112

1213
// static mocks
1314
export * from './lib/utils/commit.mock';
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import type {
2+
ProjectGraph,
3+
ProjectGraphDependency,
4+
ProjectGraphProjectNode,
5+
} from '@nx/devkit';
6+
7+
/**
8+
* Converts nodes and dependencies into a ProjectGraph object for testing purposes.
9+
*
10+
* @param nodes - Array of ProjectGraphProjectNode representing project nodes.
11+
* @param dependencies - Optional dependencies for each project in a record format.
12+
* @returns A ProjectGraph object.
13+
*/
14+
export function toProjectGraph(
15+
nodes: ProjectGraphProjectNode[],
16+
dependencies?: Record<string, string[]>,
17+
): ProjectGraph {
18+
return {
19+
nodes: Object.fromEntries(
20+
nodes.map(node => [
21+
node.name,
22+
{
23+
...node,
24+
data: {
25+
targets: {
26+
lint: {
27+
options: {
28+
lintFilePatterns: `${node.data.root}/**/*.ts`,
29+
},
30+
},
31+
},
32+
sourceRoot: `${node.data.root}/src`,
33+
...node.data,
34+
},
35+
},
36+
]),
37+
),
38+
dependencies: Object.fromEntries(
39+
nodes.map(node => [
40+
node.name,
41+
dependencies?.[node.name]?.map(
42+
(target): ProjectGraphDependency => ({
43+
source: node.name,
44+
target,
45+
type: 'static',
46+
}),
47+
) ?? [],
48+
]),
49+
),
50+
};
51+
}

0 commit comments

Comments
 (0)