Compare commits

..

56 Commits

Author SHA1 Message Date
aeaa957efa 4.0.0 2025-12-13 23:00:52 +00:00
e5fcbb9a09 BREAKING(structure): modernize internal structure and support unpacking 2025-12-13 22:59:58 +00:00
4ebc37fa5a 3.1.4 2025-12-13 11:44:20 +00:00
5bfe60927e fix(npmextra.json): update to new format 2025-12-13 11:44:03 +00:00
9e6b91b891 v3.1.3 2025-12-13 09:44:15 +00:00
e40e8f6a77 fix(npmextra): Align npmextra.json package name with package.json (@git.zone/tsbuild) 2025-12-13 09:44:15 +00:00
ebe7afce82 v3.1.2 2025-11-28 08:44:42 +00:00
27aa318054 fix(TsBuild): Set default TypeScript target to ESNext 2025-11-28 08:44:42 +00:00
9a071ce82f v3.1.1 2025-11-27 23:04:13 +00:00
ad227ded73 fix(compiler): Update default TypeScript target to ES2024 2025-11-27 23:04:13 +00:00
c66a941aaf v3.1.0 2025-11-17 12:25:31 +00:00
a3b58dda39 feat(tsbuild.classes): Update default TypeScript lib to lib.esnext.d.ts 2025-11-17 12:25:31 +00:00
4b29107130 v3.0.0 2025-11-17 11:57:32 +00:00
09adbc1965 BREAKING CHANGE(TsBuild): Stop forcing emitDecoratorMetadata in protected compiler defaults 2025-11-17 11:57:32 +00:00
7fb571d4f6 v2.7.3 2025-11-17 11:51:56 +00:00
6b32dced5a fix(tsbuild.classes): Remove duplicate emitDecoratorMetadata from default compiler options and centralize it in protected defaults 2025-11-17 11:51:56 +00:00
df39aa48d0 v2.7.2 2025-11-17 08:08:21 +00:00
a4863bc761 fix(compilerOptions): Remove experimentalDecorators and useDefineForClassFields from default TypeScript compiler options 2025-11-17 08:08:21 +00:00
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
29 changed files with 7669 additions and 4227 deletions

1
.gitignore vendored
View File

@@ -18,3 +18,4 @@ dist/
dist_*/ dist_*/
# custom # custom
.claude

View File

@@ -1,5 +1,197 @@
# Changelog # Changelog
## 2025-12-13 - 3.1.3 - fix(npmextra)
Align npmextra.json package name with package.json (@git.zone/tsbuild)
- Corrected npmPackagename in npmextra.json from "@gitzone/tsbuild" to "@git.zone/tsbuild" to match package.json and README
- Metadata-only change: no code or API behavior affected
## 2025-11-28 - 3.1.2 - fix(TsBuild)
Set default TypeScript target to ESNext
- Default compiler target changed from ScriptTarget.ES2024 to ScriptTarget.ESNext in ts/tsbuild.classes.tsbuild.ts
- Aligns the default compiler options with documentation and ensures use of the latest language features
## 2025-11-27 - 3.1.1 - fix(compiler)
Update default TypeScript target to ES2024
- Default compiler option 'target' changed from ScriptTarget.ESNext to ScriptTarget.ES2024 in ts/tsbuild.classes.tsbuild.ts
- Aligns emitted code to ES2024 language features by default
## 2025-11-17 - 3.1.0 - feat(tsbuild.classes)
Update default TypeScript lib to lib.esnext.d.ts
- Changed default compilerOptions.lib from ['lib.dom.d.ts', 'lib.es2022.d.ts'] to ['lib.dom.d.ts', 'lib.esnext.d.ts'] in compilerOptionsDefault.
- Allows newer ECMAScript/DOM features by default when compiling with tsbuild (affects emitted types and available globals).
- Behavioral default change only — no public API changes; callers can still override lib via tsconfig, programmatic options, or CLI.
## 2025-11-17 - 3.0.0 - BREAKING CHANGE(TsBuild)
Stop forcing emitDecoratorMetadata in protected compiler defaults
- Removed emitDecoratorMetadata from the set of protected/critical default compiler options.
- Projects that relied on tsbuild automatically enabling emitDecoratorMetadata (for DI frameworks like NestJS, TypeORM, Inversify) must now enable it explicitly in their tsconfig.json or via programmatic/CLI options.
- No other protected defaults were changed; outDir, noEmitOnError, declaration and inlineSourceMap remain enforced.
- Behavior is now more permissive: decorator metadata generation is controlled by user configuration rather than being forced by tsbuild.
## 2025-11-17 - 2.7.3 - fix(tsbuild.classes)
Remove duplicate emitDecoratorMetadata from default compiler options and centralize it in protected defaults
- Removed emitDecoratorMetadata from compilerOptionsDefault in ts/tsbuild.classes.tsbuild.ts to avoid duplicate configuration.
- emitDecoratorMetadata remains enforced via getCriticalDefaults(), ensuring decorator metadata support is protected from tsconfig.json overrides.
- Prevents inconsistencies during compiler option merging by centralizing the decorator-related setting.
## 2025-11-17 - 2.7.2 - fix(compilerOptions)
Remove experimentalDecorators and useDefineForClassFields from default TypeScript compiler options
- Removed experimentalDecorators from compilerOptionsDefault in ts/tsbuild.classes.tsbuild.ts
- Removed useDefineForClassFields from compilerOptionsDefault in ts/tsbuild.classes.tsbuild.ts
- Default compiler options now rely on TypeScript's upstream defaults for decorator and class field behavior
- If your project relies on these settings, re-enable them in your tsconfig.json or pass them via the programmatic API / CLI
## 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) ## 2025-03-17 - 2.2.7 - fix(compiler)
Improve diagnostic checking and error handling in the TypeScript compiler integration Improve diagnostic checking and error handling in the TypeScript compiler integration

View File

@@ -10,7 +10,7 @@
"gitscope": "gitzone", "gitscope": "gitzone",
"gitrepo": "tsbuild", "gitrepo": "tsbuild",
"description": "A tool for compiling TypeScript files using the latest nightly features, offering flexible APIs and a CLI for streamlined development.", "description": "A tool for compiling TypeScript files using the latest nightly features, offering flexible APIs and a CLI for streamlined development.",
"npmPackagename": "@gitzone/tsbuild", "npmPackagename": "@git.zone/tsbuild",
"license": "MIT", "license": "MIT",
"keywords": [ "keywords": [
"TypeScript", "TypeScript",

View File

@@ -1,6 +1,6 @@
{ {
"name": "@git.zone/tsbuild", "name": "@git.zone/tsbuild",
"version": "2.2.7", "version": "4.0.0",
"private": false, "private": false,
"description": "A tool for compiling TypeScript files using the latest nightly features, offering flexible APIs and a CLI for streamlined development.", "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", "main": "dist_ts/index.js",
@@ -10,8 +10,8 @@
"tsbuild": "./cli.js" "tsbuild": "./cli.js"
}, },
"scripts": { "scripts": {
"test": "tsrun test/test.ts", "test": "tstest test/test.ts --verbose",
"build": "node cli.ts.js --web --allowimplicitany", "build": "node cli.ts.js --web",
"buildDocs": "tsdoc" "buildDocs": "tsdoc"
}, },
"repository": { "repository": {
@@ -29,27 +29,28 @@
"development", "development",
"API" "API"
], ],
"author": "Lossless GmbH", "author": "Task Venture Capital GmbH",
"license": "MIT", "license": "MIT",
"bugs": { "bugs": {
"url": "https://code.foss.global/git.zone/tsbuild/issues" "url": "https://code.foss.global/git.zone/tsbuild/issues"
}, },
"homepage": "https://code.foss.global/git.zone/tsbuild#README", "homepage": "https://code.foss.global/git.zone/tsbuild#README",
"dependencies": { "dependencies": {
"@git.zone/tspublish": "^1.9.1", "@git.zone/tspublish": "^1.10.3",
"@push.rocks/early": "^4.0.4", "@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/smartdelay": "^3.0.5",
"@push.rocks/smartfile": "^11.1.5", "@push.rocks/smartfile": "^13.1.2",
"@push.rocks/smartlog": "^3.0.7", "@push.rocks/smartfs": "^1.2.0",
"@push.rocks/smartpath": "^5.0.18", "@push.rocks/smartlog": "^3.1.10",
"@push.rocks/smartpromise": "^4.2.2", "@push.rocks/smartpath": "^6.0.0",
"typescript": "5.7.3" "@push.rocks/smartpromise": "^4.2.3",
"typescript": "5.9.3"
}, },
"devDependencies": { "devDependencies": {
"@git.zone/tsrun": "^1.2.47", "@git.zone/tsrun": "^2.0.1",
"@push.rocks/tapbundle": "^5.5.6", "@git.zone/tstest": "^3.1.3",
"@types/node": "^22.12.0" "@types/node": "^25.0.1"
}, },
"files": [ "files": [
"ts/**/*", "ts/**/*",
@@ -65,5 +66,6 @@
], ],
"browserslist": [ "browserslist": [
"last 1 chrome versions" "last 1 chrome versions"
] ],
"packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39"
} }

8604
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,215 @@
# @git.zone/tsbuild - Project Memory
## Quick Reference
### Public API - Classes
1. **TsCompiler** - Main compilation class with compileFiles, compileGlob, checkTypes, checkEmit methods
2. **TsConfig** - TypeScript configuration management (tsconfig.json handling)
3. **TsPublishConfig** - TsPublish configuration (tspublish.json handling)
4. **TsUnpacker** - Output directory flattening
5. **FsHelpers** - Filesystem utilities (static methods)
6. **TsBuildCli** - CLI command handler
### 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
## Architecture - Modular Structure
### Module Organization
```
ts/
index.ts # Main entry, re-exports all modules
plugins.ts # Dependency imports only
mod_fs/
index.ts # exports
classes.fshelpers.ts # FsHelpers - static filesystem utilities
mod_config/
index.ts # exports
classes.tsconfig.ts # TsConfig - tsconfig.json handling
classes.tspublishconfig.ts # TsPublishConfig - tspublish.json handling
mod_unpack/
index.ts # exports
classes.tsunpacker.ts # TsUnpacker - output flattening
mod_compiler/
index.ts # exports
classes.tscompiler.ts # TsCompiler + legacy compatibility functions
mod_cli/
index.ts # exports
classes.tsbuildcli.ts # TsBuildCli - CLI command handler
```
### Class Responsibilities
**TsCompiler** (`mod_compiler/classes.tscompiler.ts`)
- Core compilation with `compileFiles()`, `compileGlob()`
- Type checking with `checkTypes()`, `checkEmit()`
- Configuration via TsConfig
- Automatic unpacking via TsUnpacker
**TsConfig** (`mod_config/classes.tsconfig.ts`)
- Load and parse tsconfig.json
- Merge configuration with priority order
- Protected defaults handling
**TsPublishConfig** (`mod_config/classes.tspublishconfig.ts`)
- Load and parse tspublish.json
- `shouldUnpack` property (default true)
- `order` property for tsfolders ordering
**TsUnpacker** (`mod_unpack/classes.tsunpacker.ts`)
- Detect nested output structure
- Flatten output directories
- Configurable via TsPublishConfig
**FsHelpers** (`mod_fs/classes.fshelpers.ts`)
- `listFilesWithGlob()` - Glob pattern file listing
- `extractSourceFolder()` - Pattern parsing
- File/directory existence checks
- Directory operations
**TsBuildCli** (`mod_cli/classes.tsbuildcli.ts`)
- All CLI commands registered
- Uses TsCompiler for compilation
## Configuration Priority (5 levels)
1. Default options (hardcoded in TsConfig)
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
- `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)
- Source Maps: Inline (no separate .map files)
- Declaration Files: ALWAYS generated (protected)
- Output: dist_ts/
- Implicit any: ALLOWED by default
- esModuleInterop: true
- verbatimModuleSyntax: true
## Error Handling
### Error Summary Structure
```typescript
interface IErrorSummary {
errorsByFile: Record<string, Diagnostic[]>
generalErrors: Diagnostic[]
totalErrors: number
totalFiles: number
}
```
### Compile Result Structure
```typescript
interface ICompileResult {
emittedFiles: string[]
errorSummary: IErrorSummary
}
```
## Dependencies Used
- @git.zone/tspublish@^1.10.3 - Module ordering
- @push.rocks/smartcli@^4.0.19 - CLI framework
- @push.rocks/smartfile@^13.1.2 - File content handling
- @push.rocks/smartfs@^1.2.0 - Filesystem operations
- @push.rocks/smartpath@^6.0.0 - Path transformation utilities
- @push.rocks/smartpromise@^4.2.3 - Promise utilities
- @push.rocks/smartdelay@^3.0.5 - Delay utilities
- typescript@5.9.3 - TypeScript compiler
### smartfs Usage
- File listing: `smartfs.directory(path).recursive().filter(pattern).list()`
- File existence: `smartfs.file(path).exists()`
- Directory existence: `smartfs.directory(path).exists()`
- FsHelpers wraps smartfs for glob pattern support
## Unpack Feature
When TypeScript compiles files that import from sibling directories, it creates nested output:
```
dist_ts_core/
ts_core/ ← nested output
ts_shared/ ← pulled-in dependency
```
The unpack feature automatically flattens this to:
```
dist_ts_core/
index.js ← flat
```
### Configuration
- Reads `tspublish.json` from source folder
- `unpack: true` (default if not present) → flatten output
- `unpack: false` → skip unpacking
### Implementation
- `TsUnpacker` class in `mod_unpack/classes.tsunpacker.ts`
- Called automatically after each successful compilation in `TsCompiler.compileGlob()`
## Special Behaviors
### tsfolders Command Ordering
1. Always: ts_interfaces first (if no tspublish.json)
2. Always: ts_shared second (if no tspublish.json)
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)
## Edge Cases
1. **Empty file list** - Returns [], no error
2. **Glob duplicates** - Files compile multiple times
3. **Non-existent files** - TypeScript "file not found" errors
4. **skipLibCheck warning** - 1-line default, 5s 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
- 3.1.4+ - Major restructuring with mod_* modules
- Full OO architecture with TsCompiler, TsConfig, TsPublishConfig, TsUnpacker, TsBuildCli, FsHelpers
- Backward compatibility maintained for legacy functions
- Automatic "unpack" feature for nested output directories
- Migrated filesystem operations from smartfile to smartfs

967
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

@@ -6,12 +6,12 @@ early.stop();
import { anExportedString } from './tocompile2.js'; import { anExportedString } from './tocompile2.js';
console.log(anExportedString); console.log(anExportedString);
class test2 { class test2 {
test = [];
constructor() { constructor() {
this.test = [];
console.log('hi'); console.log('hi');
} }
} }
const run = async () => { const run = async () => {
return 'hi'; return 'hi';
}; };
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidG9jb21waWxlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdG9jb21waWxlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDLENBQUM7QUFDcEIsT0FBTyxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsQ0FBQztBQUVyQixPQUFPLEtBQUssS0FBSyxNQUFNLG1CQUFtQixDQUFDO0FBRTNDLEtBQUssQ0FBQyxLQUFLLEVBQUUsQ0FBQztBQUNkLEtBQUssQ0FBQyxJQUFJLEVBQUUsQ0FBQztBQUViLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBQ25ELE9BQU8sQ0FBQyxHQUFHLENBQUMsZ0JBQWdCLENBQUMsQ0FBQztBQUU5QixNQUFNLEtBQUs7SUFFVDtRQURBLFNBQUksR0FBYSxFQUFFLENBQUM7UUFFbEIsT0FBTyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUNwQixDQUFDO0NBQ0Y7QUFFRCxNQUFNLEdBQUcsR0FBRyxLQUFLLElBQXFCLEVBQUU7SUFDdEMsT0FBTyxJQUFJLENBQUM7QUFDZCxDQUFDLENBQUMifQ== //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidG9jb21waWxlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdG9jb21waWxlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDLENBQUM7QUFDcEIsT0FBTyxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsQ0FBQztBQUVyQixPQUFPLEtBQUssS0FBSyxNQUFNLG1CQUFtQixDQUFDO0FBRTNDLEtBQUssQ0FBQyxLQUFLLEVBQUUsQ0FBQztBQUNkLEtBQUssQ0FBQyxJQUFJLEVBQUUsQ0FBQztBQUViLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBQ25ELE9BQU8sQ0FBQyxHQUFHLENBQUMsZ0JBQWdCLENBQUMsQ0FBQztBQUU5QixNQUFNLEtBQUs7SUFDVCxJQUFJLEdBQWEsRUFBRSxDQUFDO0lBQ3BCO1FBQ0UsT0FBTyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUNwQixDQUFDO0NBQ0Y7QUFFRCxNQUFNLEdBQUcsR0FBRyxLQUFLLElBQXFCLEVBQUU7SUFDdEMsT0FBTyxJQUFJLENBQUM7QUFDZCxDQUFDLENBQUMifQ==

View File

@@ -1,19 +1,21 @@
import { tap, expect, expectAsync } from '@push.rocks/tapbundle'; import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as tsbuild from '../ts/index.js'; import { TsCompiler } from '../ts/index.js';
let assetfiles: string[] = ['./test/assets/tocompile.ts', './test/assets/tocompile2.ts']; const assetfiles: string[] = ['./test/assets/tocompile.ts', './test/assets/tocompile2.ts'];
let assetfiles2 = { const assetfiles2 = {
'./test/assets/**/!(*.d.ts|*.js|output)': './test/assets/output', './test/assets/**/!(*.d.ts|*.js|output)': './test/assets/output',
}; };
tap.test('should convert files from an array with single files to output', async (tools) => { tap.test('should compile files from an array', async () => {
tsbuild.compileFileArray(assetfiles, { outDir: './test/assets/output' }); const compiler = new TsCompiler();
await compiler.compileFilesOrThrow(assetfiles, { outDir: './test/assets/output' });
}); });
tap.test('should convert files from an array with single files to output', async (tools) => { tap.test('should compile files from glob pattern object', async () => {
tsbuild.compileGlobStringObject(assetfiles2); const compiler = new TsCompiler();
await compiler.compileGlob(assetfiles2);
}); });
tap.start(); tap.start();

View File

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

View File

@@ -1,5 +1,14 @@
import * as early from '@push.rocks/early'; import * as early from '@push.rocks/early';
early.start('tsbuild'); early.start('tsbuild');
export * from './tsbuild.exports.js';
export * from './tsbuild.cli.js'; // Export from new modular structure
export * from './mod_fs/index.js';
export * from './mod_config/index.js';
export * from './mod_unpack/index.js';
export * from './mod_compiler/index.js';
export * from './mod_cli/index.js';
// Re-export TypeScript types for convenience
export type { CompilerOptions, ScriptTarget, ModuleKind } from 'typescript';
early.stop(); early.stop();

View File

@@ -0,0 +1,320 @@
import * as path from 'path';
import * as smartcli from '@push.rocks/smartcli';
import * as smartpath from '@push.rocks/smartpath';
import * as tspublish from '@git.zone/tspublish';
import { TsCompiler } from '../mod_compiler/index.js';
import { FsHelpers } from '../mod_fs/index.js';
/**
* TsBuildCli handles all CLI commands for tsbuild.
* Provides commands for compiling, type checking, and emit validation.
*/
export class TsBuildCli {
private cli: smartcli.Smartcli;
private cwd: string;
constructor(cwd: string = process.cwd()) {
this.cwd = cwd;
this.cli = new smartcli.Smartcli();
this.registerCommands();
}
/**
* Register all CLI commands
*/
private registerCommands(): void {
this.registerStandardCommand();
this.registerCustomCommand();
this.registerEmitCheckCommand();
this.registerTsFoldersCommand();
this.registerCheckCommand();
}
/**
* Standard command: compiles ts folder to dist_ts
*/
private registerStandardCommand(): void {
this.cli.standardCommand().subscribe(async (argvArg) => {
const compiler = new TsCompiler(this.cwd, argvArg);
await compiler.compileGlob({
'./ts/**/*.ts': './dist_ts',
});
const summary = (argvArg as any)?.__tsbuildFinalErrorSummary;
if (summary && summary.totalErrors > 0) {
process.exit(1);
}
});
}
/**
* Custom command: compiles specified directories to dist_ prefixed directories
*/
private registerCustomCommand(): void {
this.cli.addCommand('custom').subscribe(async (argvArg) => {
const listedDirectories = argvArg._;
listedDirectories.shift(); // removes the first element that is "custom"
const compilationCommandObject: Record<string, string> = {};
for (const directory of listedDirectories) {
compilationCommandObject[`./${directory}/**/*.ts`] = `./dist_${directory}`;
}
const compiler = new TsCompiler(this.cwd, argvArg);
await compiler.compileGlob(compilationCommandObject);
const summary = (argvArg as any)?.__tsbuildFinalErrorSummary;
if (summary && summary.totalErrors > 0) {
process.exit(1);
}
});
}
/**
* Emit check command: validates files can be emitted without producing output
*/
private registerEmitCheckCommand(): void {
this.cli.addCommand('emitcheck').subscribe(async (argvArg) => {
const patterns = argvArg._.slice(1);
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 allFiles = await this.collectFilesFromPatterns(patterns);
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`);
const compiler = new TsCompiler(this.cwd, argvArg);
const success = await compiler.checkEmit(allFiles);
process.exit(success ? 0 : 1);
});
}
/**
* TsFolders command: compiles all ts_* directories in order
*/
private registerTsFoldersCommand(): void {
this.cli.addCommand('tsfolders').subscribe(async (argvArg) => {
// List folders matching /^ts/ regex
const allEntries = await FsHelpers.listDirectory(this.cwd);
const tsFolders = allEntries
.filter((e) => e.isDirectory && /^ts/.test(e.name))
.map((e) => e.name);
// Get tspublish.json based ranking
const tsPublishInstance = new tspublish.TsPublish();
const tsPublishModules = await tsPublishInstance.getModuleSubDirs(this.cwd);
// Create an array with folder names and their ranks
const foldersWithOrder: Array<{ folder: string; rank: number }> = [];
for (const folder of tsFolders) {
let rank = Infinity;
if (tsPublishModules[folder] && tsPublishModules[folder].order !== undefined) {
rank = tsPublishModules[folder].order;
}
foldersWithOrder.push({ folder, rank });
}
// Sort the folders based on rank
foldersWithOrder.sort((a, b) => a.rank - b.rank);
// Construct the sorted list of folders
const sortedTsFolders: string[] = [];
for (const item of foldersWithOrder) {
sortedTsFolders.push(item.folder);
}
// Ensure ts_interfaces is first and ts_shared is second if they exist
const ensurePosition = (folderName: string, position: number) => {
if (tsFolders.indexOf(folderName) > -1 && Object.keys(tsPublishModules).indexOf(folderName) === -1) {
const currentIndex = sortedTsFolders.indexOf(folderName);
if (currentIndex > -1) {
sortedTsFolders.splice(currentIndex, 1);
}
sortedTsFolders.splice(position, 0, folderName);
}
};
ensurePosition('ts_interfaces', 0);
ensurePosition('ts_shared', 1);
// Display compilation plan
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');
// Build compilation object
const compilationCommandObject: Record<string, string> = {};
for (const tsFolder of sortedTsFolders) {
compilationCommandObject[`./${tsFolder}/**/*.ts`] = `./dist_${tsFolder}`;
}
const compiler = new TsCompiler(this.cwd, argvArg);
await compiler.compileGlob(compilationCommandObject);
const summary = (argvArg as any)?.__tsbuildFinalErrorSummary;
if (summary && summary.totalErrors > 0) {
process.exit(1);
}
});
}
/**
* Check command: type checks files without emitting
*/
private registerCheckCommand(): void {
this.cli.addCommand('check').subscribe(async (argvArg) => {
const patterns = argvArg._.slice(1);
// If no patterns provided, default to checking ts/**/* and then test/**/*
if (patterns.length === 0) {
await this.runDefaultTypeChecks(argvArg);
return;
}
const allFiles = await this.collectFilesFromPatterns(patterns);
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`);
const compiler = new TsCompiler(this.cwd, argvArg);
const success = await compiler.checkTypes(allFiles);
process.exit(success ? 0 : 1);
});
}
/**
* Run default type checks for ts/ and test/ directories
*/
private async runDefaultTypeChecks(argvArg: any): Promise<void> {
console.log('\n🔬 Running default type checking sequence...\n');
// First check ts/**/* without skiplibcheck
console.log('📂 Checking ts/**/* files...');
const tsTsFiles = await FsHelpers.listFilesWithGlob(this.cwd, 'ts/**/*.ts');
if (tsTsFiles.length > 0) {
console.log(` Found ${tsTsFiles.length} TypeScript files in ts/`);
const tsAbsoluteFiles = smartpath.transform.toAbsolute(tsTsFiles, this.cwd) as string[];
const tsCompiler = new TsCompiler(this.cwd, argvArg);
const tsSuccess = await tsCompiler.checkTypes(tsAbsoluteFiles);
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 testTsFiles = await FsHelpers.listFilesWithGlob(this.cwd, 'test/**/*.ts');
if (testTsFiles.length > 0) {
console.log(` Found ${testTsFiles.length} TypeScript files in test/`);
const testAbsoluteFiles = smartpath.transform.toAbsolute(testTsFiles, this.cwd) as string[];
const testArgvArg = { ...argvArg, skiplibcheck: true };
const testCompiler = new TsCompiler(this.cwd, testArgvArg);
const testSuccess = await testCompiler.checkTypes(testAbsoluteFiles);
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);
}
/**
* Collect files from patterns (glob or direct paths)
*/
private async collectFilesFromPatterns(patterns: string[]): Promise<string[]> {
let allFiles: string[] = [];
for (const pattern of patterns) {
if (pattern.includes('*') || pattern.includes('{') || pattern.includes('?')) {
// Handle as glob pattern
console.log(`Processing glob pattern: ${pattern}`);
try {
const stringMatchedFiles = await FsHelpers.listFilesWithGlob(this.cwd, pattern);
if (stringMatchedFiles.length === 0) {
console.warn(`⚠️ Warning: No files matched the pattern '${pattern}'`);
} else {
console.log(`📂 Found ${stringMatchedFiles.length} files matching pattern '${pattern}'`);
const absoluteMatchedFiles = smartpath.transform.toAbsolute(stringMatchedFiles, this.cwd) as string[];
allFiles = allFiles.concat(absoluteMatchedFiles);
}
} catch (err) {
console.error(`❌ Error processing glob pattern '${pattern}': ${err}`);
}
} else {
// Handle as direct file path
const filePath = path.isAbsolute(pattern) ? pattern : path.join(this.cwd, pattern);
const fileExists = await FsHelpers.fileExists(filePath);
if (fileExists) {
allFiles.push(filePath);
} else {
console.error(`❌ Error: File not found: ${filePath}`);
process.exit(1);
}
}
}
// Filter to only TypeScript files
return allFiles.filter((file) => file.endsWith('.ts') || file.endsWith('.tsx'));
}
/**
* Start parsing CLI arguments
*/
public run(): void {
this.cli.startParse();
}
}
/**
* Run the CLI
*/
export const runCli = async (): Promise<void> => {
const cli = new TsBuildCli();
cli.run();
};

1
ts/mod_cli/index.ts Normal file
View File

@@ -0,0 +1 @@
export * from './classes.tsbuildcli.js';

View File

@@ -0,0 +1,536 @@
import type { CompilerOptions, Diagnostic, Program } from 'typescript';
import typescript from 'typescript';
import * as smartdelay from '@push.rocks/smartdelay';
import * as smartpromise from '@push.rocks/smartpromise';
import * as smartpath from '@push.rocks/smartpath';
import { TsConfig } from '../mod_config/index.js';
import { FsHelpers } from '../mod_fs/index.js';
import { performUnpack } from '../mod_unpack/index.js';
/**
* Interface for error summary data
*/
export interface IErrorSummary {
errorsByFile: Record<string, Diagnostic[]>;
generalErrors: Diagnostic[];
totalErrors: number;
totalFiles: number;
}
/**
* Interface for task information
*/
export interface ITaskInfo {
taskNumber: number;
totalTasks: number;
sourcePattern: string;
destDir: string;
fileCount: number;
}
/**
* Interface for compilation result
*/
export interface ICompileResult {
emittedFiles: string[];
errorSummary: IErrorSummary;
}
/**
* TsCompiler handles TypeScript compilation with error tracking,
* configuration management, and output unpacking.
*/
export class TsCompiler {
private config: TsConfig;
private cwd: string;
private argvArg?: any;
constructor(cwd: string = process.cwd(), argvArg?: any) {
this.cwd = cwd;
this.config = new TsConfig(cwd);
this.argvArg = argvArg;
}
/**
* Get the current working directory
*/
public getCwd(): string {
return this.cwd;
}
/**
* Get the TsConfig instance
*/
public getConfig(): TsConfig {
return this.config;
}
/**
* Create compiler options by merging defaults, tsconfig.json, and custom options
*/
public createOptions(customOptions: CompilerOptions = {}): CompilerOptions {
return this.config.merge(customOptions, this.argvArg);
}
/**
* Create a TypeScript program from file names and options
*/
private createProgram(fileNames: string[], options: CompilerOptions): Program {
return typescript.createProgram(fileNames, options);
}
/**
* Process TypeScript diagnostics and return error summary
*/
private processDiagnostics(diagnostics: readonly Diagnostic[]): IErrorSummary {
const errorsByFile: Record<string, Diagnostic[]> = {};
const generalErrors: Diagnostic[] = [];
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,
};
}
/**
* Display error summary to console
*/
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 = 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) {
console.log(` ${lineContent.trimEnd()}`);
const indicator = ' '.repeat(character) + `${colors.red}^${colors.reset}`;
console.log(` ${indicator}`);
}
} catch {
// 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 = 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');
}
/**
* Handle skipLibCheck warning display
*/
private async handleSkipLibCheckWarning(): Promise<void> {
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 smartdelay.delayFor(5000);
} else if (!this.argvArg?.quiet && !this.argvArg?.json) {
console.log('⚠️ skipLibCheck enabled; use --confirmskiplibcheck to pause with warning.');
}
}
/**
* Compile files with error tracking (returns result instead of throwing)
*/
public async compileFiles(
fileNames: string[],
customOptions: CompilerOptions = {},
taskInfo?: ITaskInfo
): Promise<ICompileResult> {
const options = this.createOptions(customOptions);
if (options.skipLibCheck) {
await this.handleSkipLibCheckWarning();
}
// Enhanced logging with task info
const startTime = Date.now();
if (taskInfo) {
const { taskNumber, totalTasks, sourcePattern, fileCount } = taskInfo;
const relativeDestDir = taskInfo.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 ${fileNames.length} files...`);
}
const done = smartpromise.defer<ICompileResult>();
const program = this.createProgram(fileNames, options);
// Check for pre-emit diagnostics first
const preEmitDiagnostics = 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');
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 (taskInfo) {
const { taskNumber, totalTasks } = 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 (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');
done.resolve({ emittedFiles: [], errorSummary: combinedErrorSummary });
}
return done.promise;
}
/**
* Compile files (throws on error)
*/
public async compileFilesOrThrow(fileNames: string[], customOptions: CompilerOptions = {}): Promise<string[]> {
const result = await this.compileFiles(fileNames, customOptions);
if (result.errorSummary.totalErrors > 0) {
throw new Error('TypeScript compilation failed.');
}
return result.emittedFiles;
}
/**
* Compile glob patterns with automatic unpacking
*/
public async compileGlob(
globPatterns: Record<string, string>,
customOptions: CompilerOptions = {}
): Promise<ICompileResult> {
const emittedFiles: string[] = [];
const errorSummaries: IErrorSummary[] = [];
const totalTasks = Object.keys(globPatterns).length;
let currentTask = 0;
const isQuiet = this.argvArg?.quiet === true;
const isJson = this.argvArg?.json === true;
if (!isQuiet && !isJson) {
console.log(`\n👷 TypeScript Compilation Tasks (${totalTasks} task${totalTasks !== 1 ? 's' : ''}):`);
Object.entries(globPatterns).forEach(([source, dest]) => {
console.log(` 📂 ${source}${dest}`);
});
console.log('');
}
for (const pattern of Object.keys(globPatterns)) {
const destPath = globPatterns[pattern];
if (!pattern || !destPath) continue;
// Get files matching the glob pattern
const files = await FsHelpers.listFilesWithGlob(this.cwd, pattern);
// Transform to absolute paths
const absoluteFiles = smartpath.transform.toAbsolute(files, this.cwd) as string[];
// Get destination directory as absolute path
const destDir = smartpath.transform.toAbsolute(destPath, this.cwd) as string;
// Update compiler options with the output directory
const options: CompilerOptions = {
...customOptions,
outDir: destDir,
};
currentTask++;
const taskInfo: ITaskInfo = {
taskNumber: currentTask,
totalTasks,
sourcePattern: pattern,
destDir: destPath,
fileCount: absoluteFiles.length,
};
const result = await this.compileFiles(absoluteFiles, options, taskInfo);
emittedFiles.push(...result.emittedFiles);
errorSummaries.push(result.errorSummary);
// Perform unpack if compilation succeeded
if (result.errorSummary.totalErrors === 0) {
await performUnpack(pattern, destDir, this.cwd);
}
}
// Merge all error summaries
const finalErrorSummary = this.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: typescript.flattenDiagnosticMessageText(d.messageText as any, '\n'),
})),
])
),
};
console.log(JSON.stringify(result));
} else if (!isQuiet) {
this.displayFinalSummary(finalErrorSummary);
}
// Attach summary to argvArg for CLI exit behavior
if (this.argvArg && typeof this.argvArg === 'object') {
(this.argvArg as any).__tsbuildFinalErrorSummary = finalErrorSummary;
}
return {
emittedFiles,
errorSummary: finalErrorSummary,
};
}
/**
* Check if files can be emitted without actually emitting
*/
public async checkEmit(fileNames: string[], customOptions: CompilerOptions = {}): Promise<boolean> {
const options = { ...this.createOptions(customOptions), noEmit: true };
const fileCount = fileNames.length;
console.log(`\n🔍 Checking if ${fileCount} file${fileCount !== 1 ? 's' : ''} can be emitted...`);
const program = this.createProgram(fileNames, options);
const preEmitDiagnostics = typescript.getPreEmitDiagnostics(program);
const preEmitErrorSummary = this.processDiagnostics(preEmitDiagnostics);
const emitResult = program.emit(undefined, undefined, undefined, true);
const emitErrorSummary = this.processDiagnostics(emitResult.diagnostics);
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;
}
/**
* Check TypeScript files for type errors without emission
*/
public async checkTypes(fileNames: string[], customOptions: CompilerOptions = {}): Promise<boolean> {
const options = { ...this.createOptions(customOptions), noEmit: true };
const fileCount = fileNames.length;
console.log(`\n🔍 Type checking ${fileCount} TypeScript file${fileCount !== 1 ? 's' : ''}...`);
const program = this.createProgram(fileNames, options);
const diagnostics = typescript.getPreEmitDiagnostics(program);
const errorSummary = this.processDiagnostics(diagnostics);
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;
}
/**
* Merge multiple error summaries into one
*/
private mergeErrorSummaries(summaries: IErrorSummary[]): IErrorSummary {
const mergedErrorsByFile: Record<string, Diagnostic[]> = {};
const mergedGeneralErrors: Diagnostic[] = [];
let totalErrors = 0;
summaries.forEach((summary) => {
Object.entries(summary.errorsByFile).forEach(([fileName, errors]) => {
if (!mergedErrorsByFile[fileName]) {
mergedErrorsByFile[fileName] = [];
}
mergedErrorsByFile[fileName] = mergedErrorsByFile[fileName].concat(errors);
});
mergedGeneralErrors.push(...summary.generalErrors);
totalErrors += summary.totalErrors;
});
return {
errorsByFile: mergedErrorsByFile,
generalErrors: mergedGeneralErrors,
totalErrors,
totalFiles: Object.keys(mergedErrorsByFile).length,
};
}
/**
* Display final compilation summary
*/
private displayFinalSummary(errorSummary: 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');
}
}

1
ts/mod_compiler/index.ts Normal file
View File

@@ -0,0 +1 @@
export * from './classes.tscompiler.js';

View File

@@ -0,0 +1,180 @@
import * as fs from 'fs';
import * as path from 'path';
import type { CompilerOptions } from 'typescript';
import typescript from 'typescript';
/**
* Default compiler options for TypeScript compilation
*/
export const compilerOptionsDefault: CompilerOptions = {
declaration: true,
inlineSourceMap: true,
noEmitOnError: true,
outDir: 'dist_ts/',
module: typescript.ModuleKind.NodeNext,
target: typescript.ScriptTarget.ESNext,
moduleResolution: typescript.ModuleResolutionKind.NodeNext,
lib: ['lib.dom.d.ts', 'lib.esnext.d.ts'],
noImplicitAny: false,
esModuleInterop: true,
verbatimModuleSyntax: true,
baseUrl: './',
};
/**
* TsConfig handles loading and merging TypeScript compiler configurations.
* It supports reading tsconfig.json and merging with defaults and custom options.
*/
export class TsConfig {
private cwd: string;
private cachedTsConfig: CompilerOptions | null = null;
constructor(cwd: string = process.cwd()) {
this.cwd = cwd;
}
/**
* Get the current working directory
*/
public getCwd(): string {
return this.cwd;
}
/**
* Load and parse tsconfig.json from the current directory
*/
public load(): CompilerOptions {
if (this.cachedTsConfig !== null) {
return this.cachedTsConfig;
}
let tsconfig: any;
try {
const tsconfigPath = path.join(this.cwd, 'tsconfig.json');
const tsconfigContent = fs.readFileSync(tsconfigPath, 'utf8');
tsconfig = JSON.parse(tsconfigContent);
} catch {
this.cachedTsConfig = {};
return {};
}
if (!tsconfig || !tsconfig.compilerOptions) {
this.cachedTsConfig = {};
return {};
}
const returnObject: CompilerOptions = { ...tsconfig.compilerOptions };
// Convert target string to enum
if (tsconfig.compilerOptions.target && typeof tsconfig.compilerOptions.target === 'string') {
const targetKey = tsconfig.compilerOptions.target.toUpperCase();
if (targetKey in typescript.ScriptTarget) {
returnObject.target = typescript.ScriptTarget[targetKey as keyof typeof typescript.ScriptTarget];
}
}
// Convert module string to enum
if (tsconfig.compilerOptions.module && typeof tsconfig.compilerOptions.module === 'string') {
const moduleKey = tsconfig.compilerOptions.module.toUpperCase();
if (moduleKey in typescript.ModuleKind) {
returnObject.module = typescript.ModuleKind[moduleKey as keyof typeof typescript.ModuleKind];
} else if (moduleKey === 'NODENEXT') {
returnObject.module = typescript.ModuleKind.NodeNext;
}
}
// Convert moduleResolution string to enum
if (tsconfig.compilerOptions.moduleResolution && typeof tsconfig.compilerOptions.moduleResolution === 'string') {
const moduleResolutionKey = tsconfig.compilerOptions.moduleResolution.toUpperCase();
if (moduleResolutionKey in typescript.ModuleResolutionKind) {
returnObject.moduleResolution = typescript.ModuleResolutionKind[
moduleResolutionKey as keyof typeof typescript.ModuleResolutionKind
];
} else if (moduleResolutionKey === 'NODENEXT') {
returnObject.moduleResolution = typescript.ModuleResolutionKind.NodeNext;
}
}
// Apply path transformations (ts_ → dist_ts_)
if (tsconfig.compilerOptions.paths) {
returnObject.paths = { ...tsconfig.compilerOptions.paths };
for (const pathKey of Object.keys(returnObject.paths)) {
if (Array.isArray(returnObject.paths[pathKey]) && returnObject.paths[pathKey].length > 0) {
returnObject.paths[pathKey][0] = returnObject.paths[pathKey][0].replace('./ts_', './dist_ts_');
}
}
}
this.cachedTsConfig = returnObject;
return returnObject;
}
/**
* Get default compiler options
*/
public getDefaultOptions(): CompilerOptions {
return { ...compilerOptionsDefault };
}
/**
* Get critical/protected default options that shouldn't be overridden by tsconfig.json
*/
public getProtectedDefaults(): CompilerOptions {
return {
outDir: 'dist_ts/',
noEmitOnError: true,
declaration: true,
inlineSourceMap: true,
};
}
/**
* Process command line arguments and return applicable compiler options
*/
public getCommandLineOptions(argvArg?: any): CompilerOptions {
if (!argvArg) return {};
const options: CompilerOptions = {};
if (argvArg.skiplibcheck) {
options.skipLibCheck = true;
}
if (argvArg.disallowimplicitany) {
options.noImplicitAny = true;
}
if (argvArg.commonjs) {
options.module = typescript.ModuleKind.CommonJS;
options.moduleResolution = typescript.ModuleResolutionKind.NodeJs;
}
return options;
}
/**
* Merge compiler options with proper priority order:
* 1. Default options
* 2. tsconfig.json options
* 3. Protected defaults (cannot be overridden by tsconfig)
* 4. Custom options (programmatic)
* 5. CLI options (highest priority)
*/
public merge(customOptions: CompilerOptions = {}, argvArg?: any): CompilerOptions {
return {
...this.getDefaultOptions(),
...this.load(),
...this.getProtectedDefaults(),
...customOptions,
...this.getCommandLineOptions(argvArg),
};
}
/**
* Clear the cached tsconfig (useful for reloading)
*/
public clearCache(): void {
this.cachedTsConfig = null;
}
}

View File

@@ -0,0 +1,116 @@
import * as fs from 'fs';
import * as path from 'path';
/**
* Interface for tspublish.json configuration
*/
export interface ITsPublishJson {
order?: number;
unpack?: boolean;
[key: string]: any;
}
/**
* TsPublishConfig handles loading and parsing tspublish.json files.
* These configuration files control module-specific settings like
* compilation order and output unpacking behavior.
*/
export class TsPublishConfig {
private folderPath: string;
private cachedConfig: ITsPublishJson | null | undefined = undefined;
constructor(folderPath: string) {
this.folderPath = folderPath;
}
/**
* Get the folder path this config is for
*/
public getFolderPath(): string {
return this.folderPath;
}
/**
* Load and parse tspublish.json from the folder
* Returns null if file doesn't exist or is invalid
*/
public async load(): Promise<ITsPublishJson | null> {
if (this.cachedConfig !== undefined) {
return this.cachedConfig;
}
try {
const configPath = path.join(this.folderPath, 'tspublish.json');
const content = await fs.promises.readFile(configPath, 'utf8');
this.cachedConfig = JSON.parse(content);
return this.cachedConfig;
} catch {
this.cachedConfig = null;
return null;
}
}
/**
* Synchronously load and parse tspublish.json from the folder
* Returns null if file doesn't exist or is invalid
*/
public loadSync(): ITsPublishJson | null {
if (this.cachedConfig !== undefined) {
return this.cachedConfig;
}
try {
const configPath = path.join(this.folderPath, 'tspublish.json');
const content = fs.readFileSync(configPath, 'utf8');
this.cachedConfig = JSON.parse(content);
return this.cachedConfig;
} catch {
this.cachedConfig = null;
return null;
}
}
/**
* Check if output should be unpacked (flattened)
* Default is true if not specified
*/
public get shouldUnpack(): boolean {
const config = this.loadSync();
if (!config || config.unpack === undefined) {
return true; // Default to true
}
return config.unpack === true;
}
/**
* Get the compilation order for tsfolders command
* Returns Infinity if not specified (sorted last)
*/
public get order(): number {
const config = this.loadSync();
if (!config || config.order === undefined) {
return Infinity;
}
return config.order;
}
/**
* Check if tspublish.json exists in the folder
*/
public async exists(): Promise<boolean> {
try {
const configPath = path.join(this.folderPath, 'tspublish.json');
await fs.promises.access(configPath, fs.constants.F_OK);
return true;
} catch {
return false;
}
}
/**
* Clear the cached config (useful for reloading)
*/
public clearCache(): void {
this.cachedConfig = undefined;
}
}

2
ts/mod_config/index.ts Normal file
View File

@@ -0,0 +1,2 @@
export * from './classes.tsconfig.js';
export * from './classes.tspublishconfig.js';

View File

@@ -0,0 +1,144 @@
import * as fs from 'fs';
import * as path from 'path';
import * as smartfsModule from '@push.rocks/smartfs';
// Create a smartfs instance with Node.js provider
const smartfs = new smartfsModule.SmartFs(new smartfsModule.SmartFsProviderNode());
/**
* FsHelpers provides filesystem utility methods for tsbuild.
* All methods are static for convenience.
*/
export class FsHelpers {
/**
* The smartfs instance for filesystem operations
*/
public static readonly smartfs = smartfs;
/**
* List files matching a glob pattern like './ts/**\/*.ts'
* Parses the pattern to extract base directory and filter pattern
*/
public static async listFilesWithGlob(basePath: string, globPattern: string): Promise<string[]> {
// Remove leading ./ if present
const pattern = globPattern.replace(/^\.\//, '');
// Find the first directory part before any glob characters
const globChars = ['*', '?', '{', '['];
let baseDir = basePath;
// Find where the glob pattern starts
const parts = pattern.split('/');
const staticParts: string[] = [];
const filterParts: string[] = [];
let foundGlob = false;
for (const part of parts) {
if (!foundGlob && !globChars.some(c => part.includes(c))) {
staticParts.push(part);
} else {
foundGlob = true;
filterParts.push(part);
}
}
// Build the base directory
if (staticParts.length > 0) {
baseDir = path.join(basePath, ...staticParts);
}
// Build the filter pattern (just the filename part, ignoring ** for directories)
// The recursive() handles the ** part
const fileFilter = filterParts[filterParts.length - 1] || '*';
// Check if we need recursive search
const needsRecursive = filterParts.some(p => p === '**' || p.includes('**'));
let dirBuilder = smartfs.directory(baseDir);
if (needsRecursive) {
dirBuilder = dirBuilder.recursive();
}
try {
const entries = await dirBuilder.filter(fileFilter).list();
return entries.filter(e => e.isFile).map(e => e.path);
} catch {
// Directory doesn't exist or other error
return [];
}
}
/**
* Extract source folder name from a glob pattern
* './ts_core/**\/*.ts' → 'ts_core'
* 'ts_foo/**\/*.ts' → 'ts_foo'
*/
public static extractSourceFolder(pattern: string): string | null {
const match = pattern.match(/^\.?\/?([^\/\*]+)/);
return match ? match[1] : null;
}
/**
* Get the current working directory
*/
public static getCwd(): string {
return process.cwd();
}
/**
* Get the package directory (where package.json is located)
*/
public static getPackageDir(): string {
return path.resolve(__dirname, '../../');
}
/**
* Check if a file exists
*/
public static async fileExists(filePath: string): Promise<boolean> {
return smartfs.file(filePath).exists();
}
/**
* Check if a directory exists
*/
public static async directoryExists(dirPath: string): Promise<boolean> {
return smartfs.directory(dirPath).exists();
}
/**
* Read a JSON file and parse it
*/
public static readJsonSync<T = any>(filePath: string): T {
const content = fs.readFileSync(filePath, 'utf8');
return JSON.parse(content);
}
/**
* List directory contents
*/
public static async listDirectory(dirPath: string) {
return smartfs.directory(dirPath).list();
}
/**
* Remove a directory recursively
*/
public static async removeDirectory(dirPath: string): Promise<void> {
await fs.promises.rm(dirPath, { recursive: true });
}
/**
* Move/rename a file or directory
*/
public static async move(src: string, dest: string): Promise<void> {
await fs.promises.rename(src, dest);
}
/**
* Remove an empty directory
*/
public static async removeEmptyDirectory(dirPath: string): Promise<void> {
await fs.promises.rmdir(dirPath);
}
}

1
ts/mod_fs/index.ts Normal file
View File

@@ -0,0 +1 @@
export * from './classes.fshelpers.js';

View File

@@ -0,0 +1,153 @@
import * as fs from 'fs';
import * as path from 'path';
import { TsPublishConfig } from '../mod_config/index.js';
import { FsHelpers } from '../mod_fs/index.js';
/**
* TsUnpacker handles flattening of nested TypeScript output directories.
*
* When TypeScript compiles files that import from sibling directories,
* it creates a nested structure like:
* dist_ts_core/ts_core/index.js
* dist_ts_core/ts_shared/helper.js
*
* This class flattens it to:
* dist_ts_core/index.js
*/
export class TsUnpacker {
private sourceFolderName: string;
private destDir: string;
private cwd: string;
private config: TsPublishConfig;
constructor(sourceFolderName: string, destDir: string, cwd: string = process.cwd()) {
this.sourceFolderName = sourceFolderName;
this.destDir = destDir;
this.cwd = cwd;
this.config = new TsPublishConfig(path.join(cwd, sourceFolderName));
}
/**
* Create an unpacker from a glob pattern
* './ts_core/**\/*.ts' → sourceFolderName = 'ts_core'
*/
public static fromGlobPattern(
sourcePattern: string,
destDir: string,
cwd: string = process.cwd()
): TsUnpacker | null {
const sourceFolderName = FsHelpers.extractSourceFolder(sourcePattern);
if (!sourceFolderName) {
return null;
}
return new TsUnpacker(sourceFolderName, destDir, cwd);
}
/**
* Get the source folder name
*/
public getSourceFolderName(): string {
return this.sourceFolderName;
}
/**
* Get the destination directory
*/
public getDestDir(): string {
return this.destDir;
}
/**
* Check if unpacking should be performed based on tspublish.json config
* Default is true if not specified
*/
public async shouldUnpack(): Promise<boolean> {
return this.config.shouldUnpack;
}
/**
* Check if nested structure exists in the destination directory
*/
public async detectNesting(): Promise<boolean> {
const nestedPath = path.join(this.destDir, this.sourceFolderName);
return FsHelpers.directoryExists(nestedPath);
}
/**
* Get the path to the nested directory
*/
public getNestedPath(): string {
return path.join(this.destDir, this.sourceFolderName);
}
/**
* Perform the unpack operation - flatten nested output directories
* Returns true if unpacking was performed, false if skipped
*/
public async unpack(): Promise<boolean> {
// Check if we should unpack based on config
if (!(await this.shouldUnpack())) {
return false;
}
// Check if nested structure exists
if (!(await this.detectNesting())) {
return false;
}
const nestedPath = this.getNestedPath();
// Delete sibling folders (not the source folder)
await this.removeSiblingDirectories();
// Move contents from nested folder up
await this.moveNestedContentsUp();
// Remove empty nested folder
await FsHelpers.removeEmptyDirectory(nestedPath);
return true;
}
/**
* Remove sibling directories in the destination folder
* (directories other than the source folder being unpacked)
*/
private async removeSiblingDirectories(): Promise<void> {
const entries = await FsHelpers.listDirectory(this.destDir);
for (const entry of entries) {
if (entry.isDirectory && entry.name !== this.sourceFolderName) {
await FsHelpers.removeDirectory(path.join(this.destDir, entry.name));
}
}
}
/**
* Move contents from the nested folder up to the destination directory
*/
private async moveNestedContentsUp(): Promise<void> {
const nestedPath = this.getNestedPath();
const entries = await FsHelpers.listDirectory(nestedPath);
for (const entry of entries) {
const src = path.join(nestedPath, entry.name);
const dest = path.join(this.destDir, entry.name);
await FsHelpers.move(src, dest);
}
}
}
/**
* Convenience function to perform unpack operation
* Can be used directly without instantiating the class
*/
export async function performUnpack(
sourcePattern: string,
destDir: string,
cwd: string = process.cwd()
): Promise<boolean> {
const unpacker = TsUnpacker.fromGlobPattern(sourcePattern, destDir, cwd);
if (!unpacker) {
return false;
}
return unpacker.unpack();
}

1
ts/mod_unpack/index.ts Normal file
View File

@@ -0,0 +1 @@
export * from './classes.tsunpacker.js';

View File

@@ -1,4 +0,0 @@
import * as plugins from './plugins.js';
export const cwd = process.cwd();
export const packageDir = plugins.path.join(plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url), '../');

View File

@@ -1,16 +1,13 @@
// node native // node native
import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
export { export { fs, path };
path
}
// @git.zone scope // @git.zone scope
import * as tspublish from '@git.zone/tspublish'; import * as tspublish from '@git.zone/tspublish';
export { export { tspublish };
tspublish
}
// @push.rocks scope // @push.rocks scope
import * as smartcli from '@push.rocks/smartcli'; import * as smartcli from '@push.rocks/smartcli';
@@ -24,6 +21,4 @@ export { smartcli, smartdelay, smartfile, smartpath, smartpromise };
// third party scope // third party scope
import typescript from 'typescript'; import typescript from 'typescript';
export { export { typescript };
typescript
}

View File

@@ -1,151 +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);
// Check for pre-emit diagnostics first
const preEmitDiagnostics = plugins.typescript.getPreEmitDiagnostics(program);
let hasErrors = false;
// Log pre-emit diagnostics if any
if (preEmitDiagnostics.length > 0) {
preEmitDiagnostics.forEach((diagnostic) => {
hasErrors = true;
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')}`
);
}
});
}
// Only continue to emit phase if no pre-emit errors
if (hasErrors) {
console.error('TypeScript pre-emit checks failed. Please fix the issues above.');
process.exit(1);
}
// If no pre-emit errors, proceed with emit
const emitResult = program.emit();
// Check for emit diagnostics
if (emitResult.diagnostics.length > 0) {
emitResult.diagnostics.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

@@ -1,92 +0,0 @@
import * as plugins from './plugins.js';
import * as paths from './paths.js';
import * as tsbuild from './tsbuild.exports.js';
export const runCli = async () => {
const tsbuildCli = new plugins.smartcli.Smartcli();
/**
* the standard task compiles anything in ts/ directory to dist directory
*/
tsbuildCli.standardCommand().subscribe(async (argvArg) => {
tsbuild.compileGlobStringObject(
{
'./ts/**/*.ts': './dist_ts',
},
{},
process.cwd(),
argvArg
);
});
/**
* the custom command compiles any customDir to dist_customDir
*/
tsbuildCli.addCommand('custom').subscribe(async (argvArg) => {
const listedDirectories = argvArg._;
listedDirectories.shift(); // removes the first element that is "custom"
const compilationCommandObject: { [key: string]: string } = {};
for (const directory of listedDirectories) {
compilationCommandObject[`./${directory}/**/*.ts`] = `./dist_${directory}`;
}
await tsbuild.compileGlobStringObject(compilationCommandObject, {}, process.cwd(), argvArg);
});
/**
* the custom command compiles any customDir to dist_customDir
*/
tsbuildCli.addCommand('tsfolders').subscribe(async (argvArg) => {
const tsFolders = await plugins.smartfile.fs.listFolders(paths.cwd, /^ts/);
// Now tsFolders contains all other folders except 'ts_shared' and 'ts_interfaces'
// We've established a base order. Now let's look at tspublish.json based ranking.
const tsPublishInstance = new plugins.tspublish.TsPublish();
const tsPublishModules = await tsPublishInstance.getModuleSubDirs(paths.cwd);
// tsPublishModules is an object: { [folderName]: tspublishJsonData }
// Create an array with folder names and their ranks
const foldersWithOrder = [];
for (const folder of tsFolders) {
let rank = Infinity; // Default rank if not specified
if (tsPublishModules[folder] && tsPublishModules[folder].order !== undefined) {
rank = tsPublishModules[folder].order;
}
foldersWithOrder.push({ folder, rank });
}
// Sort the folders based on rank
foldersWithOrder.sort((a, b) => a.rank - b.rank);
// Construct the sorted list of folders
const sortedTsFolders = [];
// Add the rest of the folders in sorted order
for (const item of foldersWithOrder) {
sortedTsFolders.push(item.folder);
}
// Let's make sure 'ts_shared' is always transpiled first
const ensurePosition = (folderNameArg: string, ensuredPosition: number) => {
if (tsFolders.indexOf(folderNameArg) > -1 && Object.keys(tsPublishModules).indexOf(folderNameArg) === -1) {
sortedTsFolders.splice(tsFolders.indexOf(folderNameArg), 1);
sortedTsFolders.splice(ensuredPosition, 0, folderNameArg);
}
}
ensurePosition('ts_interfaces', 0);
ensurePosition('ts_shared', 1);
const compilationCommandObject: { [key: string]: string } = {};
console.log(`compiling in this order:`);
console.log(sortedTsFolders);
for (const tsFolder of sortedTsFolders) {
compilationCommandObject[`./${tsFolder}/**/*.ts`] = `./dist_${tsFolder}`;
}
await tsbuild.compileGlobStringObject(compilationCommandObject, {}, process.cwd(), argvArg);
});
tsbuildCli.startParse();
};

View File

@@ -1,76 +0,0 @@
import * as plugins from './plugins.js';
import type { CompilerOptions, ScriptTarget, ModuleKind } from 'typescript';
import { compiler, mergeCompilerOptions } from './tsbuild.classes.compiler.js';
export type { CompilerOptions, ScriptTarget, ModuleKind };
export * from './tsbuild.classes.compiler.js';
/**
* compile am array of absolute file paths
*/
export let compileFileArray = (
fileStringArrayArg: string[],
compilerOptionsArg: CompilerOptions = {},
argvArg?: any
): Promise<any[]> => {
return compiler(fileStringArrayArg, mergeCompilerOptions(compilerOptionsArg, argvArg), argvArg);
};
/**
* compile advanced glob configurations
* @param globStringArrayArg a array of glob strings
* {
* './some/origin/folder/**\/*.ts': './some/destination/folder'
* }
*/
export let compileGlobStringObject = async (
globStringObjectArg: Record<string, string>,
tsOptionsArg: CompilerOptions = {},
cwdArg: string = process.cwd(),
argvArg?: any
) => {
let compiledFiles: any[] = [];
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);
// Ensure fileTreeArray contains only strings before transforming
const stringFileTreeArray = Array.isArray(fileTreeArray)
? fileTreeArray.filter((item): item is string => typeof item === 'string')
: [];
// Transform to absolute paths
const absoluteFilePathArray = plugins.smartpath.transform.toAbsolute(
stringFileTreeArray,
cwdArg
) as string[];
// Get destination directory as absolute path
const destDir: string = plugins.smartpath.transform.toAbsolute(
globStringObjectArg[keyArg],
cwdArg
) as string;
// Update compiler options with the output directory
const updatedTsOptions: CompilerOptions = {
...tsOptionsArg,
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);
}
}
return compiledFiles;
};

View File

@@ -1,7 +1,5 @@
{ {
"compilerOptions": { "compilerOptions": {
"experimentalDecorators": true,
"useDefineForClassFields": false,
"target": "ES2022", "target": "ES2022",
"module": "NodeNext", "module": "NodeNext",
"moduleResolution": "NodeNext", "moduleResolution": "NodeNext",