Find truly affected packages in monorepos based on line-level code changes
npx @tessl/cli install tessl/npm-traf--core@0.0.00
# @traf/core
1
2
@traf/core is a TypeScript library that intelligently identifies truly affected projects in monorepos by analyzing code changes at the line and semantic level, rather than using traditional file-level dependency analysis. It uses the TypeScript compiler API to find changed code elements (functions, classes, constants) and recursively tracks their references across the monorepo to determine which projects are actually impacted by changes.
3
4
## Package Information
5
6
- **Package Name**: @traf/core
7
- **Package Type**: npm
8
- **Language**: TypeScript
9
- **Installation**: `npm install @traf/core`
10
11
## Core Imports
12
13
```typescript
14
import { trueAffected, DEFAULT_INCLUDE_TEST_FILES } from '@traf/core';
15
import type { TrueAffected, TrueAffectedProject, TrueAffectedLogging } from '@traf/core';
16
import type { CompilerOptions } from 'ts-morph';
17
```
18
19
This package is ESM-only (`"type": "module"` in package.json). Note that `CompilerOptions` is not exported from `@traf/core` and must be imported from `ts-morph` if you need to type the `compilerOptions` parameter.
20
21
## Basic Usage
22
23
```typescript
24
import { trueAffected } from '@traf/core';
25
26
const affectedProjects = await trueAffected({
27
cwd: process.cwd(),
28
rootTsConfig: 'tsconfig.base.json',
29
base: 'origin/main',
30
projects: [
31
{
32
name: 'my-app',
33
sourceRoot: 'apps/my-app/src',
34
tsConfig: 'apps/my-app/tsconfig.json',
35
},
36
{
37
name: 'shared-lib',
38
sourceRoot: 'libs/shared/src',
39
tsConfig: 'libs/shared/tsconfig.json',
40
},
41
],
42
});
43
44
console.log('Affected projects:', affectedProjects);
45
// Output: ['my-app', 'shared-lib']
46
```
47
48
## Architecture
49
50
@traf/core operates through the following key processes:
51
52
- **Git Diff Analysis**: Uses git to identify all changed lines in the current branch compared to a base branch
53
- **Semantic Code Analysis**: Uses ts-morph (TypeScript Compiler API wrapper) to parse changed lines and identify the specific code elements that changed (functions, classes, constants, etc.)
54
- **Reference Tracking**: Recursively finds all references to changed elements across the entire monorepo using TypeScript's language service
55
- **Project Mapping**: Maps source files to their containing projects and builds the list of affected projects
56
- **Non-Source File Handling**: Tracks changes to non-source files (assets, configs) by finding imports/requires of those files
57
- **Implicit Dependencies**: Includes projects that declare implicit dependencies on affected projects
58
- **Lockfile Analysis**: Experimental feature to detect dependency changes in package-lock files and find affected files
59
60
## Capabilities
61
62
### True Affected Analysis
63
64
Analyzes a monorepo to find projects that are truly affected by code changes in the current branch. Uses semantic-level change detection rather than file-level to reduce false positives.
65
66
```typescript { .api }
67
/**
68
* Finds projects that are truly affected by code changes based on line-level semantic analysis
69
* @param options - Configuration object
70
* @returns Promise resolving to array of affected project names
71
*/
72
function trueAffected(options: TrueAffected): Promise<string[]>;
73
74
interface TrueAffected extends TrueAffectedLogging {
75
/** Current working directory */
76
cwd: string;
77
78
/** Path to root tsconfig file with path mappings for all projects (optional but recommended) */
79
rootTsConfig?: string;
80
81
/** Base branch to compare against (default: 'origin/main') */
82
base?: string;
83
84
/** Array of projects to analyze */
85
projects: TrueAffectedProject[];
86
87
/** Patterns to include regardless of semantic analysis (e.g., test files). Default: test/spec files */
88
include?: (string | RegExp)[];
89
90
/** TypeScript compiler options from ts-morph */
91
compilerOptions?: CompilerOptions;
92
93
/** Paths to ignore during analysis (default: node_modules, dist, build, .git) */
94
ignoredPaths?: (string | RegExp)[];
95
96
/** Experimental feature to detect lockfile changes and find affected files */
97
__experimentalLockfileCheck?: boolean;
98
}
99
100
interface TrueAffectedProject {
101
/** Project name */
102
name: string;
103
104
/** Project source root directory path */
105
sourceRoot: string;
106
107
/** Path to project's tsconfig file (default: {sourceRoot}/tsconfig.json) */
108
tsConfig?: string;
109
110
/** Array of project names that implicitly depend on this project */
111
implicitDependencies?: string[];
112
113
/** Build targets for the project */
114
targets?: string[];
115
}
116
117
interface TrueAffectedLogging {
118
/** Console-compatible logger instance (default: console with debug controlled by DEBUG env var) */
119
logger?: Console;
120
}
121
```
122
123
**Usage Example:**
124
125
```typescript
126
import { trueAffected } from '@traf/core';
127
128
// Analyze monorepo with custom configuration
129
const affected = await trueAffected({
130
cwd: '/path/to/monorepo',
131
rootTsConfig: 'tsconfig.base.json',
132
base: 'origin/develop',
133
projects: [
134
{
135
name: 'api-service',
136
sourceRoot: 'apps/api/src',
137
tsConfig: 'apps/api/tsconfig.json',
138
implicitDependencies: ['database-migrations'],
139
},
140
{
141
name: 'web-app',
142
sourceRoot: 'apps/web/src',
143
tsConfig: 'apps/web/tsconfig.json',
144
},
145
{
146
name: 'shared-utils',
147
sourceRoot: 'libs/utils/src',
148
tsConfig: 'libs/utils/tsconfig.json',
149
},
150
{
151
name: 'database-migrations',
152
sourceRoot: 'libs/migrations',
153
},
154
],
155
include: [
156
/\.(spec|test)\.(ts|js)x?$/, // Include test files
157
/\.config\.(ts|js)$/, // Include config files
158
],
159
ignoredPaths: [
160
'./node_modules',
161
'./dist',
162
'./coverage',
163
],
164
logger: console,
165
});
166
167
console.log(`Found ${affected.length} affected projects:`, affected);
168
```
169
170
**Notes:**
171
172
- The `rootTsConfig` should include a `paths` property mapping all projects so ts-morph can resolve references across the monorepo
173
- Each project's `tsConfig` should only include files for that specific project
174
- If a project's `tsConfig` doesn't exist, the library will add all `.ts` and `.js` files from the `sourceRoot`
175
- The algorithm tracks changes recursively, so if function A is changed and function B references A, both projects containing A and B are marked as affected
176
- Setting `DEBUG=true` environment variable enables detailed debug logging
177
178
### Default Test File Pattern
179
180
Regular expression constant for the default test file inclusion pattern.
181
182
```typescript { .api }
183
const DEFAULT_INCLUDE_TEST_FILES: RegExp;
184
```
185
186
This constant equals `/\.(spec|test)\.(ts|js)x?/` and matches files like:
187
- `file.spec.ts`
188
- `file.test.js`
189
- `component.spec.tsx`
190
- `utils.test.jsx`
191
192
**Usage Example:**
193
194
```typescript
195
import { trueAffected, DEFAULT_INCLUDE_TEST_FILES } from '@traf/core';
196
197
const affected = await trueAffected({
198
cwd: process.cwd(),
199
rootTsConfig: 'tsconfig.base.json',
200
projects: [...],
201
include: [
202
DEFAULT_INCLUDE_TEST_FILES,
203
/\.e2e\.(ts|js)$/, // Also include e2e test files
204
],
205
});
206
```
207
208
## Types
209
210
### CompilerOptions
211
212
Type re-exported from ts-morph for TypeScript compiler configuration.
213
214
```typescript { .api }
215
import { CompilerOptions } from 'ts-morph';
216
```
217
218
This type is used in the `TrueAffected.compilerOptions` property to configure the TypeScript compiler behavior during analysis. Common options include:
219
220
```typescript
221
const affected = await trueAffected({
222
cwd: process.cwd(),
223
projects: [...],
224
compilerOptions: {
225
allowJs: true, // Allow JavaScript files (default: true)
226
strict: false, // Disable strict type checking for analysis
227
skipLibCheck: true, // Skip type checking of declaration files
228
},
229
});
230
```
231
232
## How It Works
233
234
The algorithm performs the following steps:
235
236
1. **Git Diff Extraction**: Runs `git diff` against the base branch to identify all changed files and the specific line numbers that changed in each file
237
238
2. **TypeScript Project Setup**: Creates a ts-morph Project instance and adds all source files from the specified projects using their tsconfig files
239
240
3. **Changed Line Analysis**: For each changed line in source files:
241
- Finds the AST node at that line number
242
- Identifies the root code element (function, class, const, etc.) containing that line
243
- Marks the containing project as affected
244
245
4. **Reference Tracking**: For each changed element:
246
- Uses ts-morph's `findReferencesAsNodes()` to find all references to that element
247
- Recursively processes each reference to find its references
248
- Marks all projects containing references as affected
249
- Maintains a visited set to avoid infinite loops
250
251
5. **Non-Source File Handling**: For changed non-source files (images, JSON, etc.):
252
- Searches the codebase for imports/requires of those files
253
- Treats those import statements as changed lines and applies the reference tracking algorithm
254
255
6. **Test File Inclusion**: Any changed files matching the `include` patterns cause their containing project to be marked as affected
256
257
7. **Implicit Dependencies**: Projects that declare implicit dependencies on affected projects are added to the affected set
258
259
8. **Lockfile Analysis** (experimental): If enabled and package-lock.json changed:
260
- Compares lockfile before and after to find changed dependencies
261
- Searches for imports of changed dependencies
262
- Marks projects importing changed dependencies as affected
263
264
## Error Handling
265
266
The library may throw errors in the following scenarios:
267
268
- **Git errors**: If the base branch doesn't exist or git commands fail, an error is thrown with message: `"Unable to get diff for base: \"{base}\". are you using the correct base?"`
269
- **File resolution errors**: If a file cannot be retrieved from a git revision, an error is thrown with message: `"Unable to get file \"{filePath}\" for base: \"{base}\". are you using the correct base?"`
270
271
## Integration with Monorepo Tools
272
273
@traf/core is designed as a reusable core library for integration with various monorepo tools. Companion packages provide tool-specific integrations:
274
275
- `@traf/nx` - Integration with Nx monorepo tool
276
- `@traf/turbo` - Integration with Turbo monorepo tool (future)
277
278
These packages handle tool-specific project configuration and provide convenient wrappers around the core functionality.
279