Skip to content

Commit 8b0c04f

Browse files
michaelbe812rainerhahnekamp
authored andcommitted
feat(core): support multiple entry points
feat: update get entries chore: add test project for multi-project setup docs(core): update CLI docs with information about entryPoints refactor(core): remove unused import refactor(core): move entry type feat(core): validate correct entry settings chore(core): add tests for list in case of entryPoints refactor(core): fix case for parse-config chore(core): fix spacing fix(core): re-add entryPoints to Configuration chore(core): surpass eslint issue refactor(test-projects): remove empty style sheets refactor(core): remove type assertions refactor(core): correct type-narrowing for isEmptyRecord
1 parent 53a111c commit 8b0c04f

68 files changed

Lines changed: 16920 additions & 133 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/docs/cli.md

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,29 @@ The core package (@softarc/sheriff-core) comes with a CLI to initialize the conf
1010

1111
Run `npx sheriff init` to create a `sheriff.config.ts`. Its configuration runs with [automatic tagging](./dependency-rules#automatic-tagging), meaning no dependency rules are in place, and it only checks for the module boundaries.
1212

13-
## `verify [main.ts]`
13+
## `verify`
14+
Run `npx sheriff verify` either against an `entryFile` or against one or multiple `entryPoints` to check if your project violates any of your rules.
1415

15-
Run `npx sheriff verify main.ts` to check if your project violates any of your rules. `main.ts` is the entry file where Sheriff should traverse the imports.
16+
To run sheriff against an `entryFile` run `npx sheriff verify main.ts`. `main.ts` is the entry file where Sheriff should traverse the imports.
1617

1718
Depending on your project, you will likely have a different entry file. For example, with an Angular CLI-based project, it would be `npx sheriff verify src/main.ts`.
1819

1920
You can omit the entry file if you set a value to the property `entryFile` in the `sheriff.config.ts`.
2021

2122
In that case, you only run `npx sheriff verify`.
2223

23-
## `list [main.ts]`
24+
To run sheriff against `entryPoints` run `npx sheriff verify app-i,app-ii`. `app-i` and `app-ii` are the entry points where Sheriff should traverse the imports.
25+
26+
The `entryPoints` must be defined in the `sheriff.config.ts` file.
27+
28+
## `list`
2429

2530
Run `npx sheriff list main.ts` to print out all your modules along their tags. As explained above, you can alternatively use the `entryFile` property in `sheriff.config.ts`.
2631

27-
## `export [main.ts]`
32+
Alternatively, you can run `npx sheriff list app-i,app-ii` to list the modules for the specified `entryPoints`.
33+
34+
## `export`
2835

2936
Run `npx sheriff export main.ts > export.json` and the dependency graph will be stored in `export.json` in JSON format. The dependency graph starts from the entry file and includes all reachable files. For every file, it will include the assigned module as well as the tags.
37+
38+
Alternatively, you can run `npx sheriff export app-i,app-ii > export.json`.
Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1-
import { getEntryFromCliOrConfig } from './internal/get-entry-from-cli-or-config';
1+
import { getEntriesFromCliOrConfig } from './internal/get-entries-from-cli-or-config';
22
import { cli } from './cli';
33
import { getProjectData } from '../api/get-project-data';
44
import getFs from '../fs/getFs';
55

66
export function exportData(...args: string[]): void {
77
const fs = getFs();
8-
const entryFile = getEntryFromCliOrConfig(args[0], false);
8+
const projectEntries = getEntriesFromCliOrConfig(args[0], false);
99

10-
const data = getProjectData(entryFile, fs.cwd(), { includeExternalLibraries: true });
11-
cli.log(JSON.stringify(data, null, ' '));
10+
for (const entry of projectEntries) {
11+
const data = getProjectData(entry.entry, fs.cwd(), {
12+
includeExternalLibraries: true,
13+
});
14+
cli.log(JSON.stringify(data, null, ' '));
15+
}
1216
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export type Entry<TEntry> = {
2+
projectName: string;
3+
entry: TEntry;
4+
};
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import getFs from '../../fs/getFs';
2+
import { init, ProjectInfo } from '../../main/init';
3+
import { parseConfig } from '../../config/parse-config';
4+
import { toFsPath } from '../../file-info/fs-path';
5+
import { isEmptyRecord } from '../../util/is-empty-record';
6+
import { parseEntryPointsFromCli } from './parse-entry-points-from-cli';
7+
import { Entry } from './entry';
8+
9+
export const DEFAULT_PROJECT_NAME = 'default';
10+
11+
export function getEntriesFromCliOrConfig(
12+
entryFileOrEntryPoints?: string,
13+
): Array<Entry<ProjectInfo>>;
14+
export function getEntriesFromCliOrConfig(
15+
entryFileOrEntryPoints?: string,
16+
runInit?: true,
17+
): Array<Entry<ProjectInfo>>;
18+
export function getEntriesFromCliOrConfig(
19+
entryFileOrEntryPoints?: string,
20+
runInit?: false,
21+
): Array<Entry<string>>;
22+
export function getEntriesFromCliOrConfig(
23+
/**
24+
* the CLI forwards either the entry file e.g. "src/main.ts" or
25+
* the entry point(s) e.g. app-i,app-ii
26+
*/
27+
entryFileOrEntryPoints = '',
28+
runInit = true,
29+
): Array<Entry<string>> | Array<Entry<ProjectInfo>> {
30+
const fs = getFs();
31+
const potentialConfigFile = fs.join(fs.cwd(), 'sheriff.config.ts');
32+
33+
/**
34+
* CLI argument given
35+
*/
36+
if (entryFileOrEntryPoints) {
37+
// CLI argument given and no config file is present -> only entry file can work
38+
if (!fs.exists(potentialConfigFile)) {
39+
return processEntryFile(entryFileOrEntryPoints, runInit, fs);
40+
}
41+
42+
if (fs.exists(potentialConfigFile)) {
43+
// two cases to check: check for entry points otherwise it is an entry file
44+
const sheriffConfig = parseConfig(potentialConfigFile);
45+
46+
const potentialEntryPoints = parseEntryPointsFromCli(
47+
entryFileOrEntryPoints,
48+
sheriffConfig,
49+
);
50+
51+
if (potentialEntryPoints) {
52+
// if entry points are given, return them
53+
return processEntryFile(potentialEntryPoints, runInit, fs);
54+
} else {
55+
// otherwise it is an entry file
56+
return processEntryFile(entryFileOrEntryPoints, runInit, fs);
57+
}
58+
}
59+
}
60+
61+
if (fs.exists(potentialConfigFile)) {
62+
const sheriffConfig = parseConfig(potentialConfigFile);
63+
64+
if (sheriffConfig.entryFile) {
65+
return processEntryFile(sheriffConfig.entryFile, runInit, fs);
66+
} else if (
67+
sheriffConfig.entryPoints &&
68+
!isEmptyRecord(sheriffConfig.entryPoints)
69+
) {
70+
return processEntryFile(sheriffConfig.entryPoints, runInit, fs);
71+
} else {
72+
throw new Error(
73+
'No entry file or entry points found in sheriff.config.ts. Please provide the option via the CLI.',
74+
);
75+
}
76+
}
77+
78+
throw new Error(
79+
'Please provide an entry file (e.g. main.ts) or entry points (e.g. { projectName: "main.ts" })',
80+
);
81+
}
82+
83+
// Helper function to process entry file consistently
84+
function processEntryFile(
85+
entryFileValue: string | Record<string, string>,
86+
runInit: boolean,
87+
fs: ReturnType<typeof getFs>,
88+
): Array<Entry<ProjectInfo>> | Array<Entry<string>> {
89+
if (typeof entryFileValue === 'string') {
90+
return runInit
91+
? [
92+
{
93+
projectName: DEFAULT_PROJECT_NAME,
94+
entry: init(toFsPath(fs.join(fs.cwd(), entryFileValue))),
95+
},
96+
]
97+
: [{ projectName: DEFAULT_PROJECT_NAME, entry: entryFileValue }];
98+
} else {
99+
const entries = Object.entries(entryFileValue);
100+
101+
return runInit
102+
? (entries.map(([projectName, entry]) => ({
103+
projectName,
104+
entry: init(toFsPath(fs.join(fs.cwd(), entry))),
105+
})) as Array<Entry<ProjectInfo>>)
106+
: (entries.map(([projectName, entry]) => ({
107+
projectName,
108+
entry: entry,
109+
})) as Array<Entry<string>>);
110+
}
111+
}

packages/core/src/lib/cli/internal/get-entry-from-cli-or-config.ts

Lines changed: 0 additions & 28 deletions
This file was deleted.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { Configuration } from '../../config/configuration';
2+
import { isEmptyRecord } from '../../util/is-empty-record';
3+
4+
export function parseEntryPointsFromCli(
5+
entryFileOrEntryPoints: string,
6+
sheriffConfig: Configuration,
7+
): Record<string, string> | undefined {
8+
const entryPointsFromConfig = sheriffConfig.entryPoints;
9+
if (entryFileOrEntryPoints.includes(',')) {
10+
if (!entryPointsFromConfig || isEmptyRecord(entryPointsFromConfig)) {
11+
return undefined;
12+
}
13+
const splittedEntries = entryFileOrEntryPoints.split(',');
14+
const entryPoints: Record<string, string> = {};
15+
16+
for (const entry of splittedEntries) {
17+
const entryPoint = entryPointsFromConfig[entry];
18+
if (entryPoint) {
19+
entryPoints[entry] = entryPoint;
20+
}
21+
}
22+
return entryPoints;
23+
}
24+
25+
// If no comma is found, it could be a single entry point
26+
if (
27+
entryPointsFromConfig &&
28+
entryFileOrEntryPoints in entryPointsFromConfig
29+
) {
30+
const singleEntryPoint = entryPointsFromConfig[entryFileOrEntryPoints];
31+
return { [entryFileOrEntryPoints]: singleEntryPoint };
32+
}
33+
34+
return undefined;
35+
}

packages/core/src/lib/cli/list.ts

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,42 @@
11
import { FsPath, toFsPath } from '../file-info/fs-path';
22
import { ProjectInfo } from '../main/init';
33
import { calcTagsForModule } from '../tags/calc-tags-for-module';
4-
import { getEntryFromCliOrConfig } from './internal/get-entry-from-cli-or-config';
4+
import {
5+
DEFAULT_PROJECT_NAME,
6+
getEntriesFromCliOrConfig,
7+
} from './internal/get-entries-from-cli-or-config';
58
import getFs from '../fs/getFs';
69
import { cli } from './cli';
710
import { logInfoForMissingSheriffConfig } from './internal/log-info-for-missing-sheriff-config';
811

912
export function list(args: string[]) {
10-
const projectInfo = getEntryFromCliOrConfig(args[0]);
11-
logInfoForMissingSheriffConfig(projectInfo);
12-
13-
// root doesn't count
14-
const modulesCount = projectInfo.modules.length - 1;
13+
const projectEntries = getEntriesFromCliOrConfig(args[0]);
14+
if (projectEntries.length > 0) {
15+
logInfoForMissingSheriffConfig(projectEntries[0].entry);
16+
}
1517

16-
cli.log(`This project contains ${modulesCount} modules:`);
17-
cli.log('');
18+
for (const projectEntry of projectEntries) {
19+
// root doesn't count
20+
const modulesCount = projectEntry.entry.modules.length - 1;
21+
const projectName = projectEntry.projectName;
22+
if (projectName !== DEFAULT_PROJECT_NAME) {
23+
cli.log(cli.bold(`Project: ${projectName}`));
24+
cli.log('');
25+
}
26+
cli.log(`This project contains ${modulesCount} modules:`);
27+
cli.log('');
1828

19-
cli.log('. (root)');
20-
const directory = mapModulesToDirectory(
21-
Array.from(
22-
projectInfo.modules
23-
.filter((module) => !module.isRoot)
24-
.map((module) => toFsPath(module.path)),
25-
),
26-
projectInfo,
27-
);
28-
printDirectory(directory);
29+
cli.log('. (root)');
30+
const directory = mapModulesToDirectory(
31+
Array.from(
32+
projectEntry.entry.modules
33+
.filter((module) => !module.isRoot)
34+
.map((module) => toFsPath(module.path)),
35+
),
36+
projectEntry.entry,
37+
);
38+
printDirectory(directory);
39+
}
2940
}
3041

3142
type Directory = Record<

packages/core/src/lib/cli/tests/__snapshots__/list.spec.ts.snap

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,43 @@ exports[`list > should include modules not reachable by entryFile > log 1`] = `
1313
└── <b>model</b> (domain:customers, type:model)"
1414
`;
1515

16+
exports[`list > should list all files reachable by entryPoints > log 1`] = `
17+
"<b>Project: holidays</b>
18+
19+
This project contains 5 modules:
20+
21+
. (root)
22+
└── projects
23+
├── holidays
24+
└── src
25+
└── holidays
26+
├── <b>feature</b> (noTag)
27+
└── <b>data</b> (noTag)
28+
└── customer-support
29+
└── src
30+
└── self-service
31+
├── <b>feature</b> (noTag)
32+
├── <b>data</b> (noTag)
33+
└── <b>model</b> (noTag)
34+
<b>Project: customer-support</b>
35+
36+
This project contains 5 modules:
37+
38+
. (root)
39+
└── projects
40+
├── holidays
41+
└── src
42+
└── holidays
43+
├── <b>feature</b> (noTag)
44+
└── <b>data</b> (noTag)
45+
└── customer-support
46+
└── src
47+
└── self-service
48+
├── <b>feature</b> (noTag)
49+
├── <b>data</b> (noTag)
50+
└── <b>model</b> (noTag)"
51+
`;
52+
1653
exports[`list > should list modules without sheriff config > log 1`] = `
1754
"Default settings applied. For more control, run "npx sheriff init" to create a sheriff.config.ts.
1855
This project contains 2 modules:

0 commit comments

Comments
 (0)