Find truly affected packages in monorepos based on line-level code changes
npx @tessl/cli install tessl/npm-traf--core@0.0.0@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.
npm install @traf/coreimport { trueAffected, DEFAULT_INCLUDE_TEST_FILES } from '@traf/core';
import type { TrueAffected, TrueAffectedProject, TrueAffectedLogging } from '@traf/core';
import type { CompilerOptions } from 'ts-morph';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.
import { trueAffected } from '@traf/core';
const affectedProjects = await trueAffected({
cwd: process.cwd(),
rootTsConfig: 'tsconfig.base.json',
base: 'origin/main',
projects: [
{
name: 'my-app',
sourceRoot: 'apps/my-app/src',
tsConfig: 'apps/my-app/tsconfig.json',
},
{
name: 'shared-lib',
sourceRoot: 'libs/shared/src',
tsConfig: 'libs/shared/tsconfig.json',
},
],
});
console.log('Affected projects:', affectedProjects);
// Output: ['my-app', 'shared-lib']@traf/core operates through the following key processes:
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.
/**
* Finds projects that are truly affected by code changes based on line-level semantic analysis
* @param options - Configuration object
* @returns Promise resolving to array of affected project names
*/
function trueAffected(options: TrueAffected): Promise<string[]>;
interface TrueAffected extends TrueAffectedLogging {
/** Current working directory */
cwd: string;
/** Path to root tsconfig file with path mappings for all projects (optional but recommended) */
rootTsConfig?: string;
/** Base branch to compare against (default: 'origin/main') */
base?: string;
/** Array of projects to analyze */
projects: TrueAffectedProject[];
/** Patterns to include regardless of semantic analysis (e.g., test files). Default: test/spec files */
include?: (string | RegExp)[];
/** TypeScript compiler options from ts-morph */
compilerOptions?: CompilerOptions;
/** Paths to ignore during analysis (default: node_modules, dist, build, .git) */
ignoredPaths?: (string | RegExp)[];
/** Experimental feature to detect lockfile changes and find affected files */
__experimentalLockfileCheck?: boolean;
}
interface TrueAffectedProject {
/** Project name */
name: string;
/** Project source root directory path */
sourceRoot: string;
/** Path to project's tsconfig file (default: {sourceRoot}/tsconfig.json) */
tsConfig?: string;
/** Array of project names that implicitly depend on this project */
implicitDependencies?: string[];
/** Build targets for the project */
targets?: string[];
}
interface TrueAffectedLogging {
/** Console-compatible logger instance (default: console with debug controlled by DEBUG env var) */
logger?: Console;
}Usage Example:
import { trueAffected } from '@traf/core';
// Analyze monorepo with custom configuration
const affected = await trueAffected({
cwd: '/path/to/monorepo',
rootTsConfig: 'tsconfig.base.json',
base: 'origin/develop',
projects: [
{
name: 'api-service',
sourceRoot: 'apps/api/src',
tsConfig: 'apps/api/tsconfig.json',
implicitDependencies: ['database-migrations'],
},
{
name: 'web-app',
sourceRoot: 'apps/web/src',
tsConfig: 'apps/web/tsconfig.json',
},
{
name: 'shared-utils',
sourceRoot: 'libs/utils/src',
tsConfig: 'libs/utils/tsconfig.json',
},
{
name: 'database-migrations',
sourceRoot: 'libs/migrations',
},
],
include: [
/\.(spec|test)\.(ts|js)x?$/, // Include test files
/\.config\.(ts|js)$/, // Include config files
],
ignoredPaths: [
'./node_modules',
'./dist',
'./coverage',
],
logger: console,
});
console.log(`Found ${affected.length} affected projects:`, affected);Notes:
rootTsConfig should include a paths property mapping all projects so ts-morph can resolve references across the monorepotsConfig should only include files for that specific projecttsConfig doesn't exist, the library will add all .ts and .js files from the sourceRootDEBUG=true environment variable enables detailed debug loggingRegular expression constant for the default test file inclusion pattern.
const DEFAULT_INCLUDE_TEST_FILES: RegExp;This constant equals /\.(spec|test)\.(ts|js)x?/ and matches files like:
file.spec.tsfile.test.jscomponent.spec.tsxutils.test.jsxUsage Example:
import { trueAffected, DEFAULT_INCLUDE_TEST_FILES } from '@traf/core';
const affected = await trueAffected({
cwd: process.cwd(),
rootTsConfig: 'tsconfig.base.json',
projects: [...],
include: [
DEFAULT_INCLUDE_TEST_FILES,
/\.e2e\.(ts|js)$/, // Also include e2e test files
],
});Type re-exported from ts-morph for TypeScript compiler configuration.
import { CompilerOptions } from 'ts-morph';This type is used in the TrueAffected.compilerOptions property to configure the TypeScript compiler behavior during analysis. Common options include:
const affected = await trueAffected({
cwd: process.cwd(),
projects: [...],
compilerOptions: {
allowJs: true, // Allow JavaScript files (default: true)
strict: false, // Disable strict type checking for analysis
skipLibCheck: true, // Skip type checking of declaration files
},
});The algorithm performs the following steps:
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
TypeScript Project Setup: Creates a ts-morph Project instance and adds all source files from the specified projects using their tsconfig files
Changed Line Analysis: For each changed line in source files:
Reference Tracking: For each changed element:
findReferencesAsNodes() to find all references to that elementNon-Source File Handling: For changed non-source files (images, JSON, etc.):
Test File Inclusion: Any changed files matching the include patterns cause their containing project to be marked as affected
Implicit Dependencies: Projects that declare implicit dependencies on affected projects are added to the affected set
Lockfile Analysis (experimental): If enabled and package-lock.json changed:
The library may throw errors in the following scenarios:
"Unable to get diff for base: \"{base}\". are you using the correct base?""Unable to get file \"{filePath}\" for base: \"{base}\". are you using the correct base?"@traf/core is designed as a reusable core library for integration with various monorepo tools. Companion packages provide tool-specific integrations:
@traf/nx - Integration with Nx monorepo tool@traf/turbo - Integration with Turbo monorepo tool (future)These packages handle tool-specific project configuration and provide convenient wrappers around the core functionality.