@raegen/tscheck
v0.1.7
Published
Lightweight type-checker for TypeScript monorepos with project references - no emit required
Downloads
801
Maintainers
Readme
tscheck
Lightweight type-checker for TypeScript monorepos with project references - no emit required.
Why?
TypeScript's tsc --build requires emitting files (at minimum --emitDeclarationOnly). This means:
- You need
outDirconfigured - Files are written to disk
- CI pipelines need to handle generated artifacts
tscheck solves this by building .d.ts declarations in-memory only, allowing you to type-check monorepos with project references without producing any output files.
Warning
Bear in mind what this actually means. Because it's by no means magic. What tscheck does is a tradeoff:
+ no emitted artifacts to manage and a slight speed improvement
- noticably higher memory usage
So, if you're looking for a plug&play typechecking in a monorepo and you can afford the memory usage, this might be worth a try. Otherwise, just stick to stock.
Installation
npm install -D @raegen/tscheck typescriptCLI Usage
# Check the default tsconfig.json
tscheck
# Check a specific project
tscheck packages/app/tsconfig.json
# Only care about errors in THIS project, not its dependencies
tscheck packages/app/tsconfig.json --own
# Solution-style config: check each referenced project and report all errors
tscheck tsconfig.json
# Verbose: show which project is being checked and a summary at the end
tscheck tsconfig.json -v
# Skip references pointing outside the tsconfig's own directory
tscheck tsconfig.json --ignore-external-referencesOptions
| Flag | Description |
|------|-------------|
| -p, --project <path> | Path to tsconfig.json (default: ./tsconfig.json) |
| --own-errors-only, --own | Only report errors owned by the target project (see Ownership); implies --ignore-external-references |
| -r, --include-references | Treat all projects in the graph as "owned" (report all errors regardless of origin) |
| --ignore-external-references | Skip project references that resolve outside the referencing tsconfig's directory |
| --show-ownership | Show which project owns each error |
| -t, --timing | Show per-project timing breakdown |
| --pretty / --no-pretty | Enable/disable colored output (default: enabled when stdout is a TTY) |
| -v, --verbose | Print each project as it is checked and a summary at the end |
| --debug | Show debug information about the project graph and diagnostic attribution |
Exit Codes
| Code | Meaning |
|------|---------|
| 0 | No type errors (or no owned errors when --own) |
| 1 | Type errors found |
| 2 | Configuration or runtime error |
Ownership
The --own flag controls which errors count toward the exit code and are displayed.
For a plain (non-solution) tsconfig, owned files are those matched by its include/files patterns. Errors in files that are only reachable via imports from dependencies — even if those dependencies are listed in references — are not owned.
For a solution-style tsconfig (files: [] with only references), tscheck recurses through the solution chain. Any leaf tsconfig reachable by following only solution-style nodes is owned. The references inside those leaf configs (e.g. a shared library added by a sync tool) are dependency references — their errors are not owned from the solution's perspective.
Example:
monorepo/tsconfig.json ← solution (files: [])
packages/app/tsconfig.json ← solution (files: [])
tsconfig.app.json ← leaf (owned from monorepo root)
tsconfig.node.json ← leaf (owned from monorepo root)
packages/shared ← dependency reference (NOT owned from app, IS owned from monorepo root)Running tscheck packages/app/tsconfig.json --own will not report errors in packages/shared, because shared is only a dependency of the leaf configs, not part of the app's solution chain.
Running tscheck monorepo/tsconfig.json --own will report errors in packages/shared, because shared appears as a leaf in the monorepo's solution chain (it has files and is reachable from the root).
--own implies --ignore-external-references so that tsconfig references added by tools like the NX TypeScript sync generator (which may point to workspace peers outside the package directory) are silently skipped rather than causing spurious TS6305 errors.
Programmatic API
Simple API
import { check } from '@raegen/tscheck';
const result = check('./tsconfig.json');
if (!result.success) {
console.log(result.formattedDiagnostics);
console.log(result.formattedSummary);
process.exit(1);
}check() accepts either a path string or an options object:
import { check } from '@raegen/tscheck';
const result = check({
project: './tsconfig.json',
ownErrorsOnly: true,
includeReferences: true,
ignoreExternalReferences: true,
colors: true,
});Low-level API
import { checkProjects } from '@raegen/tscheck';
const result = checkProjects({
project: './tsconfig.json',
ownErrorsOnly: true,
onProjectStart: (project, index, total) => {
console.log(`[${index + 1}/${total}] ${project.configPath}`);
},
onDiagnostic: (attributed) => {
if (attributed.isOwned) process.stdout.write('!');
},
});
if (!result.success) {
console.log(`Found ${result.ownedErrorCount} owned errors`);
process.exit(1);
}CheckOptions
interface CheckOptions {
/** Path to root tsconfig.json */
project: string;
/** Only count/report errors owned by the target project (see Ownership section) */
ownErrorsOnly?: boolean;
/** Treat all projects in the graph as "owned" */
includeReferences?: boolean;
/**
* Skip project references that resolve outside the referencing tsconfig's directory.
* Implied when ownErrorsOnly is true.
*/
ignoreExternalReferences?: boolean;
/** Callback for each diagnostic as it's found */
onDiagnostic?: (diagnostic: AttributedDiagnostic) => void;
/** Callback when starting to check a project (only fires for projects with files) */
onProjectStart?: (project: ProjectNode, index: number, total: number) => void;
/** Callback when finished checking a project (only fires for projects with files) */
onProjectEnd?: (project: ProjectNode, diagnostics: AttributedDiagnostic[], durationMs?: number) => void;
}CheckResult
interface CheckResult {
/** Whether type-checking passed with no errors */
success: boolean;
/** All diagnostics (errors and warnings) */
diagnostics: ts.Diagnostic[];
/** Diagnostics with project attribution */
attributedDiagnostics: AttributedDiagnostic[];
/** Number of projects checked (solution-style umbrella configs excluded) */
projectCount: number;
/** Total number of files checked */
fileCount: number;
/** Total number of errors */
errorCount: number;
/** Number of warnings */
warningCount: number;
/** Number of errors owned by the checked project(s) */
ownedErrorCount: number;
/** Number of errors from dependencies */
dependencyErrorCount: number;
/** Per-project timing information */
projectTimings: ProjectTiming[];
/** Total checking duration in milliseconds */
totalDurationMs: number;
}AttributedDiagnostic
interface AttributedDiagnostic {
/** The underlying TypeScript diagnostic */
diagnostic: ts.Diagnostic;
/** The project that was being checked when this diagnostic was found */
sourceProject: ProjectNode;
/** The project that owns the file where the error is */
owningProject: ProjectNode | null;
/** Whether this error is owned by the target project (see Ownership section) */
isOwned: boolean;
}How It Works
- Parse project graph — Resolves
tsconfig.jsonand all project references into a dependency graph, topologically sorted so dependencies come first. - Virtual file system — Creates an in-memory compiler host that captures
.d.tsoutput from each project. - Type-check — Runs TypeScript's semantic analysis on each project in order, writing declarations to memory rather than disk.
- Cross-project resolution — Later projects resolve imports from earlier ones via the in-memory
.d.tsfiles, exactly astsc --buildwould, but without touching the filesystem. - Diagnostic attribution — Every diagnostic is tagged with its owning project, enabling
--ownand--show-ownershipto work correctly across the graph.
License
MIT
