Skip to content

Commit 2618b5d

Browse files
committed
feat(core): ability to load module boundaries from nx-dotnet config
Signed-off-by: AgentEnder <craigorycoppola@gmail.com>
1 parent 3fb8ba4 commit 2618b5d

File tree

10 files changed

+197
-16
lines changed

10 files changed

+197
-16
lines changed

docs/core/index.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,34 @@ Run my-api locally
4949
npx nx serve my-api
5050
```
5151

52+
## nrwl/nx/enforce-module-boundaries support
53+
54+
Nrwl publishes an eslint rule for enforcing module boundaries based on tags in a library. We recently added similar support to nx-dotnet.
55+
56+
To avoid duplicating the rules configuration, if your workspace already has it, nx-dotnet can read the dependency constraints from your workspace's eslint files. It does this by looking at what is configured for typescript files.
57+
58+
If your workspace does not currently contain eslint, do not worry! You do not have to install eslint just for its configuration. The same dependency constraints can be placed inside of your .nx-dotnet.rc.json file at workspace root. This should look something like below:
59+
60+
```json
61+
{
62+
"moduleBoundaries": [
63+
{
64+
"onlyDependOnLibsWithTags": ["a", "shared"],
65+
"sourceTag": "a"
66+
},
67+
{
68+
"onlyDependOnLibsWithTags": ["b", "shared"],
69+
"sourceTag": "b"
70+
},
71+
{
72+
"onlyDependOnLibsWithTags": ["shared"],
73+
"sourceTag": "shared"
74+
}
75+
],
76+
"nugetPackages": {}
77+
}
78+
```
79+
5280
# API Reference
5381

5482
## Generators

packages/core/README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,31 @@ Run my-api locally
3939
```shell
4040
npx nx serve my-api
4141
```
42+
43+
## nrwl/nx/enforce-module-boundaries support
44+
45+
Nrwl publishes an eslint rule for enforcing module boundaries based on tags in a library. We recently added similar support to nx-dotnet.
46+
47+
To avoid duplicating the rules configuration, if your workspace already has it, nx-dotnet can read the dependency constraints from your workspace's eslint files. It does this by looking at what is configured for typescript files.
48+
49+
If your workspace does not currently contain eslint, do not worry! You do not have to install eslint just for its configuration. The same dependency constraints can be placed inside of your .nx-dotnet.rc.json file at workspace root. This should look something like below:
50+
51+
```json
52+
{
53+
"moduleBoundaries": [
54+
{
55+
"onlyDependOnLibsWithTags": ["a", "shared"],
56+
"sourceTag": "a"
57+
},
58+
{
59+
"onlyDependOnLibsWithTags": ["b", "shared"],
60+
"sourceTag": "b"
61+
},
62+
{
63+
"onlyDependOnLibsWithTags": ["shared"],
64+
"sourceTag": "shared"
65+
}
66+
],
67+
"nugetPackages": {}
68+
}
69+
```

packages/core/src/generators/utils/generate-project.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
formatFiles,
44
getWorkspaceLayout,
55
names,
6+
normalizePath,
67
NxJsonProjectConfiguration,
78
ProjectConfiguration,
89
ProjectType,
@@ -197,9 +198,11 @@ export function addPrebuildMsbuildTask(
197198
options: { projectRoot: string; name: string },
198199
xml: XmlDocument,
199200
) {
200-
const scriptPath = relative(
201-
options.projectRoot,
202-
require.resolve('@nx-dotnet/core/src/tasks/check-module-boundaries'),
201+
const scriptPath = normalizePath(
202+
relative(
203+
options.projectRoot,
204+
require.resolve('@nx-dotnet/core/src/tasks/check-module-boundaries'),
205+
),
203206
);
204207

205208
const fragment = new XmlDocument(`
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { Tree, writeJson } from '@nrwl/devkit';
2+
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
3+
import {
4+
CONFIG_FILE_PATH,
5+
ModuleBoundaries,
6+
NxDotnetConfig,
7+
} from '@nx-dotnet/utils';
8+
9+
import {
10+
checkModuleBoundariesForProject,
11+
loadModuleBoundaries,
12+
} from './check-module-boundaries';
13+
import * as checkModule from './check-module-boundaries';
14+
import * as ESLintNamespace from 'eslint';
15+
16+
const MOCK_BOUNDARIES: ModuleBoundaries = [
17+
{
18+
onlyDependOnLibsWithTags: ['a', 'shared'],
19+
sourceTag: 'a',
20+
},
21+
{
22+
onlyDependOnLibsWithTags: ['b', 'shared'],
23+
sourceTag: 'b',
24+
},
25+
{
26+
onlyDependOnLibsWithTags: ['shared'],
27+
sourceTag: 'shared',
28+
},
29+
];
30+
31+
describe('load-module-boundaries', () => {
32+
let appTree: Tree;
33+
34+
beforeEach(() => {
35+
appTree = createTreeWithEmptyWorkspace();
36+
});
37+
38+
afterEach(() => {
39+
jest.resetAllMocks();
40+
});
41+
42+
it('should not load eslint if boundaries in config', async () => {
43+
const eslintConstructorSpy = jest.spyOn(ESLintNamespace, 'ESLint');
44+
writeJson<NxDotnetConfig>(appTree, CONFIG_FILE_PATH, {
45+
moduleBoundaries: MOCK_BOUNDARIES,
46+
nugetPackages: {},
47+
});
48+
const boundaries = await loadModuleBoundaries('', appTree);
49+
expect(eslintConstructorSpy).not.toHaveBeenCalled();
50+
expect(boundaries).toEqual(MOCK_BOUNDARIES);
51+
});
52+
53+
it('should load from eslint if boundaries not in config', async () => {
54+
const eslintConfigSpy = jest
55+
.spyOn(ESLintNamespace, 'ESLint')
56+
.mockReturnValue({
57+
calculateConfigForFile: jest.fn().mockResolvedValue({
58+
rules: {
59+
'@nrwl/nx/enforce-module-boundaries': [
60+
1,
61+
{ depConstraints: MOCK_BOUNDARIES },
62+
],
63+
},
64+
}),
65+
} as unknown as ESLintNamespace.ESLint);
66+
writeJson<NxDotnetConfig>(appTree, CONFIG_FILE_PATH, {
67+
nugetPackages: {},
68+
});
69+
const boundaries = await loadModuleBoundaries('', appTree);
70+
expect(eslintConfigSpy).toHaveBeenCalledTimes(1);
71+
expect(boundaries).toEqual(MOCK_BOUNDARIES);
72+
});
73+
});
74+
75+
describe('enforce-module-boundaries', () => {
76+
it('should exit early if no tags on project', async () => {
77+
const spy = jest.spyOn(checkModule, 'loadModuleBoundaries');
78+
const results = await checkModuleBoundariesForProject('a', {
79+
version: 2,
80+
projects: {
81+
a: {
82+
tags: [],
83+
targets: {},
84+
root: '',
85+
},
86+
},
87+
});
88+
expect(spy).not.toHaveBeenCalled();
89+
expect(results).toHaveLength(0);
90+
});
91+
});

packages/core/src/tasks/check-module-boundaries.ts

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,16 @@ import {
77

88
import { ESLint } from 'eslint';
99

10-
import { getDependantProjectsForNxProject } from '@nx-dotnet/utils';
10+
import {
11+
getDependantProjectsForNxProject,
12+
ModuleBoundaries,
13+
readConfig,
14+
} from '@nx-dotnet/utils';
1115
import {
1216
NxJsonConfiguration,
1317
NxJsonProjectConfiguration,
1418
readJsonFile,
19+
Tree,
1520
} from '@nrwl/devkit';
1621

1722
type ExtendedWorkspaceJson = WorkspaceJsonConfiguration & {
@@ -28,15 +33,7 @@ export async function checkModuleBoundariesForProject(
2833
//
2934
return [];
3035
}
31-
32-
const { rules } = await new ESLint().calculateConfigForFile(
33-
`${projectRoot}/non-existant.ts`,
34-
);
35-
const [, moduleBoundaryConfig] = rules['@nrwl/nx/enforce-module-boundaries'];
36-
const configuredConstraints: {
37-
sourceTag: '*' | string;
38-
onlyDependOnLibsWithTags: string[];
39-
}[] = moduleBoundaryConfig?.depConstraints ?? [];
36+
const configuredConstraints = await loadModuleBoundaries(projectRoot);
4037
const relevantConstraints = configuredConstraints.filter(
4138
(x) =>
4239
tags.includes(x.sourceTag) && !x.onlyDependOnLibsWithTags.includes('*'),
@@ -67,6 +64,28 @@ export async function checkModuleBoundariesForProject(
6764
return violations;
6865
}
6966

67+
/**
68+
* Loads module boundaries from eslintrc or .nx-dotnet.rc.json
69+
* @param root Which file should be used when pulling from eslint
70+
* @returns List of module boundaries
71+
*/
72+
export async function loadModuleBoundaries(
73+
root: string,
74+
host?: Tree,
75+
): Promise<ModuleBoundaries> {
76+
const configured = readConfig(host).moduleBoundaries;
77+
if (!configured) {
78+
const result = await new ESLint().calculateConfigForFile(
79+
`${root}/non-existant.ts`,
80+
);
81+
const [, moduleBoundaryConfig] =
82+
result.rules['@nrwl/nx/enforce-module-boundaries'];
83+
return moduleBoundaryConfig?.depConstraints ?? [];
84+
} else {
85+
return configured;
86+
}
87+
}
88+
7089
async function main() {
7190
const parser = await import('yargs-parser');
7291
const { project } = parser(process.argv.slice(2), {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './cmd-line-parameter';
22
export * from './nx-dotnet-config.interface';
3+
export * from './nx';
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
import { ModuleBoundaries } from './nx';
2+
13
export interface NxDotnetConfig {
24
/**
35
* Map of package -> version, used for Single Version Principle.
46
*/
57
nugetPackages: {
68
[key: string]: string | undefined;
79
};
10+
moduleBoundaries?: ModuleBoundaries;
811
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export type ModuleBoundaries = {
2+
sourceTag: '*' | string;
3+
onlyDependOnLibsWithTags: string[];
4+
}[];

packages/utils/src/lib/utility-functions/config.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
import { readJson, Tree, writeJson } from '@nrwl/devkit';
2+
import { appRootPath } from '@nrwl/tao/src/utils/app-root';
3+
import { readJsonSync } from 'fs-extra';
24

35
import { CONFIG_FILE_PATH } from '../constants';
46
import { NxDotnetConfig } from '../models';
57

6-
export function readConfig(host: Tree): NxDotnetConfig {
7-
return readJson(host, CONFIG_FILE_PATH);
8+
export function readConfig(host?: Tree): NxDotnetConfig {
9+
return host
10+
? readJson(host, CONFIG_FILE_PATH)
11+
: readJsonSync(`${appRootPath}/${CONFIG_FILE_PATH}`);
812
}
913

1014
export function updateConfig(host: Tree, value: NxDotnetConfig) {

tools/scripts/hooks/documentation.check.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export function getChangedFiles(base = 'master', directory = '.'): string[] {
2323

2424
console.log(`📖 Checking for documentation changes`);
2525
execSync('nx workspace-generator generate-docs');
26-
const changes = getChangedFiles('master', 'docs');
26+
const changes = getChangedFiles('HEAD', 'docs');
2727
if (changes.length) {
2828
console.log(`❌ Found changes in docs files`);
2929
changes.forEach((file) => {

0 commit comments

Comments
 (0)