Skip to content

Commit a813e6c

Browse files
authored
feat: DEBUG flag and project.json validation (#31)
* feat: support verbose logging * feat(nx): project.json runtime validation & verbose * use DEBUG env var instead of verbose mode
1 parent 9a218d0 commit a813e6c

File tree

7 files changed

+470
-39
lines changed

7 files changed

+470
-39
lines changed

libs/core/src/true-affected.spec.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@ import { trueAffected } from './true-affected';
22
import * as git from './git';
33
import * as lockFiles from './lock-files';
44

5+
jest.mock('chalk', () => ({
6+
default: {
7+
bold: (str: string) => str,
8+
},
9+
}));
10+
511
describe('trueAffected', () => {
612
const cwd = 'libs/core/src/__fixtures__/monorepo';
713

@@ -406,4 +412,72 @@ describe('trueAffected', () => {
406412

407413
expect(affected).toEqual(expected);
408414
});
415+
416+
it('should log the progress', async () => {
417+
const changedFiles = [
418+
{
419+
filePath: 'proj1/index.ts',
420+
changedLines: [2],
421+
},
422+
];
423+
jest.spyOn(git, 'getChangedFiles').mockReturnValue(changedFiles);
424+
425+
const debug = jest.fn();
426+
await trueAffected({
427+
cwd,
428+
base: 'main',
429+
rootTsConfig: 'tsconfig.json',
430+
projects: [
431+
{
432+
name: 'proj1',
433+
sourceRoot: 'proj1/',
434+
tsConfig: 'proj1/tsconfig.json',
435+
},
436+
{
437+
name: 'proj2',
438+
sourceRoot: 'proj2/',
439+
tsConfig: 'proj2/tsconfig.json',
440+
},
441+
{
442+
name: 'proj3',
443+
sourceRoot: 'proj3/',
444+
tsConfig: 'proj3/tsconfig.json',
445+
implicitDependencies: ['proj1'],
446+
},
447+
],
448+
logger: {
449+
debug,
450+
} as unknown as Console,
451+
});
452+
453+
expect(debug).toHaveBeenCalledWith('Getting affected projects');
454+
expect(debug).toHaveBeenCalledWith(
455+
expect.stringContaining('Creating project with root tsconfig from')
456+
);
457+
expect(debug).toHaveBeenCalledWith(
458+
expect.stringContaining('Adding source files for project proj1')
459+
);
460+
expect(debug).toHaveBeenCalledWith(
461+
expect.stringContaining('Adding source files for project proj2')
462+
);
463+
expect(debug).toHaveBeenCalledWith(
464+
expect.stringContaining(
465+
'Could not find a tsconfig for project proj3, adding source files paths'
466+
)
467+
);
468+
expect(debug).toHaveBeenCalledWith(
469+
`Found ${changedFiles.length} changed files`
470+
);
471+
expect(debug).toHaveBeenCalledWith(
472+
`Added package proj1 to affected packages for changed line ${changedFiles[0].changedLines[0]} in ${changedFiles[0].filePath}`
473+
);
474+
expect(debug).toHaveBeenCalledWith(
475+
expect.stringMatching(
476+
new RegExp(`^Found identifier .* in .*${changedFiles[0].filePath}$`)
477+
)
478+
);
479+
expect(debug).toHaveBeenCalledWith(
480+
'Added package proj2 to affected packages'
481+
);
482+
});
409483
});

libs/core/src/true-affected.ts

Lines changed: 110 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { existsSync } from 'fs';
22
import { join, resolve } from 'path';
33
import { Project, Node, ts, SyntaxKind } from 'ts-morph';
4+
import chalk from 'chalk';
45
import { ChangedFiles, getChangedFiles } from './git';
56
import { findRootNode, getPackageNameByPath } from './utils';
67
import { TrueAffected, TrueAffectedProject } from './types';
@@ -21,14 +22,29 @@ const ignoredRootNodeTypes = [
2122

2223
export const DEFAULT_INCLUDE_TEST_FILES = /\.(spec|test)\.(ts|js)x?/;
2324

25+
const DEFAULT_LOGGER = {
26+
...console,
27+
debug: process.env['DEBUG'] === 'true' ? console.debug : () => {},
28+
};
29+
2430
export const trueAffected = async ({
2531
cwd,
2632
rootTsConfig,
2733
base = 'origin/main',
2834
projects,
2935
include = [DEFAULT_INCLUDE_TEST_FILES],
36+
logger = DEFAULT_LOGGER,
3037
__experimentalLockfileCheck = false,
3138
}: TrueAffected) => {
39+
logger.debug('Getting affected projects');
40+
if (rootTsConfig != null) {
41+
logger.debug(
42+
`Creating project with root tsconfig from ${chalk.bold(
43+
resolve(cwd, rootTsConfig)
44+
)}`
45+
);
46+
}
47+
3248
const project = new Project({
3349
compilerOptions: {
3450
allowJs: true,
@@ -41,23 +57,27 @@ export const trueAffected = async ({
4157
}),
4258
});
4359

44-
const implicitDeps = (
45-
projects.filter(
46-
({ implicitDependencies = [] }) => implicitDependencies.length > 0
47-
) as Required<TrueAffectedProject>[]
48-
).reduce(
49-
(acc, { name, implicitDependencies }) =>
50-
acc.set(name, implicitDependencies),
51-
new Map<string, string[]>()
52-
);
53-
5460
projects.forEach(
55-
({ sourceRoot, tsConfig = join(sourceRoot, 'tsconfig.json') }) => {
61+
({ name, sourceRoot, tsConfig = join(sourceRoot, 'tsconfig.json') }) => {
5662
const tsConfigPath = resolve(cwd, tsConfig);
5763

5864
if (existsSync(tsConfigPath)) {
65+
logger.debug(
66+
`Adding source files for project ${chalk.bold(
67+
name
68+
)} from tsconfig at ${chalk.bold(tsConfigPath)}`
69+
);
70+
5971
project.addSourceFilesFromTsConfig(tsConfigPath);
6072
} else {
73+
logger.debug(
74+
`Could not find a tsconfig for project ${chalk.bold(
75+
name
76+
)}, adding source files paths in ${chalk.bold(
77+
resolve(cwd, sourceRoot)
78+
)}`
79+
);
80+
6181
project.addSourceFilesAtPaths(
6282
join(resolve(cwd, sourceRoot), '**/*.{ts,js}')
6383
);
@@ -70,11 +90,13 @@ export const trueAffected = async ({
7090
cwd,
7191
});
7292

93+
logger.debug(`Found ${chalk.bold(changedFiles.length)} changed files`);
94+
7395
const sourceChangedFiles = changedFiles.filter(
7496
({ filePath }) => project.getSourceFile(resolve(cwd, filePath)) != null
7597
);
7698

77-
const ignoredPaths = ['./node_modules', './dist', './.git'];
99+
const ignoredPaths = ['./node_modules', './build', './dist', './.git'];
78100

79101
const nonSourceChangedFiles = changedFiles
80102
.filter(
@@ -83,12 +105,26 @@ export const trueAffected = async ({
83105
!filePath.endsWith(lockFileName) &&
84106
project.getSourceFile(resolve(cwd, filePath)) == null
85107
)
86-
.flatMap(({ filePath: changedFilePath }) =>
87-
findNonSourceAffectedFiles(cwd, changedFilePath, ignoredPaths)
108+
.flatMap(({ filePath: changedFilePath }) => {
109+
logger.debug(
110+
`Finding non-source affected files for ${chalk.bold(changedFilePath)}`
111+
);
112+
113+
return findNonSourceAffectedFiles(cwd, changedFilePath, ignoredPaths);
114+
});
115+
116+
if (nonSourceChangedFiles.length > 0) {
117+
logger.debug(
118+
`Found ${chalk.bold(
119+
nonSourceChangedFiles.length
120+
)} non-source affected files`
88121
);
122+
}
89123

90124
let changedFilesByLockfile: ChangedFiles[] = [];
91125
if (__experimentalLockfileCheck && hasLockfileChanged(changedFiles)) {
126+
logger.debug('Lockfile has changed, finding affected files');
127+
92128
changedFilesByLockfile = findAffectedFilesByLockfile(
93129
cwd,
94130
base,
@@ -115,6 +151,14 @@ export const trueAffected = async ({
115151
.map(({ filePath }) => getPackageNameByPath(filePath, projects))
116152
.filter((v): v is string => v != null);
117153

154+
if (changedIncludedFilesPackages.length > 0) {
155+
logger.debug(
156+
`Found ${chalk.bold(
157+
changedIncludedFilesPackages.length
158+
)} affected packages from included files`
159+
);
160+
}
161+
118162
const affectedPackages = new Set<string>(changedIncludedFilesPackages);
119163
const visitedIdentifiers = new Map<string, string[]>();
120164

@@ -137,17 +181,36 @@ export const trueAffected = async ({
137181
const identifierName = identifier.getText();
138182
const path = rootNode.getSourceFile().getFilePath();
139183

184+
logger.debug(
185+
`Found identifier ${chalk.bold(identifierName)} in ${chalk.bold(path)}`
186+
);
187+
140188
if (identifierName && path) {
141-
const visited = visitedIdentifiers.get(identifierName) ?? [];
142-
if (visited.includes(path)) return;
143-
visitedIdentifiers.set(identifierName, [...visited, path]);
189+
const visited = visitedIdentifiers.get(path) ?? [];
190+
if (visited.includes(identifierName)) {
191+
logger.debug(
192+
`Already visited ${chalk.bold(identifierName)} in ${chalk.bold(path)}`
193+
);
194+
195+
return;
196+
}
197+
198+
visitedIdentifiers.set(path, [...visited, identifierName]);
199+
200+
logger.debug(
201+
`Visiting ${chalk.bold(identifierName)} in ${chalk.bold(path)}`
202+
);
144203
}
145204

146205
refs.forEach((node) => {
147206
const sourceFile = node.getSourceFile();
148207
const pkg = getPackageNameByPath(sourceFile.getFilePath(), projects);
149208

150-
if (pkg) affectedPackages.add(pkg);
209+
if (pkg) {
210+
affectedPackages.add(pkg);
211+
212+
logger.debug(`Added package ${chalk.bold(pkg)} to affected packages`);
213+
}
151214

152215
findReferencesLibs(node);
153216
});
@@ -170,7 +233,17 @@ export const trueAffected = async ({
170233

171234
const pkg = getPackageNameByPath(sourceFile.getFilePath(), projects);
172235

173-
if (pkg) affectedPackages.add(pkg);
236+
if (pkg) {
237+
affectedPackages.add(pkg);
238+
239+
logger.debug(
240+
`Added package ${chalk.bold(
241+
pkg
242+
)} to affected packages for changed line ${chalk.bold(
243+
line
244+
)} in ${chalk.bold(filePath)}`
245+
);
246+
}
174247

175248
findReferencesLibs(changedNode);
176249
} catch {
@@ -179,12 +252,30 @@ export const trueAffected = async ({
179252
});
180253
});
181254

255+
const implicitDeps = (
256+
projects.filter(
257+
({ implicitDependencies = [] }) => implicitDependencies.length > 0
258+
) as Required<TrueAffectedProject>[]
259+
).reduce(
260+
(acc, { name, implicitDependencies }) =>
261+
acc.set(name, implicitDependencies),
262+
new Map<string, string[]>()
263+
);
264+
182265
// add implicit deps
183266
affectedPackages.forEach((pkg) => {
184267
const deps = Array.from(implicitDeps.entries())
185268
.filter(([, deps]) => deps.includes(pkg))
186269
.map(([name]) => name);
187270

271+
if (deps.length > 0) {
272+
logger.debug(
273+
`Adding implicit dependencies ${chalk.bold(
274+
deps.join(', ')
275+
)} to ${chalk.bold(pkg)}`
276+
);
277+
}
278+
188279
deps.forEach((dep) => affectedPackages.add(dep));
189280
});
190281

libs/core/src/types.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ export interface TrueAffectedProject {
66
targets?: string[];
77
}
88

9-
export interface TrueAffected {
9+
export interface TrueAffectedLogging {
10+
logger?: Console;
11+
}
12+
13+
export interface TrueAffected extends TrueAffectedLogging {
1014
cwd: string;
1115
rootTsConfig?: string;
1216
base?: string;

libs/nx/src/cli.spec.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
import { resolve } from 'path';
2+
import type { TrueAffectedProject } from '@traf/core';
3+
24
import * as cli from './cli';
35
import * as nx from './nx';
46
import { workspaceCwd } from './mocks';
5-
import { TrueAffectedProject } from '@traf/core';
67

78
jest.mock('chalk', () => ({
8-
hex: jest.fn().mockReturnValue(jest.fn()),
9+
hex: () => jest.fn(),
910
bgHex: jest.fn().mockReturnValue({
1011
bold: jest.fn(),
1112
}),
13+
bgGray: {
14+
bold: jest.fn(),
15+
},
1216
chalk: jest.fn(),
1317
}));
1418

@@ -88,7 +92,7 @@ describe('cli', () => {
8892
]);
8993
expect(affectedActionSpy).toBeCalledWith({
9094
action: 'build',
91-
all: 'true',
95+
all: true,
9296
base: 'master',
9397
cwd: resolve(process.cwd(), workspaceCwd),
9498
includeFiles: ['package.json', 'jest.setup.js'],
@@ -110,7 +114,8 @@ describe('cli', () => {
110114
beforeEach(() => {
111115
getNxTrueAffectedProjectsSpy = jest
112116
.spyOn(nx, 'getNxTrueAffectedProjects')
113-
.mockImplementation();
117+
.mockImplementation()
118+
.mockResolvedValue([]);
114119

115120
logSpy = jest.spyOn(cli, 'log').mockImplementation();
116121
});
@@ -132,13 +137,16 @@ describe('cli', () => {
132137
target: [],
133138
});
134139

135-
expect(getNxTrueAffectedProjectsSpy).toBeCalledWith(process.cwd());
140+
expect(getNxTrueAffectedProjectsSpy).toBeCalledWith(process.cwd(), {
141+
logger: expect.any(Object),
142+
});
136143
expect(trafSpy).toHaveBeenCalledWith({
137144
cwd: process.cwd(),
138145
rootTsConfig: 'tsconfig.base.json',
139146
base: 'origin/main',
140147
projects: [],
141148
include: [],
149+
logger: expect.any(Object),
142150
});
143151
});
144152

0 commit comments

Comments
 (0)