Compare commits

...

44 Commits

Author SHA1 Message Date
b38ef6cf82 2.7.1 2025-11-02 06:13:34 +00:00
8b50cd3090 fix(readme): Update documentation: expand README with usage, CLI and API examples; add readme.hints.md project memory 2025-11-02 06:13:34 +00:00
c6ab493efc 2.7.0 2025-11-02 05:31:55 +00:00
82ae8a0e4a feat(tsbuild): Add tsconfig.json support and safer compiler option merging; protect critical options; apply path and enum transforms; bump dependencies. 2025-11-02 05:31:55 +00:00
787becc4d3 2.6.8 2025-08-29 17:14:58 +00:00
746ca8767a fix(tsbuild): Avoid process.exit in library, add confirmskiplibcheck flag, improve CLI exit handling and JSON/quiet modes, update test script 2025-08-29 17:14:58 +00:00
55c1a2953a 2.6.7 2025-08-18 02:19:15 +00:00
8e9fcb8135 fix(tspublish): Bump @git.zone/tspublish dependency to ^1.10.3 2025-08-18 02:19:15 +00:00
18573c777d 2.6.6 2025-08-18 00:32:26 +00:00
fa654b83e3 fix(dependencies): Update dependency @git.zone/tspublish to ^1.10.2 2025-08-18 00:32:26 +00:00
aa3c83cd95 2.6.5 2025-08-18 00:29:51 +00:00
20ed41df42 fix(dependencies): Bump dependencies and add pnpm-workspace configuration 2025-08-18 00:29:51 +00:00
7162476f7f 2.6.4 2025-05-24 00:30:16 +00:00
1b012628eb fix(dependencies): Add .npmrc and update dependency versions for smartfile and tstest 2025-05-24 00:30:16 +00:00
4081086621 2.6.3 2025-05-21 18:06:46 +00:00
b9cf09ccba fix(tsbuild): minor maintenance updates and documentation improvements 2025-05-21 18:06:46 +00:00
8463fbc78a 2.6.2 2025-05-21 17:59:28 +00:00
0164eb51a1 fix(npm configuration): Remove .npmrc file to default npm registry behavior 2025-05-21 17:59:28 +00:00
a305dd89dd 2.6.1 2025-05-21 17:58:22 +00:00
fa6b053ee0 fix(tsbuild.classes): Improve error diagnostics handling by removing legacy helper and integrating more robust error summaries in the compilation process 2025-05-21 17:58:22 +00:00
adfba21c67 2.6.0 2025-05-21 13:38:21 +00:00
050e41cdf9 feat(tsbuild): Improve task logging and update dependencies 2025-05-21 13:38:21 +00:00
f220a11caa 2.5.2 2025-05-21 00:20:45 +00:00
0909fa306a fix(tsbuild): Improve diagnostic error handling and summary reporting for TypeScript compilation by refactoring diagnostic processing and adding pre-emit error checks. 2025-05-21 00:20:45 +00:00
88c0601c03 2.5.1 2025-05-15 14:07:26 +00:00
9645f27942 fix(commitinfo): Update commit information and metadata to synchronize release data 2025-05-15 14:07:26 +00:00
b73aa4f21f 2.5.0 2025-05-15 13:53:08 +00:00
d9d6878a9f feat(cli): Enhance type checking in CLI by adding default file pattern handling 2025-05-15 13:53:08 +00:00
30506da84c 2.4.1 2025-05-15 09:55:29 +00:00
52b4d8f944 fix(cli): Improve TS folder compilation order display in CLI 2025-05-15 09:55:29 +00:00
57d2726f6b 2.4.0 2025-05-15 09:31:57 +00:00
960fc5f213 feat(cli): Add new check command for type checking and update compiler options handling 2025-05-15 09:31:57 +00:00
6d68f35a9a 2.3.2 2025-03-20 15:30:52 +00:00
0378b9feca fix(compileGlobStringObject): Fix duplicate file outputs in glob pattern processing 2025-03-20 15:30:52 +00:00
5ecf4b7125 2.3.1 2025-03-20 15:20:27 +00:00
2aa6348cdd fix(compiler): Refactor compiler implementation with consolidated TsBuild class and improved diagnostics handling 2025-03-20 15:20:27 +00:00
9f42670865 2.3.0 2025-03-20 15:16:02 +00:00
6cc9f41bd2 feat(cli): Add emitcheck command to validate TS file emission without generating output 2025-03-20 15:16:02 +00:00
5d32ac85e0 2.2.7 2025-03-17 10:27:36 +00:00
4f2ac6922a fix(compiler): Improve diagnostic checking and error handling in the TypeScript compiler integration 2025-03-17 10:27:36 +00:00
31834e0b3e 2.2.6 2025-03-04 23:13:33 +00:00
c6c94866bb fix(package): Fix repository URL in package.json 2025-03-04 23:13:33 +00:00
09a648b435 2.2.5 2025-03-04 23:00:51 +00:00
065e0baaf7 fix(package.json): Update repository URLs in package metadata. 2025-03-04 23:00:51 +00:00
14 changed files with 8287 additions and 3222 deletions

3
.gitignore vendored
View File

@@ -17,4 +17,5 @@ node_modules/
dist/
dist_*/
# custom
# custom
.claude

View File

@@ -1,5 +1,166 @@
# Changelog
## 2025-11-02 - 2.7.1 - fix(readme)
Update documentation: expand README with usage, CLI and API examples; add readme.hints.md project memory
- Add readme.hints.md: new project memory / quick reference with public API and CLI summaries
- Expand and restructure readme.md: more comprehensive Quick Start, CLI Commands, API Reference, configuration, examples and troubleshooting
- Clarify protected compiler options, default compiler options, path transformation behavior and error-handling patterns
- Docs-only change — no source code or behavioral changes
## 2025-11-02 - 2.7.0 - feat(tsbuild)
Add tsconfig.json support and safer compiler option merging; protect critical options; apply path and enum transforms; bump dependencies.
- Add robust tsconfig.json reading with graceful fallback when no tsconfig is present or it is invalid
- Merge compiler options in a clear priority order (defaults -> tsconfig -> protected defaults -> programmatic -> CLI flags)
- Introduce protected (critical) compiler options that cannot be overridden by tsconfig.json: outDir, noEmitOnError, declaration, emitDecoratorMetadata, inlineSourceMap
- Convert string values from tsconfig (target, module, moduleResolution) to TypeScript enum values where applicable; special-case NodeNext
- Transform tsconfig path mappings by replacing './ts_' with './dist_ts_' to keep runtime path resolution consistent with compiled output
- Expose getCriticalDefaults helper and adjust mergeCompilerOptions to apply protected defaults before programmatic and CLI overrides
- Update README with documentation for tsconfig support, merge order, protected compiler options, and example tsconfig
- Bump dependencies/devDependencies: @push.rocks/smartcli ^4.0.19, @push.rocks/smartlog ^3.1.10, typescript 5.9.3, @git.zone/tsrun ^1.6.2, @git.zone/tstest ^2.7.0
## 2025-08-29 - 2.6.8 - fix(tsbuild)
Avoid process.exit in library, add confirmskiplibcheck flag, improve CLI exit handling and JSON/quiet modes, update test script
- Changed package.json test script from "tsrun test/test.ts --verbose" to "tstest test/test.ts --verbose".
- Library no longer calls process.exit from compile and compileWithErrorTracking; errors are returned or thrown so callers can decide process termination.
- skipLibCheck behavior updated: delay/warning only happens when --confirmskiplibcheck is present; otherwise a short informational note is printed (suppressed in --quiet/--json).
- CLI now awaits compileGlobStringObject calls and inspects a final error summary attached to argv to decide process.exit(1) when errors occurred.
- compileGlobStringObject/exports now respect --quiet and --json modes, emit a JSON summary when --json is used, and attach the final error summary to argv so the CLI can determine exit behavior.
## 2025-08-18 - 2.6.7 - fix(tspublish)
Bump @git.zone/tspublish dependency to ^1.10.3
- Updated dependency @git.zone/tspublish from ^1.10.2 to ^1.10.3 in package.json
## 2025-08-18 - 2.6.6 - fix(dependencies)
Update dependency @git.zone/tspublish to ^1.10.2
- Bumped @git.zone/tspublish in package.json from ^1.10.1 to ^1.10.2
## 2025-08-18 - 2.6.5 - fix(dependencies)
Bump dependencies and add pnpm-workspace configuration
- Updated @git.zone/tspublish from ^1.9.1 to ^1.10.1
- Updated @push.rocks/smartfile from ^11.2.4 to ^11.2.7
- Updated @push.rocks/smartpath from ^5.0.18 to ^6.0.0
- Updated typescript from 5.8.3 to 5.9.2
- Updated devDependency @git.zone/tstest from ^1.10.1 to ^2.3.4
- Added pnpm-workspace.yaml with onlyBuiltDependencies list (esbuild, mongodb-memory-server, puppeteer)
## 2025-05-24 - 2.6.4 - fix(dependencies)
Add .npmrc and update dependency versions for smartfile and tstest
- Add .npmrc with registry configuration for npm
- Bump @push.rocks/smartfile version from ^11.2.3 to ^11.2.4
- Bump @git.zone/tstest version from ^1.9.0 to ^1.10.1
## 2025-05-21 - 2.6.3 - fix(tsbuild)
minor maintenance updates and documentation improvements
- Updated commit metadata to align with project version
- Refined CLI command parsing and diagnostics logging for better clarity
- Improved code readability in compiler options merging
## 2025-05-21 - 2.6.2 - fix(npm configuration)
Remove .npmrc file to default npm registry behavior
- Deleted .npmrc file that hard-coded the npm registry URL to https://registry.npmjs.org/
- This change leverages npm's default registry settings and reduces configuration clutter
## 2025-05-21 - 2.6.1 - fix(tsbuild.classes)
Improve error diagnostics handling by removing legacy helper and integrating more robust error summaries in the compilation process
- Removed the handleDiagnostics method and its legacy usage
- Replaced legacy calls with inline processDiagnostics checks for pre-emit and emit phases
- Combined error summaries for clearer reporting during emit checks and type validation
- Enhanced error output to guide users on resolving TypeScript errors before emission
## 2025-05-21 - 2.6.0 - feat(tsbuild)
Improve task logging and update dependencies
- Add .npmrc file with npm registry configuration
- Update test script to use '--verbose' flag
- Bump dependency versions for @push.rocks packages and TypeScript
- Enhance TsBuild logging by incorporating task info (e.g. task number, total tasks, output folder, and compile durations)
- Propagate task info in compileFileArrayWithErrorTracking for better task tracking
## 2025-05-21 - 2.5.2 - fix(tsbuild)
Improve diagnostic error handling and summary reporting for TypeScript compilation by refactoring diagnostic processing and adding pre-emit error checks.
- Introduce a dedicated processDiagnostics function that categorizes errors by file and computes error totals.
- Refactor displayErrorSummary to provide clearer, color-coded output of error details.
- Add pre-emit error checking in compileWithErrorTracking to prevent emission when errors exist.
- Consolidate error summary merging in compileFileArrayWithErrorTracking for improved reporting.
## 2025-05-15 - 2.5.1 - fix(commitinfo)
Update commit information and metadata to synchronize release data
- Regenerated the commitinfo file with current version details
- Maintained existing functionality with no functional code changes
## 2025-05-15 - 2.5.0 - feat(cli)
Enhance type checking in CLI by adding default file pattern handling
- When no TypeScript file or glob pattern is provided, the CLI now performs a default type checking sequence.
- First checks 'ts/**/*' files with standard options, then checks 'test/**/*' files with skiplibcheck enabled.
- Improved logging to indicate file discovery and check results, ensuring clear feedback for users.
## 2025-05-15 - 2.4.1 - fix(cli)
Improve TS folder compilation order display in CLI
- Refactor folder compilation output to use a bordered, tabular format with order numbering
- Enhance readability of TS folder compilation plan in the CLI output
## 2025-05-15 - 2.4.0 - feat(cli)
Add new 'check' command for type checking and update compiler options handling
- Introduced a new 'check' command to verify TypeScript files without emitting output
- Updated CLI error messages and logging for better clarity
- Replaced '--allowimplicitany' flag with '--disallowimplicitany' to reflect new default behavior
- Modified compiler options default settings (noImplicitAny now set to false) for more flexible type handling
- Refined diagnostic output in tsbuild class for improved error reporting
- Updated .gitignore to exclude the .claude file
- Enhanced documentation in readme and implementation plan files
## 2025-03-20 - 2.3.2 - fix(compileGlobStringObject)
Fix duplicate file outputs in glob pattern processing
- Removed duplicate concatenation of compiled files in compileGlobStringObject
- Ensured unique file paths are used during TypeScript compilation
## 2025-03-20 - 2.3.1 - fix(compiler)
Refactor compiler implementation with consolidated TsBuild class and improved diagnostics handling
- Removed legacy tsbuild.classes.compiler.ts and introduced tsbuild.classes.tsbuild.ts
- Unified compiler options merging, reading tsconfig.json, and diagnostics reporting within the TsBuild class
- Updated exports to reference the new compiler class implementation for backward compatibility
## 2025-03-20 - 2.3.0 - feat(cli)
Add emitcheck command to validate TS file emission without generating output
- Implemented the emitcheck CLI command to allow checking if TypeScript files can be emitted without producing files
- Updated tsbuild.classes.compiler.ts to include the emitCheck function
- Enhanced documentation in readme.md to describe the new emitcheck usage with examples
## 2025-03-17 - 2.2.7 - fix(compiler)
Improve diagnostic checking and error handling in the TypeScript compiler integration
- Added pre-emit diagnostics check to log errors before emitting
- Process exits on encountering pre-emit errors to ensure build correctness
- Enhanced logging for emit diagnostics to aid debugging
## 2025-03-04 - 2.2.6 - fix(package)
Fix repository URL in package.json
- Updated repository URL in package.json to point to the correct Git repository.
## 2025-03-04 - 2.2.5 - fix(package.json)
Update repository URLs in package metadata.
- Corrected the 'bugs.url' and 'homepage' fields with the accurate URLs.
## 2025-03-04 - 2.2.4 - fix(core)
Fix compiler logic to remove duplicate compiled files and improve glob pattern handling.

View File

@@ -1,6 +1,6 @@
{
"name": "@git.zone/tsbuild",
"version": "2.2.4",
"version": "2.7.1",
"private": false,
"description": "A tool for compiling TypeScript files using the latest nightly features, offering flexible APIs and a CLI for streamlined development.",
"main": "dist_ts/index.js",
@@ -10,13 +10,13 @@
"tsbuild": "./cli.js"
},
"scripts": {
"test": "tsrun test/test.ts",
"build": "node cli.ts.js --web --allowimplicitany",
"test": "tstest test/test.ts --verbose",
"build": "node cli.ts.js --web",
"buildDocs": "tsdoc"
},
"repository": {
"type": "git",
"url": "git+ssh://git@gitlab.com/pushrocks/tsn.git"
"url": "https://code.foss.global/git.zone/tsbuild.git"
},
"keywords": [
"TypeScript",
@@ -32,24 +32,24 @@
"author": "Lossless GmbH",
"license": "MIT",
"bugs": {
"url": "https://gitlab.com/pushrocks/tsn/issues"
"url": "https://code.foss.global/git.zone/tsbuild/issues"
},
"homepage": "https://gitlab.com/pushrocks/tsn#README",
"homepage": "https://code.foss.global/git.zone/tsbuild#README",
"dependencies": {
"@git.zone/tspublish": "^1.9.1",
"@git.zone/tspublish": "^1.10.3",
"@push.rocks/early": "^4.0.4",
"@push.rocks/smartcli": "^4.0.11",
"@push.rocks/smartcli": "^4.0.19",
"@push.rocks/smartdelay": "^3.0.5",
"@push.rocks/smartfile": "^11.1.5",
"@push.rocks/smartlog": "^3.0.7",
"@push.rocks/smartpath": "^5.0.18",
"@push.rocks/smartpromise": "^4.2.2",
"typescript": "5.7.3"
"@push.rocks/smartfile": "^11.2.7",
"@push.rocks/smartlog": "^3.1.10",
"@push.rocks/smartpath": "^6.0.0",
"@push.rocks/smartpromise": "^4.2.3",
"typescript": "5.9.3"
},
"devDependencies": {
"@git.zone/tsrun": "^1.2.47",
"@push.rocks/tapbundle": "^5.5.6",
"@types/node": "^22.12.0"
"@git.zone/tsrun": "^1.6.2",
"@git.zone/tstest": "^2.7.0",
"@types/node": "^22.15.21"
},
"files": [
"ts/**/*",
@@ -65,5 +65,6 @@
],
"browserslist": [
"last 1 chrome versions"
]
],
"packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39"
}

8969
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

4
pnpm-workspace.yaml Normal file
View File

@@ -0,0 +1,4 @@
onlyBuiltDependencies:
- esbuild
- mongodb-memory-server
- puppeteer

View File

@@ -0,0 +1,175 @@
# @git.zone/tsbuild - Project Memory
## Quick Reference
### Public API (8 functions + 1 class)
1. **compileFileArray()** - Basic compilation, throws on error
2. **compileFileArrayWithErrorTracking()** - RECOMMENDED, returns IErrorSummary
3. **compileGlobStringObject()** - Most powerful, multiple patterns
4. **TsBuild Class** - Object-oriented API with compile, checkTypes, checkEmit methods
5. **mergeCompilerOptions()** - Utility for option merging
6. **compiler()** - Legacy function
7. **emitCheck()** - Validate emit capability
8. **checkTypes()** - Type checking only
### CLI Commands (5)
1. **tsbuild** (default) - Compiles ./ts/**/*.ts → ./dist_ts/
2. **tsbuild custom <dir1> <dir2>** - Custom directory compilation
3. **tsbuild tsfolders** - Auto-discover ts_* folders, compile in order
4. **tsbuild emitcheck <pattern>** - Validate emit without output
5. **tsbuild check [pattern]** - Type check only
### CLI Flags
- `--skiplibcheck` - Skip .d.ts type checking (shows warning)
- `--confirmskiplibcheck` - Extended warning with 5s pause
- `--disallowimplicitany` - Stricter type checking
- `--commonjs` - Use CommonJS instead of ESNext
- `--quiet` - Suppress non-error output
- `--json` - JSON output format
## Key Architecture Decisions
### Configuration Priority (5 levels)
1. Default options (hardcoded)
2. tsconfig.json (if exists)
3. Protected defaults (ensure integrity)
4. Programmatic options (function params)
5. CLI flags (highest priority)
### Protected Options
Cannot be overridden by tsconfig.json alone:
- `outDir: 'dist_ts/'` - Path transformation logic
- `noEmitOnError: true` - Build integrity
- `declaration: true` - Library support
- `emitDecoratorMetadata: true` - DI frameworks
- `inlineSourceMap: true` - Debugging
### Path Transformation
- Automatic: `./ts_interfaces``./dist_ts_interfaces`
- In tsconfig paths: `./ts_*``./dist_ts_*` (first array element only)
## Default Compiler Options
- Module: NodeNext (ESM with CommonJS fallback)
- Target: ESNext (latest JavaScript)
- Decorators: ENABLED (experimentalDecorators + emitDecoratorMetadata)
- Source Maps: Inline (no separate .map files)
- Declaration Files: ALWAYS generated (protected)
- Output: dist_ts/
- Implicit any: ALLOWED by default
- esModuleInterop: true
## Error Handling
### Error Summary Structure
```typescript
interface IErrorSummary {
errorsByFile: Record<string, Diagnostic[]>
generalErrors: Diagnostic[]
totalErrors: number
totalFiles: number
}
```
### Three Error Patterns
1. **Throw Pattern** - compileFileArray: throws on error
2. **Tracking Pattern** - compileFileArrayWithErrorTracking: returns IErrorSummary, NO throw
3. **Boolean Pattern** - checkTypes/emitCheck: returns boolean
RECOMMENDATION: Use compileFileArrayWithErrorTracking for production code
## JSON Output Format
```json
{
"success": boolean,
"totals": {
"errors": number,
"filesWithErrors": number,
"tasks": number
},
"errorsByFile": {
"fileName": [
{ "code": number, "message": string }
]
}
}
```
## Special Behaviors
### tsfolders Command Ordering
1. Always: ts_interfaces first
2. Always: ts_shared second
3. Then: Other folders by `order` property in their tspublish.json
4. Finally: Folders without order property (Infinity)
### check Command Default (No Arguments)
Two-phase check:
1. Phase 1: Type check ts/**/* (strict, include .d.ts)
2. Phase 2: Type check test/**/* (relaxed, skipLibCheck: true)
### Glob Pattern Support
- `*` single level
- `**` recursive
- `?` single char
- `{a,b}` alternation
- Duplicates: Files matching multiple patterns compile multiple times
### Task Information Display
When compiling multiple files with taskInfo param:
Shows: `[1/3] Compiling 45 files from ./src/**/*.ts`
Plus: File counts, duration, and file type breakdown
## File Structure
- **index.ts** - Main entry, re-exports all
- **tsbuild.exports.ts** - Core API functions
- **tsbuild.classes.tsbuild.ts** - TsBuild class + utility functions
- **tsbuild.cli.ts** - CLI command definitions
- **plugins.ts** - Dependency imports (smartfile, smartpath, smartcli, etc.)
- **paths.ts** - Path utilities (cwd, packageDir)
## Dependencies Used
- @git.zone/tspublish@^1.10.3 - Module ordering
- @push.rocks/* - smartcli, smartfile, smartpath, smartpromise, smartdelay
- typescript@5.9.3 - TypeScript compiler
## Edge Cases
1. **Empty file list** - Returns [], no error
2. **Glob duplicates** - Files compile multiple times, possible duplicate errors
3. **Non-existent files** - Handled by TypeScript "file not found" errors
4. **skipLibCheck warning** - 1-line default, 5-second pause with --confirmskiplibcheck
5. **Missing tsconfig.json** - Graceful fallback, no error
6. **Module resolution** - --commonjs switches to NodeJs (not NodeNext)
7. **Source maps** - Inline only (not separate .map files)
8. **File filtering** - Only .ts and .tsx; .d.ts and .js ignored
## Build Safety Features
- `noEmitOnError: true` - Prevents broken builds
- Error aggregation before final output
- Protected options ensure integrity
- Pre-emit checks before emit phase
- CLI exit code handling (0=success, 1=error)
## Recent Changes (from git log)
- 2.6.8 - Current version
- 2.6.7 - Previous version
- Added confirmskiplibcheck flag
- Improved CLI exit handling
- JSON/quiet modes enhanced
- Test script updates
## Configuration Example (tsconfig.json)
Paths get automatically transformed:
```json
{
"compilerOptions": {
"paths": {
"@utils/*": ["./ts_utils/*"] // → ["./dist_ts_utils/*"]
}
}
}
```
## No ts_* folders found in project root
The project itself doesn't have ts_interfaces, ts_shared, etc. directories.
The tsfolders command is designed for OTHER projects using tsbuild.

969
readme.md

File diff suppressed because it is too large Load Diff

45
readme.plan.md Normal file
View File

@@ -0,0 +1,45 @@
# Implementation Plan for tsbuild `check` Command
## Overview
Add a new `check` command to tsbuild that allows checking TypeScript files against a glob pattern without emitting them, similar to running the TypeScript compiler with the `--noEmit` flag.
## Implementation Steps
1. **Reread CLAUDE.md** to ensure we follow project guidelines
2. **Extend TsBuild Class**
- The existing `TsBuild` class already has a `checkEmit()` method
- We can leverage this method for our implementation
3. **Implement Check Command in CLI**
- Add a new `check` command to `tsbuild.cli.ts`
- Command should accept glob patterns as arguments
- Process glob patterns to find matching TypeScript files
- Use the `TsBuild` class to check the files without emitting
4. **Update Exports**
- Ensure any new functionality is properly exported
5. **Testing**
- Test the command with various glob patterns
- Verify error reporting works correctly
## Differences from Existing `emitcheck` Command
The `emitcheck` command already exists and checks specific files without emitting. Our new `check` command will:
- Be designed specifically for checking files against glob patterns
- Use a simpler, more intuitive command name
- Potentially add additional benefits (like summary statistics of checked files)
## Example Usage
Once implemented, the command would work like this:
```bash
npx tsbuild check ts/**/*
npx tsbuild check "src/**/*.ts" "test/**/*.ts"
```
## Expected Output
The command should:
- Report any TypeScript errors in the matched files
- Provide a count of files checked and any errors found
- Exit with code 0 if successful, or 1 if errors are found

View File

@@ -1,4 +1,4 @@
import { tap, expect, expectAsync } from '@push.rocks/tapbundle';
import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as tsbuild from '../ts/index.js';

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@git.zone/tsbuild',
version: '2.2.4',
version: '2.7.1',
description: 'A tool for compiling TypeScript files using the latest nightly features, offering flexible APIs and a CLI for streamlined development.'
}

View File

@@ -1,128 +0,0 @@
// import all the stuff we need
import * as plugins from './plugins.js';
import * as paths from './paths.js';
import type { CompilerOptions, ScriptTarget, ModuleKind } from './tsbuild.exports.js';
/**
* the default typescript compilerOptions
*/
export const compilerOptionsDefault: CompilerOptions = {
declaration: true,
emitDecoratorMetadata: true,
experimentalDecorators: true,
inlineSourceMap: true,
noEmitOnError: true,
outDir: 'dist_ts/',
module: plugins.typescript.ModuleKind.NodeNext,
target: plugins.typescript.ScriptTarget.ESNext,
moduleResolution: plugins.typescript.ModuleResolutionKind.NodeNext,
lib: ['lib.dom.d.ts', 'lib.es2022.d.ts'],
noImplicitAny: true,
esModuleInterop: true,
useDefineForClassFields: false,
verbatimModuleSyntax: true,
baseUrl: './',
};
/**
* merges compilerOptions with the default compiler options
*/
export const mergeCompilerOptions = (
customTsOptions: CompilerOptions,
argvArg?: any
): CompilerOptions => {
// create merged options
const mergedOptions: CompilerOptions = {
...compilerOptionsDefault,
...customTsOptions,
...(argvArg && argvArg.skiplibcheck
? {
skipLibCheck: true,
}
: {}),
...(argvArg && argvArg.allowimplicitany
? {
noImplicitAny: false,
}
: {}),
...(argvArg && argvArg.commonjs
? {
module: plugins.typescript.ModuleKind.CommonJS,
moduleResolution: plugins.typescript.ModuleResolutionKind.NodeJs,
}
: {}),
...(() => {
const returnObject: CompilerOptions = {};
console.log(`looking at tsconfig.json at ${paths.cwd}`);
const tsconfig = plugins.smartfile.fs.toObjectSync(plugins.path.join(paths.cwd, 'tsconfig.json'));
if (tsconfig && tsconfig.compilerOptions && tsconfig.compilerOptions.baseUrl) {
console.log('baseUrl found in tsconfig.json');
returnObject.baseUrl = tsconfig.compilerOptions.baseUrl;
}
if (tsconfig && tsconfig.compilerOptions && tsconfig.compilerOptions.paths) {
console.log('paths found in tsconfig.json');
returnObject.paths = tsconfig.compilerOptions.paths;
for (const path of Object.keys(tsconfig.compilerOptions.paths)) {
returnObject.paths[path][0] = returnObject.paths[path][0].replace('./ts_', './dist_ts_');
}
}
return returnObject;
})(),
};
console.log(mergedOptions);
return mergedOptions;
};
/**
* the internal main compiler function that compiles the files
*/
export const compiler = async (
fileNames: string[],
options: plugins.typescript.CompilerOptions,
argvArg?: any
): Promise<any[]> => {
if (options.skipLibCheck) {
console.log('? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?');
console.log('You are skipping libcheck... Is that really wanted?');
console.log('continuing in 5 seconds...');
console.log('? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?');
await plugins.smartdelay.delayFor(5000);
}
console.log(`Compiling ${fileNames.length} files...`);
const done = plugins.smartpromise.defer<any[]>();
const program = plugins.typescript.createProgram(fileNames, options);
const emitResult = program.emit();
// implement check only
/*let emitResult = program.emit(undefined,(args) => {
console.log(args)
});*/
const allDiagnostics = plugins.typescript
.getPreEmitDiagnostics(program)
.concat(emitResult.diagnostics);
allDiagnostics.forEach((diagnostic) => {
if (diagnostic.file) {
const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start!);
const message = plugins.typescript.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
console.log(`${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`);
} else {
console.log(
`${plugins.typescript.flattenDiagnosticMessageText(diagnostic.messageText, '\n')}`
);
}
});
const exitCode = emitResult.emitSkipped ? 1 : 0;
if (exitCode === 0) {
console.log('TypeScript emit succeeded!');
done.resolve(emitResult.emittedFiles);
} else {
console.error('TypeScript emit failed. Please investigate!');
process.exit(exitCode);
}
return done.promise;
};

View File

@@ -0,0 +1,596 @@
// import all the stuff we need
import * as plugins from './plugins.js';
import * as paths from './paths.js';
import type { CompilerOptions, ScriptTarget, ModuleKind } from './tsbuild.exports.js';
/**
* Interface for error summary data
*/
export interface IErrorSummary {
errorsByFile: Record<string, plugins.typescript.Diagnostic[]>;
generalErrors: plugins.typescript.Diagnostic[];
totalErrors: number;
totalFiles: number;
}
/**
* Default compiler options for TypeScript compilation
*/
export const compilerOptionsDefault: CompilerOptions = {
declaration: true,
emitDecoratorMetadata: true,
experimentalDecorators: true,
inlineSourceMap: true,
noEmitOnError: true,
outDir: 'dist_ts/',
module: plugins.typescript.ModuleKind.NodeNext,
target: plugins.typescript.ScriptTarget.ESNext,
moduleResolution: plugins.typescript.ModuleResolutionKind.NodeNext,
lib: ['lib.dom.d.ts', 'lib.es2022.d.ts'],
noImplicitAny: false, // Allow implicit any by default
esModuleInterop: true,
useDefineForClassFields: false,
verbatimModuleSyntax: true,
baseUrl: './',
};
/**
* TsBuild class for handling TypeScript compilation
*/
export class TsBuild {
private fileNames: string[] = [];
private options: plugins.typescript.CompilerOptions;
private argvArg?: any;
private taskInfo?: any;
/**
* Create a new TsBuild instance
*/
constructor(
fileNames: string[] = [],
customOptions: CompilerOptions = {},
argvArg?: any,
taskInfo?: any
) {
this.fileNames = fileNames;
this.argvArg = argvArg;
this.taskInfo = taskInfo;
this.options = this.mergeCompilerOptions(customOptions, argvArg);
}
/**
* Helper function to read and process tsconfig.json
*/
private getTsConfigOptions(): CompilerOptions {
let tsconfig: any;
// Try to read tsconfig.json, but don't fail if it doesn't exist
try {
tsconfig = plugins.smartfile.fs.toObjectSync(plugins.path.join(paths.cwd, 'tsconfig.json'));
} catch (error) {
// tsconfig.json doesn't exist or is invalid - use defaults
return {};
}
if (!tsconfig || !tsconfig.compilerOptions) {
return {};
}
// Start by copying ALL compiler options from tsconfig.json
const returnObject: CompilerOptions = { ...tsconfig.compilerOptions };
// Apply special transformations for string-to-enum conversions
// Process target (convert string to enum)
if (tsconfig.compilerOptions.target && typeof tsconfig.compilerOptions.target === 'string') {
const targetKey = tsconfig.compilerOptions.target.toUpperCase();
if (targetKey in plugins.typescript.ScriptTarget) {
returnObject.target = plugins.typescript.ScriptTarget[targetKey as keyof typeof plugins.typescript.ScriptTarget];
}
}
// Process module (convert string to enum)
if (tsconfig.compilerOptions.module && typeof tsconfig.compilerOptions.module === 'string') {
const moduleKey = tsconfig.compilerOptions.module.toUpperCase();
if (moduleKey in plugins.typescript.ModuleKind) {
returnObject.module = plugins.typescript.ModuleKind[moduleKey as keyof typeof plugins.typescript.ModuleKind];
} else if (moduleKey === 'NODENEXT') {
returnObject.module = plugins.typescript.ModuleKind.NodeNext;
}
}
// Process moduleResolution (convert string to enum)
if (tsconfig.compilerOptions.moduleResolution && typeof tsconfig.compilerOptions.moduleResolution === 'string') {
const moduleResolutionKey = tsconfig.compilerOptions.moduleResolution.toUpperCase();
if (moduleResolutionKey in plugins.typescript.ModuleResolutionKind) {
returnObject.moduleResolution = plugins.typescript.ModuleResolutionKind[
moduleResolutionKey as keyof typeof plugins.typescript.ModuleResolutionKind
];
} else if (moduleResolutionKey === 'NODENEXT') {
returnObject.moduleResolution = plugins.typescript.ModuleResolutionKind.NodeNext;
}
}
// Apply path transformations (ts_ → dist_ts_)
if (tsconfig.compilerOptions.paths) {
returnObject.paths = { ...tsconfig.compilerOptions.paths };
for (const path of Object.keys(returnObject.paths)) {
if (Array.isArray(returnObject.paths[path]) && returnObject.paths[path].length > 0) {
returnObject.paths[path][0] = returnObject.paths[path][0].replace('./ts_', './dist_ts_');
}
}
}
return returnObject;
}
/**
* Returns critical default options that should not be overridden by tsconfig.json
* These options are essential for tsbuild's functionality and build integrity
*/
private getCriticalDefaults(): CompilerOptions {
return {
outDir: 'dist_ts/', // Required for path transformation logic
noEmitOnError: true, // Build integrity - prevent broken builds
declaration: true, // Library consumers depend on .d.ts files
emitDecoratorMetadata: true, // Required for dependency injection frameworks
inlineSourceMap: true, // Consistent debugging experience
};
}
/**
* Process command line arguments and return applicable compiler options
*/
private getCommandLineOptions(argvArg?: any): CompilerOptions {
if (!argvArg) return {};
const options: CompilerOptions = {};
if (argvArg.skiplibcheck) {
options.skipLibCheck = true;
}
// Changed behavior: --disallowimplicitany instead of --allowimplicitany
if (argvArg.disallowimplicitany) {
options.noImplicitAny = true;
}
if (argvArg.commonjs) {
options.module = plugins.typescript.ModuleKind.CommonJS;
options.moduleResolution = plugins.typescript.ModuleResolutionKind.NodeJs;
}
return options;
}
/**
* Merges compilerOptions with the default compiler options
*
* Merge order (later overwrites earlier):
* 1. compilerOptionsDefault - Base defaults for all options
* 2. getTsConfigOptions() - User's tsconfig.json (all options)
* 3. getCriticalDefaults() - Protected options that shouldn't be overridden by tsconfig.json
* 4. customTsOptions - Programmatic options (can override critical defaults)
* 5. getCommandLineOptions() - CLI flags (highest priority)
*/
public mergeCompilerOptions(
customTsOptions: CompilerOptions = {},
argvArg?: any
): CompilerOptions {
// create merged options
const mergedOptions: CompilerOptions = {
...compilerOptionsDefault, // 1. All defaults
...this.getTsConfigOptions(), // 2. User's tsconfig.json (all options)
...this.getCriticalDefaults(), // 3. Protected overrides
...customTsOptions, // 4. Programmatic options
...this.getCommandLineOptions(argvArg), // 5. CLI flags (highest priority)
};
return mergedOptions;
}
/**
* Helper function to process TypeScript diagnostics and return error summary
*/
private processDiagnostics(diagnostics: readonly plugins.typescript.Diagnostic[]): IErrorSummary {
const errorsByFile: Record<string, plugins.typescript.Diagnostic[]> = {};
const generalErrors: plugins.typescript.Diagnostic[] = [];
// Categorize diagnostics
diagnostics.forEach((diagnostic) => {
if (diagnostic.file) {
const fileName = diagnostic.file.fileName;
if (!errorsByFile[fileName]) {
errorsByFile[fileName] = [];
}
errorsByFile[fileName].push(diagnostic);
} else {
generalErrors.push(diagnostic);
}
});
return {
errorsByFile,
generalErrors,
totalErrors: diagnostics.length,
totalFiles: Object.keys(errorsByFile).length
};
}
/**
* Helper function to display error summary
*/
private displayErrorSummary(errorSummary: IErrorSummary): void {
if (errorSummary.totalErrors === 0) {
return;
}
const { errorsByFile, generalErrors, totalErrors, totalFiles } = errorSummary;
// Print error summary header
console.log('\n' + '='.repeat(80));
console.log(`❌ Found ${totalErrors} error${totalErrors !== 1 ? 's' : ''} in ${totalFiles} file${totalFiles !== 1 ? 's' : ''}:`);
console.log('='.repeat(80));
// Color codes for error formatting
const colors = {
reset: '\x1b[0m',
red: '\x1b[31m',
yellow: '\x1b[33m',
cyan: '\x1b[36m',
white: '\x1b[37m',
brightRed: '\x1b[91m'
};
// Print file-specific errors
Object.entries(errorsByFile).forEach(([fileName, fileErrors]) => {
// Show relative path if possible for cleaner output
const displayPath = fileName.replace(process.cwd(), '').replace(/^\//, '');
console.log(`\n${colors.cyan}File: ${displayPath} ${colors.yellow}(${fileErrors.length} error${fileErrors.length !== 1 ? 's' : ''})${colors.reset}`);
console.log('-'.repeat(80));
fileErrors.forEach((diagnostic) => {
if (diagnostic.file && diagnostic.start !== undefined) {
const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
const message = plugins.typescript.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
const errorCode = diagnostic.code ? `TS${diagnostic.code}` : 'Error';
console.log(`${colors.white}Line ${line + 1}, Col ${character + 1}${colors.reset}: ${colors.brightRed}${errorCode}${colors.reset} - ${message}`);
// Try to show the code snippet if possible
try {
const lineContent = diagnostic.file.text.split('\n')[line];
if (lineContent) {
// Show the line of code
console.log(` ${lineContent.trimRight()}`);
// Show the error position indicator
const indicator = ' '.repeat(character) + `${colors.red}^${colors.reset}`;
console.log(` ${indicator}`);
}
} catch (e) {
// Failed to get source text, skip showing the code snippet
}
}
});
});
// Print general errors
if (generalErrors.length > 0) {
console.log(`\n${colors.yellow}General Errors:${colors.reset}`);
console.log('-'.repeat(80));
generalErrors.forEach((diagnostic) => {
const message = plugins.typescript.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
const errorCode = diagnostic.code ? `TS${diagnostic.code}` : 'Error';
console.log(`${colors.brightRed}${errorCode}${colors.reset}: ${message}`);
});
}
console.log('\n' + '='.repeat(80) + '\n');
}
/**
* Creates a TypeScript program from file names and options
*/
private createProgram(
options: plugins.typescript.CompilerOptions = this.options
): plugins.typescript.Program {
return plugins.typescript.createProgram(this.fileNames, options);
}
/**
* Set file names to be compiled
*/
public setFileNames(fileNames: string[]): void {
this.fileNames = fileNames;
}
/**
* Set compiler options
*/
public setOptions(options: CompilerOptions): void {
this.options = { ...this.options, ...options };
}
/**
* The main compiler function that compiles the files and returns error summary
*/
public async compileWithErrorTracking(): Promise<{ emittedFiles: any[], errorSummary: IErrorSummary }> {
if (this.options.skipLibCheck) {
if (this.argvArg?.confirmskiplibcheck) {
console.log('\n⚠ WARNING ⚠️');
console.log('You are skipping libcheck... Is that really wanted?');
console.log('Continuing in 5 seconds...\n');
await plugins.smartdelay.delayFor(5000);
} else {
// No delay by default; keep a short note unless in quiet/json modes
if (!this.argvArg?.quiet && !this.argvArg?.json) {
console.log('⚠️ skipLibCheck enabled; use --confirmskiplibcheck to pause with warning.');
}
}
}
// Enhanced logging with task info
const startTime = Date.now();
if (this.taskInfo) {
const { taskNumber, totalTasks, sourcePattern, destDir, fileCount } = this.taskInfo;
const relativeDestDir = destDir.replace(process.cwd(), '').replace(/^\//, '');
console.log(`\n🔨 [${taskNumber}/${totalTasks}] Compiling ${fileCount} file${fileCount !== 1 ? 's' : ''} from ${sourcePattern}`);
console.log(` 📁 Output: ${relativeDestDir}`);
} else {
console.log(`🔨 Compiling ${this.fileNames.length} files...`);
}
const done = plugins.smartpromise.defer<{ emittedFiles: any[], errorSummary: IErrorSummary }>();
const program = this.createProgram();
// Check for pre-emit diagnostics first
const preEmitDiagnostics = plugins.typescript.getPreEmitDiagnostics(program);
const preEmitErrorSummary = this.processDiagnostics(preEmitDiagnostics);
// Only continue to emit phase if no pre-emit errors
if (preEmitErrorSummary.totalErrors > 0) {
this.displayErrorSummary(preEmitErrorSummary);
console.error('\n❌ TypeScript pre-emit checks failed. Please fix the issues listed above before proceeding.');
console.error(' Type errors must be resolved before the compiler can emit output files.\n');
// Return error summary instead of exiting to allow final summary display
done.resolve({ emittedFiles: [], errorSummary: preEmitErrorSummary });
return done.promise;
}
// If no pre-emit errors, proceed with emit
const emitResult = program.emit();
const emitErrorSummary = this.processDiagnostics(emitResult.diagnostics);
// Combine error summaries
const combinedErrorSummary: IErrorSummary = {
errorsByFile: { ...preEmitErrorSummary.errorsByFile, ...emitErrorSummary.errorsByFile },
generalErrors: [...preEmitErrorSummary.generalErrors, ...emitErrorSummary.generalErrors],
totalErrors: preEmitErrorSummary.totalErrors + emitErrorSummary.totalErrors,
totalFiles: Object.keys({ ...preEmitErrorSummary.errorsByFile, ...emitErrorSummary.errorsByFile }).length
};
const exitCode = emitResult.emitSkipped ? 1 : 0;
if (exitCode === 0) {
const endTime = Date.now();
const duration = endTime - startTime;
if (this.taskInfo) {
const { taskNumber, totalTasks } = this.taskInfo;
console.log(`✅ [${taskNumber}/${totalTasks}] Task completed in ${duration}ms`);
} else {
console.log(`✅ TypeScript emit succeeded! (${duration}ms)`);
}
// Get count of emitted files by type
const jsFiles = emitResult.emittedFiles?.filter(f => f.endsWith('.js')).length || 0;
const dtsFiles = emitResult.emittedFiles?.filter(f => f.endsWith('.d.ts')).length || 0;
const mapFiles = emitResult.emittedFiles?.filter(f => f.endsWith('.map')).length || 0;
// If we have emitted files, show a summary
if (emitResult.emittedFiles && emitResult.emittedFiles.length > 0) {
console.log(` 📄 Generated ${emitResult.emittedFiles.length} files: ${jsFiles} .js, ${dtsFiles} .d.ts, ${mapFiles} source maps`);
}
done.resolve({ emittedFiles: emitResult.emittedFiles || [], errorSummary: combinedErrorSummary });
} else {
this.displayErrorSummary(combinedErrorSummary);
console.error('\n❌ TypeScript emit failed. Please investigate the errors listed above!');
console.error(' No output files have been generated.\n');
// Do not exit here; return error summary so caller can decide
done.resolve({ emittedFiles: [], errorSummary: combinedErrorSummary });
return done.promise;
}
return done.promise;
}
/**
* The main compiler function that compiles the files
*/
public async compile(): Promise<any[]> {
if (this.options.skipLibCheck) {
if (this.argvArg?.confirmskiplibcheck) {
console.log('\n⚠ WARNING ⚠️');
console.log('You are skipping libcheck... Is that really wanted?');
console.log('Continuing in 5 seconds...\n');
await plugins.smartdelay.delayFor(5000);
} else {
if (!this.argvArg?.quiet && !this.argvArg?.json) {
console.log('⚠️ skipLibCheck enabled; use --confirmskiplibcheck to pause with warning.');
}
}
}
console.log(`🔨 Compiling ${this.fileNames.length} files...`);
const done = plugins.smartpromise.defer<any[]>();
const program = this.createProgram();
// Check for pre-emit diagnostics first
const preEmitDiagnostics = plugins.typescript.getPreEmitDiagnostics(program);
const preEmitErrorSummary = this.processDiagnostics(preEmitDiagnostics);
// Only continue to emit phase if no pre-emit errors
if (preEmitErrorSummary.totalErrors > 0) {
this.displayErrorSummary(preEmitErrorSummary);
console.error('\n❌ TypeScript pre-emit checks failed. Please fix the issues listed above before proceeding.');
console.error(' Type errors must be resolved before the compiler can emit output files.\n');
// Throw instead of exiting to keep library pure
throw new Error('TypeScript pre-emit checks failed.');
}
// If no pre-emit errors, proceed with emit
const emitResult = program.emit();
const emitErrorSummary = this.processDiagnostics(emitResult.diagnostics);
const exitCode = emitResult.emitSkipped ? 1 : 0;
if (exitCode === 0) {
console.log('\n✅ TypeScript emit succeeded!');
// Get count of emitted files by type
const jsFiles = emitResult.emittedFiles?.filter(f => f.endsWith('.js')).length || 0;
const dtsFiles = emitResult.emittedFiles?.filter(f => f.endsWith('.d.ts')).length || 0;
const mapFiles = emitResult.emittedFiles?.filter(f => f.endsWith('.map')).length || 0;
// If we have emitted files, show a summary
if (emitResult.emittedFiles && emitResult.emittedFiles.length > 0) {
console.log(` Generated ${emitResult.emittedFiles.length} files: ${jsFiles} .js, ${dtsFiles} .d.ts, ${mapFiles} source maps`);
}
done.resolve(emitResult.emittedFiles);
} else {
this.displayErrorSummary(emitErrorSummary);
console.error('\n❌ TypeScript emit failed. Please investigate the errors listed above!');
console.error(' No output files have been generated.\n');
// Throw instead of exiting to keep library pure
throw new Error('TypeScript emit failed.');
}
return done.promise;
}
/**
* Function to check if files can be emitted without actually emitting them
*/
public async checkEmit(): Promise<boolean> {
const fileCount = this.fileNames.length;
console.log(`\n🔍 Checking if ${fileCount} file${fileCount !== 1 ? 's' : ''} can be emitted...`);
// Create a program with noEmit option
const program = this.createProgram({
...this.options,
noEmit: true
});
// Check for pre-emit diagnostics
const preEmitDiagnostics = plugins.typescript.getPreEmitDiagnostics(program);
const preEmitErrorSummary = this.processDiagnostics(preEmitDiagnostics);
// Run the emit phase but with noEmit: true to check for emit errors without producing files
const emitResult = program.emit(undefined, undefined, undefined, true);
const emitErrorSummary = this.processDiagnostics(emitResult.diagnostics);
// Combine error summaries
const combinedErrorSummary: IErrorSummary = {
errorsByFile: { ...preEmitErrorSummary.errorsByFile, ...emitErrorSummary.errorsByFile },
generalErrors: [...preEmitErrorSummary.generalErrors, ...emitErrorSummary.generalErrors],
totalErrors: preEmitErrorSummary.totalErrors + emitErrorSummary.totalErrors,
totalFiles: Object.keys({ ...preEmitErrorSummary.errorsByFile, ...emitErrorSummary.errorsByFile }).length
};
const success = combinedErrorSummary.totalErrors === 0 && !emitResult.emitSkipped;
if (success) {
console.log('\n✅ TypeScript emit check passed! All files can be emitted successfully.');
console.log(` ${fileCount} file${fileCount !== 1 ? 's' : ''} ${fileCount !== 1 ? 'are' : 'is'} ready to be compiled.\n`);
} else {
this.displayErrorSummary(combinedErrorSummary);
console.error('\n❌ TypeScript emit check failed. Please fix the issues listed above.');
console.error(' The compilation cannot proceed until these errors are resolved.\n');
}
return success;
}
/**
* Function to check TypeScript files for type errors without emission
*/
public async checkTypes(): Promise<boolean> {
const fileCount = this.fileNames.length;
console.log(`\n🔍 Type checking ${fileCount} TypeScript file${fileCount !== 1 ? 's' : ''}...`);
// Create a program with noEmit option explicitly set
const program = this.createProgram({
...this.options,
noEmit: true
});
// Check for type errors
const diagnostics = plugins.typescript.getPreEmitDiagnostics(program);
const errorSummary = this.processDiagnostics(diagnostics);
// Set success flag
const success = errorSummary.totalErrors === 0;
if (success) {
console.log('\n✅ TypeScript type check passed! No type errors found.');
console.log(` All ${fileCount} file${fileCount !== 1 ? 's' : ''} passed type checking successfully.\n`);
} else {
this.displayErrorSummary(errorSummary);
console.error('\n❌ TypeScript type check failed. Please fix the type errors listed above.');
console.error(' The type checker found issues that need to be resolved.\n');
}
return success;
}
}
/**
* Merges compilerOptions with the default compiler options (backward compatibility)
*/
export const mergeCompilerOptions = (
customTsOptions: CompilerOptions,
argvArg?: any
): CompilerOptions => {
const tsBuild = new TsBuild();
return tsBuild.mergeCompilerOptions(customTsOptions, argvArg);
};
/**
* The internal main compiler function that compiles the files (backward compatibility)
*/
export const compiler = async (
fileNames: string[],
options: plugins.typescript.CompilerOptions,
argvArg?: any
): Promise<any[]> => {
const tsBuild = new TsBuild(fileNames, options, argvArg);
return tsBuild.compile();
};
/**
* Function to check if a TypeScript file can be emitted without actually emitting it (backward compatibility)
*/
export const emitCheck = async (
fileNames: string[],
options: plugins.typescript.CompilerOptions = {},
argvArg?: any
): Promise<boolean> => {
const tsBuild = new TsBuild(fileNames, options, argvArg);
return tsBuild.checkEmit();
};
/**
* Function to check TypeScript files for type errors without emission (backward compatibility)
*/
export const checkTypes = async (
fileNames: string[],
options: plugins.typescript.CompilerOptions = {},
argvArg?: any
): Promise<boolean> => {
const tsBuild = new TsBuild(fileNames, options, argvArg);
return tsBuild.checkTypes();
};

View File

@@ -9,7 +9,7 @@ export const runCli = async () => {
* the standard task compiles anything in ts/ directory to dist directory
*/
tsbuildCli.standardCommand().subscribe(async (argvArg) => {
tsbuild.compileGlobStringObject(
await tsbuild.compileGlobStringObject(
{
'./ts/**/*.ts': './dist_ts',
},
@@ -17,6 +17,10 @@ export const runCli = async () => {
process.cwd(),
argvArg
);
const summary = (argvArg as any)?.__tsbuildFinalErrorSummary;
if (summary && summary.totalErrors > 0) {
process.exit(1);
}
});
/**
@@ -30,6 +34,94 @@ export const runCli = async () => {
compilationCommandObject[`./${directory}/**/*.ts`] = `./dist_${directory}`;
}
await tsbuild.compileGlobStringObject(compilationCommandObject, {}, process.cwd(), argvArg);
const summary = (argvArg as any)?.__tsbuildFinalErrorSummary;
if (summary && summary.totalErrors > 0) {
process.exit(1);
}
});
/**
* the emitcheck command checks if a TypeScript file can be emitted without actually emitting it
*/
tsbuildCli.addCommand('emitcheck').subscribe(async (argvArg) => {
const patterns = argvArg._.slice(1); // Remove the first element which is 'emitcheck'
if (patterns.length === 0) {
console.error('\n❌ Error: Please provide at least one TypeScript file path or glob pattern');
console.error(' Usage: tsbuild emitcheck <file_or_glob_pattern> [additional_patterns ...]\n');
console.error(' Example: tsbuild emitcheck "src/**/*.ts" "test/**/*.ts"\n');
process.exit(1);
}
const cwd = process.cwd();
let allFiles: string[] = [];
// Process each pattern - could be a direct file path or a glob pattern
for (const pattern of patterns) {
// Check if the pattern looks like a glob pattern
if (pattern.includes('*') || pattern.includes('{') || pattern.includes('?')) {
// Handle as glob pattern
console.log(`Processing glob pattern: ${pattern}`);
try {
const matchedFiles = await plugins.smartfile.fs.listFileTree(cwd, pattern);
// Ensure matchedFiles contains only strings
const stringMatchedFiles = Array.isArray(matchedFiles)
? matchedFiles.filter((item): item is string => typeof item === 'string')
: [];
if (stringMatchedFiles.length === 0) {
console.warn(`⚠️ Warning: No files matched the pattern '${pattern}'`);
} else {
console.log(`📂 Found ${stringMatchedFiles.length} files matching pattern '${pattern}'`);
// Transform to absolute paths
const absoluteMatchedFiles = plugins.smartpath.transform.toAbsolute(
stringMatchedFiles,
cwd
) as string[];
// Add to the list of all files to check
allFiles = allFiles.concat(absoluteMatchedFiles);
}
} catch (err) {
console.error(`❌ Error processing glob pattern '${pattern}': ${err}`);
}
} else {
// Handle as direct file path
const filePath = plugins.path.isAbsolute(pattern)
? pattern
: plugins.path.join(cwd, pattern);
try {
await plugins.smartfile.fs.fileExists(filePath);
allFiles.push(filePath);
} catch (err) {
console.error(`❌ Error: File not found: ${filePath}`);
process.exit(1);
}
}
}
// Filter to only TypeScript files
allFiles = allFiles.filter(file => file.endsWith('.ts') || file.endsWith('.tsx'));
if (allFiles.length === 0) {
console.error('\n❌ Error: No TypeScript files found to check');
console.error(' Please verify your file paths or glob patterns.\n');
process.exit(1);
}
console.log(`\n🔎 Found ${allFiles.length} TypeScript file${allFiles.length !== 1 ? 's' : ''} to check`);
// Process compiler options
const compilerOptions = tsbuild.mergeCompilerOptions({}, argvArg);
// Run emit check
const success = await tsbuild.emitCheck(allFiles, compilerOptions, argvArg);
// Exit with appropriate code
process.exit(success ? 0 : 1);
});
/**
@@ -80,12 +172,167 @@ export const runCli = async () => {
const compilationCommandObject: { [key: string]: string } = {};
console.log(`compiling in this order:`);
console.log(sortedTsFolders);
const folderCount = sortedTsFolders.length;
console.log(`\n📂 TypeScript Folder Compilation Plan (${folderCount} folder${folderCount !== 1 ? 's' : ''})`);
console.log('┌' + '─'.repeat(60) + '┐');
console.log('│ 🔄 Compilation Order │');
console.log('├' + '─'.repeat(60) + '┤');
sortedTsFolders.forEach((folder, index) => {
const prefix = index === folderCount - 1 ? '└─' : '├─';
const position = `${index + 1}/${folderCount}`;
console.log(`${prefix} ${position.padStart(5)} ${folder.padEnd(46)}`);
});
console.log('└' + '─'.repeat(60) + '┘\n');
for (const tsFolder of sortedTsFolders) {
compilationCommandObject[`./${tsFolder}/**/*.ts`] = `./dist_${tsFolder}`;
}
await tsbuild.compileGlobStringObject(compilationCommandObject, {}, process.cwd(), argvArg);
const summary = (argvArg as any)?.__tsbuildFinalErrorSummary;
if (summary && summary.totalErrors > 0) {
process.exit(1);
}
});
/**
* the check command checks TypeScript files against a glob pattern without emitting them
*/
tsbuildCli.addCommand('check').subscribe(async (argvArg) => {
const patterns = argvArg._.slice(1); // Remove the first element which is 'check'
// If no patterns provided, default to checking ts/**/* and then test/**/*
if (patterns.length === 0) {
console.log('\n🔬 Running default type checking sequence...\n');
// First check ts/**/* without skiplibcheck
console.log('📂 Checking ts/**/* files...');
const tsFiles = await plugins.smartfile.fs.listFileTree(process.cwd(), 'ts/**/*.ts');
const tsTsFiles = Array.isArray(tsFiles)
? tsFiles.filter((item): item is string => typeof item === 'string')
: [];
if (tsTsFiles.length > 0) {
console.log(` Found ${tsTsFiles.length} TypeScript files in ts/`);
const tsAbsoluteFiles = plugins.smartpath.transform.toAbsolute(
tsTsFiles,
process.cwd()
) as string[];
const tsCompilerOptions = tsbuild.mergeCompilerOptions({}, argvArg);
const tsSuccess = await tsbuild.checkTypes(tsAbsoluteFiles, tsCompilerOptions, argvArg);
if (!tsSuccess) {
console.error('❌ Type checking failed for ts/**/*');
process.exit(1);
}
console.log('✅ Type checking passed for ts/**/*\n');
} else {
console.log(' No TypeScript files found in ts/\n');
}
// Then check test/**/* with skiplibcheck
console.log('📂 Checking test/**/* files with --skiplibcheck...');
const testFiles = await plugins.smartfile.fs.listFileTree(process.cwd(), 'test/**/*.ts');
const testTsFiles = Array.isArray(testFiles)
? testFiles.filter((item): item is string => typeof item === 'string')
: [];
if (testTsFiles.length > 0) {
console.log(` Found ${testTsFiles.length} TypeScript files in test/`);
const testAbsoluteFiles = plugins.smartpath.transform.toAbsolute(
testTsFiles,
process.cwd()
) as string[];
// Create new argvArg with skiplibcheck for test files
const testArgvArg = { ...argvArg, skiplibcheck: true };
const testCompilerOptions = tsbuild.mergeCompilerOptions({}, testArgvArg);
const testSuccess = await tsbuild.checkTypes(testAbsoluteFiles, testCompilerOptions, testArgvArg);
if (!testSuccess) {
console.error('❌ Type checking failed for test/**/*');
process.exit(1);
}
console.log('✅ Type checking passed for test/**/*\n');
} else {
console.log(' No TypeScript files found in test/\n');
}
console.log('✅ All default type checks passed!\n');
process.exit(0);
}
const cwd = process.cwd();
let allFiles: string[] = [];
// Process each pattern - could be a direct file path or a glob pattern
for (const pattern of patterns) {
// Check if the pattern looks like a glob pattern
if (pattern.includes('*') || pattern.includes('{') || pattern.includes('?')) {
// Handle as glob pattern
console.log(`Processing glob pattern: ${pattern}`);
try {
const matchedFiles = await plugins.smartfile.fs.listFileTree(cwd, pattern);
// Ensure matchedFiles contains only strings
const stringMatchedFiles = Array.isArray(matchedFiles)
? matchedFiles.filter((item): item is string => typeof item === 'string')
: [];
if (stringMatchedFiles.length === 0) {
console.warn(`⚠️ Warning: No files matched the pattern '${pattern}'`);
} else {
console.log(`📂 Found ${stringMatchedFiles.length} files matching pattern '${pattern}'`);
// Transform to absolute paths
const absoluteMatchedFiles = plugins.smartpath.transform.toAbsolute(
stringMatchedFiles,
cwd
) as string[];
// Add to the list of all files to check
allFiles = allFiles.concat(absoluteMatchedFiles);
}
} catch (err) {
console.error(`❌ Error processing glob pattern '${pattern}': ${err}`);
}
} else {
// Handle as direct file path
const filePath = plugins.path.isAbsolute(pattern)
? pattern
: plugins.path.join(cwd, pattern);
try {
await plugins.smartfile.fs.fileExists(filePath);
allFiles.push(filePath);
} catch (err) {
console.error(`❌ Error: File not found: ${filePath}`);
process.exit(1);
}
}
}
// Filter to only TypeScript files
allFiles = allFiles.filter(file => file.endsWith('.ts') || file.endsWith('.tsx'));
if (allFiles.length === 0) {
console.error('\n❌ Error: No TypeScript files found to check');
console.error(' Please verify your file paths or glob patterns.\n');
process.exit(1);
}
console.log(`\n🔎 Found ${allFiles.length} TypeScript file${allFiles.length !== 1 ? 's' : ''} to check`);
// Process compiler options
const compilerOptions = tsbuild.mergeCompilerOptions({}, argvArg);
// Run type check without emitting
const success = await tsbuild.checkTypes(allFiles, compilerOptions, argvArg);
// Exit with appropriate code
process.exit(success ? 0 : 1);
});
tsbuildCli.startParse();

View File

@@ -1,10 +1,35 @@
import * as plugins from './plugins.js';
import type { CompilerOptions, ScriptTarget, ModuleKind } from 'typescript';
import { compiler, mergeCompilerOptions } from './tsbuild.classes.compiler.js';
import { compiler, mergeCompilerOptions, emitCheck, checkTypes } from './tsbuild.classes.tsbuild.js';
export type { CompilerOptions, ScriptTarget, ModuleKind };
export * from './tsbuild.classes.compiler.js';
export * from './tsbuild.classes.tsbuild.js';
/**
* Interface for task information
*/
export interface ITaskInfo {
taskNumber: number;
totalTasks: number;
sourcePattern: string;
destDir: string;
fileCount: number;
}
/**
* compile an array of absolute file paths with error tracking
*/
export let compileFileArrayWithErrorTracking = async (
fileStringArrayArg: string[],
compilerOptionsArg: CompilerOptions = {},
argvArg?: any,
taskInfo?: ITaskInfo
): Promise<{ emittedFiles: any[], errorSummary: import('./tsbuild.classes.tsbuild.js').IErrorSummary }> => {
const { TsBuild } = await import('./tsbuild.classes.tsbuild.js');
const tsBuild = new TsBuild(fileStringArrayArg, compilerOptionsArg, argvArg, taskInfo);
return tsBuild.compileWithErrorTracking();
};
/**
* compile am array of absolute file paths
@@ -17,6 +42,75 @@ export let compileFileArray = (
return compiler(fileStringArrayArg, mergeCompilerOptions(compilerOptionsArg, argvArg), argvArg);
};
/**
* Helper function to merge error summaries
*/
function mergeErrorSummaries(summaries: import('./tsbuild.classes.tsbuild.js').IErrorSummary[]): import('./tsbuild.classes.tsbuild.js').IErrorSummary {
const mergedErrorsByFile: Record<string, plugins.typescript.Diagnostic[]> = {};
const mergedGeneralErrors: plugins.typescript.Diagnostic[] = [];
let totalErrors = 0;
summaries.forEach(summary => {
// Merge errors by file
Object.entries(summary.errorsByFile).forEach(([fileName, errors]) => {
if (!mergedErrorsByFile[fileName]) {
mergedErrorsByFile[fileName] = [];
}
mergedErrorsByFile[fileName] = mergedErrorsByFile[fileName].concat(errors);
});
// Merge general errors
mergedGeneralErrors.push(...summary.generalErrors);
totalErrors += summary.totalErrors;
});
return {
errorsByFile: mergedErrorsByFile,
generalErrors: mergedGeneralErrors,
totalErrors,
totalFiles: Object.keys(mergedErrorsByFile).length
};
}
/**
* Helper function to display final compilation summary
*/
function displayFinalErrorSummary(errorSummary: import('./tsbuild.classes.tsbuild.js').IErrorSummary): void {
if (errorSummary.totalErrors === 0) {
console.log('\n📊 \x1b[32mCompilation Summary: All tasks completed successfully! ✅\x1b[0m\n');
return;
}
const colors = {
reset: '\x1b[0m',
red: '\x1b[31m',
yellow: '\x1b[33m',
cyan: '\x1b[36m',
brightRed: '\x1b[91m',
brightYellow: '\x1b[93m'
};
console.log('\n' + '='.repeat(80));
console.log(`📊 ${colors.brightYellow}Final Compilation Summary${colors.reset}`);
console.log('='.repeat(80));
if (errorSummary.totalFiles > 0) {
console.log(`${colors.brightRed}❌ Files with errors (${errorSummary.totalFiles}):${colors.reset}`);
Object.entries(errorSummary.errorsByFile).forEach(([fileName, errors]) => {
const displayPath = fileName.replace(process.cwd(), '').replace(/^\//, '');
console.log(` ${colors.red}${colors.reset} ${colors.cyan}${displayPath}${colors.reset} ${colors.yellow}(${errors.length} error${errors.length !== 1 ? 's' : ''})${colors.reset}`);
});
}
if (errorSummary.generalErrors.length > 0) {
console.log(`${colors.brightRed}❌ General errors: ${errorSummary.generalErrors.length}${colors.reset}`);
}
console.log(`\n${colors.brightRed}Total: ${errorSummary.totalErrors} error${errorSummary.totalErrors !== 1 ? 's' : ''} across ${errorSummary.totalFiles} file${errorSummary.totalFiles !== 1 ? 's' : ''}${colors.reset}`);
console.log('='.repeat(80) + '\n');
}
/**
* compile advanced glob configurations
* @param globStringArrayArg a array of glob strings
@@ -31,13 +125,25 @@ export let compileGlobStringObject = async (
argvArg?: any
) => {
let compiledFiles: any[] = [];
const errorSummaries: import('./tsbuild.classes.tsbuild.js').IErrorSummary[] = [];
const totalTasks = Object.keys(globStringObjectArg).length;
let currentTask = 0;
// Log the compilation tasks in a nice format (skip for --quiet or --json)
const isQuiet = argvArg?.quiet === true;
const isJson = argvArg?.json === true;
if (!isQuiet && !isJson) {
console.log(`\n👷 TypeScript Compilation Tasks (${totalTasks} task${totalTasks !== 1 ? 's' : ''}):`);
Object.entries(globStringObjectArg).forEach(([source, dest]) => {
console.log(` 📂 ${source}${dest}`);
});
console.log('');
}
for (const keyArg in globStringObjectArg) {
// Type safety check for key
if (keyArg && typeof keyArg === 'string' && globStringObjectArg[keyArg]) {
console.log(
`TypeScript assignment: transpile from ${keyArg} to ${globStringObjectArg[keyArg]}`
);
// Get files matching the glob pattern
const fileTreeArray = await plugins.smartfile.fs.listFileTree(cwdArg, keyArg);
@@ -65,12 +171,53 @@ export let compileGlobStringObject = async (
outDir: destDir,
};
// Compile the files and correctly concat the results
// Fixed: removed duplicating compiledFiles in the concat operation
const newlyCompiledFiles = await compileFileArray(absoluteFilePathArray, updatedTsOptions, argvArg);
compiledFiles = compiledFiles.concat(newlyCompiledFiles);
// Compile with error tracking
currentTask++;
const taskInfo = {
taskNumber: currentTask,
totalTasks,
sourcePattern: keyArg,
destDir: globStringObjectArg[keyArg],
fileCount: absoluteFilePathArray.length
};
const result = await compileFileArrayWithErrorTracking(absoluteFilePathArray, updatedTsOptions, argvArg, taskInfo);
compiledFiles = compiledFiles.concat(result.emittedFiles);
errorSummaries.push(result.errorSummary);
}
}
// Display final error summary after all compilation tasks
const finalErrorSummary = mergeErrorSummaries(errorSummaries);
// Output summary based on mode
if (isJson) {
const result = {
success: finalErrorSummary.totalErrors === 0,
totals: {
errors: finalErrorSummary.totalErrors,
filesWithErrors: finalErrorSummary.totalFiles,
tasks: totalTasks,
},
errorsByFile: Object.fromEntries(
Object.entries(finalErrorSummary.errorsByFile).map(([file, diags]) => [
file,
diags.map(d => ({
code: d.code,
message: plugins.typescript.flattenDiagnosticMessageText(d.messageText as any, '\n'),
}))
])
),
};
console.log(JSON.stringify(result));
} else if (!isQuiet) {
displayFinalErrorSummary(finalErrorSummary);
}
// Attach summary to argvArg so CLI can decide exit behavior
if (argvArg && typeof argvArg === 'object') {
(argvArg as any).__tsbuildFinalErrorSummary = finalErrorSummary;
}
return compiledFiles;
};
};