n building, while maxWorkers is dynamically capped to prevent memory thrashing. A parallel health monitoring service tracks type coverage, LOC growth, and dependency bloat, enforcing gates before CI promotion.
Code Example 1: Incremental Build Optimizer with Project References
// build-optimizer.ts
// Imports: TypeScript compiler API, filesystem utilities, path resolution
import ts from 'typescript';
import { access, readdir, writeFile } from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
// Resolve current directory for ESM compatibility
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Configuration interface for build optimizer
interface BuildOptimizerConfig {
rootTsConfigPath: string;
outputDir: string;
incremental: boolean;
maxWorkers: number;
reportDiagnostics: boolean;
}
// Default configuration for 500k+ LOC monoliths
const DEFAULT_CONFIG: BuildOptimizerConfig = {
rootTsConfigPath: path.resolve(__dirname, 'tsconfig.json'),
outputDir: path.resolve(__dirname, 'dist'),
incremental: true,
maxWorkers: Math.min(4, require('os').cpus().length), // Limit workers to avoid memory thrashing for large monoliths
reportDiagnostics: true,
};
/**
* Validates that the root tsconfig exists and has project references enabled
* @param config - Build optimizer configuration
* @throws Error if tsconfig is invalid or missing
*/
async function validateConfig(config: BuildOptimizerConfig): Promise {
try {
await access(config.rootTsConfigPath);
} catch {
throw new Error(`Root tsconfig not found at ${config.rootTsConfigPath}`);
}
const configFile = ts.readConfigFile(config.rootTsConfigPath, (p) => ts.sys.readFile(p));
if (configFile.error) {
throw new Error(`Invalid tsconfig: ${configFile.error.messageText}`);
}
const parsedConfig = ts.parseJsonConfigFileContent(
configFile.config,
ts.sys,
path.dirname(config.rootTsConfigPath)
);
if (!parsedConfig.options.composite) {
throw new Error('Root tsconfig must have "composite": true for project references');
}
return parsedConfig;
}
/**
* Runs incremental TypeScript build with project references
* @param config - Build optimizer configuration
*/
async function runIncrementalBuild(config: BuildOptimizerConfig): Promise {
const parsedConfig = await validateConfig(config);
const buildOptions: ts.BuildOptions = {
incremental: config.incremental,
verbose: config.reportDiagnostics,
maxWorkers: config.maxWorkers,
outDir: config.outputDir,
};
// Create build host with error handling
const buildHost = ts.createSolutionBuilderHost(undefined, undefined, (diagnostic) => {
console.error(`Build error: ${ts.formatDiagnostic(diagnostic, ts.createCompilerHost(parsedConfig.options))}`);
});
const solutionBuilder = ts.createSolutionBuilder(buildHost, [config.rootTsConfigPath], buildOptions);
const exitCode = solutionBuilder.build();
if (exitCode !== 0) {
throw new Error(`Build failed with exit code ${exitCode}`);
}
console.log(`Incremental build completed successfully. Output: ${config.outputDir}`);
}
// Main execution with top-level error handling
async function main() {
const config = { ...DEFAULT_CONFIG };
// Override config from environment variables if present
if (process.env.OUTPUT_DIR) {
config.outputDir = path.resolve(process.env.OUTPUT_DIR);
}
if (process.env.MAX_WORKERS) {
config.maxWorkers = parseInt(process.env.MAX_WORKERS, 10) || config.maxWorkers;
}
try {
await runIncrementalBuild(config);
} catch (error) {
console.error('Fatal build error:', error instanceof Error ? error.message : error);
process.exit(1);
}
}
// Run main if this is the entry point
if (import.meta.url === `file://${process.argv[1]}`) {
main();
}
Code Example 2: Monolith Health Checker
// monolith-health-checker.ts
// Imports for TypeScript analysis, metrics tracking, and reporting
import ts from 'typescript';
import { readFile, writeFile, readdir } from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
import { promisify } from 'util';
import { exec } from 'child_process';
const execAsync = promisify(exec);
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Metrics interface for health check results
interface MonolithHealthMetrics {
typeCoverage: number;
totalLOC: number;
buildTimeMs: number;
dependencyCount: number;
circularDependencyCount: number;
memoryUsageMB: number;
timestamp: string;
}
// Health checker configuration
interface HealthCheckerConfig {
rootDir: string;
tsConfigPath: string;
outputPath: string;
thresholdTypeCoverage: number;
thresholdBuildTimeMs: number;
}
const DEFAULT_HEALTH_CONFIG: HealthCheckerConfig = {
rootDir: path.resolve(__dirname, 'src'),
tsConfigPath: path.resolve(__dirname, 'tsconfig.json'),
outputPath: path.resolve(__dirname, 'health-report.json'),
thresholdTypeCoverage: 85, // Minimum 85% type coverage for monoliths
thresholdBuildTimeMs: 300000, // 5 minutes max build time
};
/**
* Calculates type coverage for a TypeScript monolith
* @param config - Health checker configuration
* @returns Type coverage percentage (0-100)
*/
async function calculateTypeCoverage(config: HealthCheckerConfig): Promise {
const { stdout } = await execAsync(`npx type-coverage --project ${config.tsConfigPath} --output json`);
const coverageReport = JSON.parse(stdout);
return coverageReport.coverage;
}
/**
* Counts total lines of code (LOC) in the monolith, excluding tests and generated code
* @param rootDir - Root directory of the monolith source
* @returns Total LOC
*/
async function countLOC(rootDir: string): Promise {
let totalLOC = 0;
const files = await getAllTypeScriptFiles(rootDir);
for (const file of files) {
const content = await readFile(file, 'utf
Architecture Decisions:
- Composite Project Structure: Enforced via
composite: true to generate .tsbuildinfo files, enabling true incremental compilation.
- Worker Capping:
maxWorkers is dynamically limited to Math.min(4, cpus.length) to prevent memory thrashing on CI runners handling 500k+ LOC.
- Environment-Driven Configuration: Runtime overrides via
process.env allow CI/CD pipelines to adjust output directories and concurrency without code changes.
- Threshold-Based Health Gates: Type coverage and build time thresholds are enforced programmatically, failing CI when technical debt exceeds acceptable bounds.
Pitfall Guide
- Ignoring
composite: true Requirement: Project references silently fall back to full recompilation if the root tsconfig.json lacks "composite": true. Always validate this flag programmatically before invoking createSolutionBuilder.
- Over-Provisioning CI Workers: Setting
maxWorkers to os.cpus().length on large monoliths causes memory thrashing and OOM kills. Cap workers at 4β6 and monitor RSS memory per process.
- Skipping AST Cache Invalidation:
ts-morph and similar AST tools retain stale node references across builds. Implement cache invalidation based on file hashes or .tsbuildinfo timestamps to prevent memory leaks and incorrect transformations.
- Hardcoding Path Resolutions: Using relative strings instead of
ts.sys or path.resolve() breaks cross-platform CI (Windows vs. Linux). Always normalize paths through the TypeScript compiler host utilities.
- Neglecting Circular Dependency Detection: Unchecked circular imports cause exponential type-check latency. Integrate
madge or ts-morph graph traversal to flag cycles before they propagate into the build graph.
- Treating Health Metrics as Static: Monitoring type coverage or build time without CI enforcement allows technical debt to accumulate. Gate PRs on threshold breaches and track metric deltas over time.
- Mixing Runtime and Type-Only Imports: Without
verbatimModuleSyntax or explicit import type, bundlers and compilers may retain unused type declarations in output artifacts, bloating bundle size and type-check scope.
Deliverables
- Blueprint: Optimized TypeScript Monolith Architecture β Dependency graph visualization, project reference mapping strategy, and incremental migration path from monolith to hybrid module federation.
- Checklist: CI/CD Integration & Health Validation β Pre-flight tsconfig validation, worker provisioning limits, AST cache invalidation steps, threshold enforcement gates, and diagnostic reporting configuration.
- Configuration Templates:
tsconfig.build.json (composite-enabled with path mapping)
build-optimizer.env (environment variable overrides for CI runners)
health-checker.config.json (threshold definitions, metric collection intervals, and output schema)
- GitHub Actions/GitLab CI workflow snippets for automated health gate enforcement and incremental build execution.