Compare commits

..

63 Commits

Author SHA1 Message Date
jkunz d9fc9dbb15 v4.4.1 2026-05-09 12:33:40 +00:00
jkunz a1960ba7cc fix(compiler): return emitted files from compilation results and align tests with emitted output handling 2026-05-09 12:33:40 +00:00
jkunz 8c1805229b v4.4.0 2026-03-24 18:13:56 +00:00
jkunz 724e5bf7ec feat(config): add smartconfig metadata and update TypeScript build configuration docs 2026-03-24 18:13:56 +00:00
jkunz cfe2eafe89 v4.3.0 2026-03-06 07:32:35 +00:00
jkunz b3d1982170 feat(mod_logger): add centralized TsBuildLogger and replace ad-hoc console output with structured, colored logging 2026-03-06 07:32:35 +00:00
jkunz 83e0e9b5dd v4.2.6 2026-03-05 16:45:53 +00:00
jkunz 094f9df55f fix(meta): no changes 2026-03-05 16:45:53 +00:00
jkunz a6a006aaec fix(compiler): move output directory cleaning to separate phase before compilation
Restructured compileGlob() into three distinct phases:
1. Resolve glob patterns and clean ALL output directories upfront
2. Compile all TypeScript tasks (no filesystem cleanup during this phase)
3. Unpack all outputs after all compilations complete

This prevents XFS metadata corruption from rm operations overlapping
with TypeScript compilation writes to sibling directories.
2026-03-05 16:45:07 +00:00
jkunz 9477875c1d v4.2.5 2026-03-05 16:03:49 +00:00
jkunz 2b73f3d582 fix(compiler): yield to the event loop after TypeScript emit to allow pending microtasks and I/O to settle before reading or modifying the output directory 2026-03-05 16:03:49 +00:00
jkunz 0ffdcf852f v4.2.4 2026-03-05 16:00:01 +00:00
jkunz f8f20be4f4 fix(fshelpers): remove outdated comment about using synchronous rm to avoid XFS metadata corruption 2026-03-05 16:00:01 +00:00
jkunz fb5421a8c4 v4.2.3 2026-03-05 15:55:42 +00:00
jkunz 79d48b0875 fix(compiler): defer unpacking until after all compilations and remove diagnostic filesystem syncs to avoid XFS metadata visibility issues 2026-03-05 15:55:42 +00:00
jkunz 8977ff4525 v4.2.2 2026-03-05 15:35:57 +00:00
jkunz 9dc74fd392 fix(compiler): force global filesystem sync to flush XFS delayed logging and add diagnostics comparing Node's readdirSync with system ls to detect directory entry inconsistencies 2026-03-05 15:35:57 +00:00
jkunz 85a33021e4 v4.2.1 2026-03-05 15:28:18 +00:00
jkunz e8e64a4ef3 fix(compiler): use TypeScript sys hooks instead of fs monkeypatching to detect writes/deletes in previous output directories 2026-03-05 15:28:18 +00:00
jkunz a5dd5252db v4.2.0 2026-03-05 15:22:05 +00:00
jkunz dc9a9640df feat(mod_compiler): add diagnostic interception of fs operations to detect and report unexpected file system changes in previously compiled output directories during compilation 2026-03-05 15:22:04 +00:00
jkunz 431c82a186 v4.1.26 2026-03-05 15:17:58 +00:00
jkunz 05184179a4 fix(compiler): fsync output directories after unpack to avoid XFS delayed logging causing corrupt or invisible directory entries during subsequent TypeScript emits 2026-03-05 15:17:58 +00:00
jkunz f15ab3a6f9 v4.1.25 2026-03-05 15:13:36 +00:00
jkunz 67d29a8e77 fix(mod_unpack): flush directory metadata on XFS before reading and use readdirSync-based iteration to avoid missing entries when unpacking 2026-03-05 15:13:36 +00:00
jkunz 0e1db4bb85 v4.1.24 2026-03-05 15:09:53 +00:00
jkunz 7a1c2d82b9 fix(mod_unpack): iterate directories with opendirSync/readSync to avoid missing entries on XFS and ensure directory handles are closed 2026-03-05 15:09:53 +00:00
jkunz 9f5e4ad76e v4.1.23 2026-03-05 15:01:36 +00:00
jkunz 4feb074c03 fix(mod_unpack): handle partial readdirSync results when moving nested directory entries and add diagnostic log 2026-03-05 15:01:36 +00:00
jkunz 95e4f1f036 v4.1.22 2026-03-05 14:54:47 +00:00
jkunz eaa66dff1d fix(mod_compiler): improve logging of successful output directories to include a sorted list of entries and use a shortened relative path 2026-03-05 14:54:47 +00:00
jkunz 8bc4f173e5 v4.1.21 2026-03-05 14:51:14 +00:00
jkunz fba2cba8e8 fix(compiler): log emitted files written outside expected destination directory for diagnostics 2026-03-05 14:51:14 +00:00
jkunz 1033996cb5 v4.1.20 2026-03-05 14:48:05 +00:00
jkunz 7e8b5c4467 fix(mod_compiler): add diagnostic snapshots for output directories around clear and compile steps 2026-03-05 14:48:05 +00:00
jkunz a738716d98 v4.1.19 2026-03-05 14:44:14 +00:00
jkunz d9c79ae4eb fix(mod_fs): use synchronous rm to avoid XFS metadata corruption when removing directories 2026-03-05 14:44:14 +00:00
jkunz a3255fd1fb v4.1.18 2026-03-05 14:40:05 +00:00
jkunz d6fb6e527e fix(mod_compiler): add diagnostic logging of output directory states after compilation and after import-path rewriting to aid debugging 2026-03-05 14:40:05 +00:00
jkunz 96bafec720 v4.1.17 2026-03-05 14:35:05 +00:00
jkunz 86f47ff743 fix(tsunpacker): use synchronous fs operations in tsunpacker to avoid readdir race conditions 2026-03-05 14:35:05 +00:00
jkunz 38c134f084 v4.1.16 2026-03-05 14:30:33 +00:00
jkunz 25372bf97d fix(mod_unpack): handle partial readdir results from signal-interrupted getdents64 when unpacking to ensure sibling removal and nested moves complete 2026-03-05 14:30:33 +00:00
jkunz f521530eed v4.1.15 2026-03-05 14:26:29 +00:00
jkunz dd81d65958 fix(mod_unpack): flatten nested output directory without temporary rename steps to avoid race conditions 2026-03-05 14:26:29 +00:00
jkunz d1ef48560d v4.1.14 2026-03-05 14:01:00 +00:00
jkunz 49d62e20a4 fix(fs): replace execSync and fsync workarounds with atomic async FsHelpers operations to avoid XFS races and shell dependencies 2026-03-05 14:01:00 +00:00
jkunz 4f8443d33f v4.1.13 2026-03-05 13:43:18 +00:00
jkunz 2fb838d1bd fix(mod_unpack): Use child_process.execSync (mv/rm) to perform unpack atomically, replacing async fs operations and logs to avoid ENOENT/partial directory listings on XFS 2026-03-05 13:43:18 +00:00
jkunz b4a1ff5eab v4.1.12 2026-03-05 13:22:17 +00:00
jkunz d3e3905e7f fix(mod_compiler): replace runtime require calls with top-level imports and use execSync/path.join for filesystem sync and traversal 2026-03-05 13:22:17 +00:00
jkunz fe60a21c59 v4.1.11 2026-03-05 13:21:03 +00:00
jkunz 920095d008 fix(mod_compiler): flush directory entries before unpack to avoid XFS delayed-log causing partial readdir results 2026-03-05 13:21:03 +00:00
jkunz e1198c0a78 v4.1.10 2026-03-05 13:15:07 +00:00
jkunz 01b2cfe69c fix(unpack): use atomic renames to flatten nested output and make unpacking more reliable 2026-03-05 13:15:07 +00:00
jkunz b38a8236da v4.1.9 2026-03-05 12:46:49 +00:00
jkunz f136fe2e40 fix(fs): improve filesystem helpers: use sync rename for reliability on certain filesystems; retry rmdir with delays and avoid recursive rm; bump @push.rocks/smartfs to ^1.3.2 2026-03-05 12:46:49 +00:00
jkunz 5fa93923d2 v4.1.8 2026-03-05 12:29:11 +00:00
jkunz 856099b5ed fix(unpack): catch unpack errors and add verbose unpack logging 2026-03-05 12:29:11 +00:00
jkunz 7aec1548eb v4.1.7 2026-03-05 12:19:24 +00:00
jkunz c269039050 fix(fs/compiler/unpack): robustify directory removal and remove noisy diagnostic logging 2026-03-05 12:19:24 +00:00
jkunz 75cb613d5e v4.1.6 2026-03-05 12:12:51 +00:00
jkunz cf05229b86 fix(mod_compiler): add diagnostic logging to report dist_ts and output directory contents after each compilation task and after import-path rewriting 2026-03-05 12:12:51 +00:00
21 changed files with 3408 additions and 5150 deletions
+1
View File
@@ -4,6 +4,7 @@
coverage/ coverage/
public/ public/
pages/ pages/
test/assets/output/
# installs # installs
node_modules/ node_modules/
+34
View File
@@ -0,0 +1,34 @@
{
"@git.zone/cli": {
"projectType": "npm",
"module": {
"githost": "gitlab.com",
"gitscope": "gitzone",
"gitrepo": "tsbuild",
"description": "A tool for compiling TypeScript files using the latest nightly features, offering flexible APIs and a CLI for streamlined development.",
"npmPackagename": "@git.zone/tsbuild",
"license": "MIT",
"keywords": [
"TypeScript",
"compilation",
"nightly features",
"CLI tool",
"file compilation",
"glob patterns",
"compiler options",
"development",
"API"
]
},
"release": {
"registries": [
"https://verdaccio.lossless.digital",
"https://registry.npmjs.org"
],
"accessLevel": "public"
}
},
"@git.zone/tsdoc": {
"legal": "\n## License and Legal Information\n\nThis repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [license](./license) file.\n\n**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.\n\nUse of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District Court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n"
}
}
+5 -5
View File
@@ -1,17 +1,17 @@
{ {
"json.schemas": [ "json.schemas": [
{ {
"fileMatch": ["/npmextra.json"], "fileMatch": ["/.smartconfig.json"],
"schema": { "schema": {
"type": "object", "type": "object",
"properties": { "properties": {
"npmci": { "@ship.zone/szci": {
"type": "object", "type": "object",
"description": "settings for npmci" "description": "settings for szci"
}, },
"gitzone": { "@git.zone/cli": {
"type": "object", "type": "object",
"description": "settings for gitzone", "description": "settings for @git.zone/cli",
"properties": { "properties": {
"projectType": { "projectType": {
"type": "string", "type": "string",
+232
View File
@@ -1,5 +1,237 @@
# Changelog # Changelog
## 2026-05-09 - 4.4.1 - fix(compiler)
return emitted files from compilation results and align tests with emitted output handling
- enable TypeScript's emitted file listing during compilation so compile results include emittedFiles
- update tests to assert emitted output from compileFilesOrThrow and compileGlob
- simplify test glob inputs and ignore generated test output directories
- refresh dependency versions and CLI examples in documentation
## 2026-03-24 - 4.4.0 - feat(config)
add smartconfig metadata and update TypeScript build configuration docs
- replace npmextra.json with .smartconfig.json and include it in published files
- add a repository license file
- upgrade TypeScript and related build dependencies
- refine tsconfig path handling to avoid mutating source path mappings
- fix tspublish config caching to distinguish unloaded and missing states
- expand documentation for import path rewriting, structured logging, and the compilation pipeline
## 2026-03-06 - 4.3.0 - feat(mod_logger)
add centralized TsBuildLogger and replace ad-hoc console output with structured, colored logging
- Add ts/mod_logger/classes.logger.ts providing header/step/detail/success/error/warn/indent utilities with ANSI color support
- Export logger from ts/mod_logger/index.ts and re-export from ts/index.ts
- Replace console.log/console.error/console.warn calls in mod_cli, mod_compiler, and mod_unpack with TsBuildLogger methods for consistent, hierarchical output
- Refactor error-summary, emit and type-check output to use logger separators, colors, and structured messages
## 2026-03-05 - 4.2.6 - fix(meta)
no changes
- Current package version: 4.2.5
- No code or file changes detected in this commit; no release required
## 2026-03-05 - 4.2.5 - fix(compiler)
yield to the event loop after TypeScript emit to allow pending microtasks and I/O to settle before reading or modifying the output directory
- Added await new Promise(resolve => process.nextTick(resolve)) immediately after program.emit()
- Prevents race conditions by allowing libuv write completions and other deferred callbacks to complete before accessing the output directory
- File changed: ts/mod_compiler/classes.tscompiler.ts
## 2026-03-05 - 4.2.4 - fix(fshelpers)
remove outdated comment about using synchronous rm to avoid XFS metadata corruption
- Comment-only change in ts/mod_fs/classes.fshelpers.ts; no runtime or API behavior changes
- Bump patch version from 4.2.3 to 4.2.4
## 2026-03-05 - 4.2.3 - fix(compiler)
defer unpacking until after all compilations and remove diagnostic filesystem syncs to avoid XFS metadata visibility issues
- Queue pending unpack operations during compilation and run them after all compile tasks complete to avoid modifying output directories while other compilations are writing.
- Remove TypeScript sys interception, execSync('sync') calls, and per-unpack fs.fsyncSync usage that attempted to work around XFS delayed metadata commits; rely on performing all unpacks after compilation instead.
- Clean up noisy diagnostic code (external 'ls' comparisons, readdir snapshots) and simplify logging of unpack results.
- Remove unused imports (fs and child_process.execSync) from the compiler module.
## 2026-03-05 - 4.2.2 - fix(compiler)
force global filesystem sync to flush XFS delayed logging and add diagnostics comparing Node's readdirSync with system ls to detect directory entry inconsistencies
- Replace per-directory fs.fsyncSync loop with execSync('sync') to ensure parent B+tree metadata is flushed on XFS
- Import execSync from child_process
- Add diagnostic comparison: run ls -1 and compare its entries to fs.readdirSync; log mismatches and full entry lists for debugging Node.js caching/readdir inconsistencies
## 2026-03-05 - 4.2.1 - fix(compiler)
use TypeScript sys hooks instead of fs monkeypatching to detect writes/deletes in previous output directories
- Replace direct fs.* monkeypatching with interception of typescript.sys.writeFile, typescript.sys.deleteFile and typescript.sys.createDirectory
- Add guards for optional sys.deleteFile before overriding it and preserve original sys methods to restore after compilation
- Update diagnostic messages to reference TypeScript sys ops and add an informational message when no ops are observed
- Reduce surface area of changes by avoiding global fs changes and focusing on TypeScript's sys API for safer interception
## 2026-03-05 - 4.2.0 - feat(mod_compiler)
add diagnostic interception of fs operations to detect and report unexpected file system changes in previously compiled output directories during compilation
- Wraps fs.unlinkSync, fs.rmSync, fs.rmdirSync, fs.renameSync and fs.writeFileSync to record operations targeting other successful output directories during a compile.
- Enabled only when there are previously compiled output dirs and when not running in quiet or JSON mode; original fs methods are restored after compilation.
- Logs up to 30 intercepted operations and prints a summary count if any ops were observed; intercepted calls still perform the original fs action (diagnostic-only).
- No functional change to compilation behavior beyond additional diagnostic reporting.
## 2026-03-05 - 4.1.26 - fix(compiler)
fsync output directories after unpack to avoid XFS delayed logging causing corrupt or invisible directory entries during subsequent TypeScript emits
- Force fsync on each successful output directory (open, fsync, close) before the next compilation step.
- Prevents XFS delayed logging from making directory entries invisible or corrupted during TypeScript emit operations.
- Operation swallows errors if a directory doesn't exist yet; non-breaking fix with small additional IO.
## 2026-03-05 - 4.1.25 - fix(mod_unpack)
flush directory metadata on XFS before reading and use readdirSync-based iteration to avoid missing entries when unpacking
- Call fs.fsyncSync on destination and nested directory file descriptors to force XFS to commit delayed directory metadata (addresses XFS CIL delayed logging causing incomplete readdir/opendir results).
- Replace opendirSync/readSync loops with readdirSync-based iteration for simpler, deterministic directory listing.
- Remove unused moved counter and update diagnostic log to report nestedEntries.length for moved entry count.
## 2026-03-05 - 4.1.24 - fix(mod_unpack)
iterate directories with opendirSync/readSync to avoid missing entries on XFS and ensure directory handles are closed
- Replaced readdirSync loops with opendirSync + readSync for destination and nested directories to provide a single stable directory handle during iteration
- Added explicit closeSync() calls to close directory handles and avoid resource leaks
- Avoids partial results/missed entries that can occur when repeatedly calling readdirSync (observed on XFS with delayed metadata)
- Preserves existing renameSync move logic and increments moved counter while cleaning up the now-empty nested directory
## 2026-03-05 - 4.1.23 - fix(mod_unpack)
handle partial readdirSync results when moving nested directory entries and add diagnostic log
- Loop over readdirSync results until the nested directory is empty to avoid missing entries from partial reads
- Count moved entries and print a diagnostic message with the final destination entry count
- Keep removal of the now-empty nested directory (fs.rmdirSync) after moving contents
## 2026-03-05 - 4.1.22 - fix(mod_compiler)
improve logging of successful output directories to include a sorted list of entries and use a shortened relative path
- Adds shortDir variable to display relative path instead of repeating inline replace(this.cwd + '/')
- Appends a sorted, comma-separated list of directory entries to the log output for easier inspection
- Change located in ts/mod_compiler/classes.tscompiler.ts
## 2026-03-05 - 4.1.21 - fix(compiler)
log emitted files written outside expected destination directory for diagnostics
- Adds diagnostic logging for emitted files that are not under the configured destDir, listing up to 20 example paths and reporting the remaining count.
- Logging is conditional: only when not in quiet mode and not emitting JSON.
- Diagnostic runs after compilation (post-compile) and before unpacking of outputs; paths are trimmed using the process cwd for readability.
## 2026-03-05 - 4.1.20 - fix(mod_compiler)
add diagnostic snapshots for output directories around clear and compile steps
- Introduce diagSnap helper to log entry and directory counts for successful output directories when not in quiet or JSON mode
- Call diagSnap before clearing the destination directory, after clearing, and after compilation to aid debugging of missing or unexpected outputs
- Behavior for emitted files and unpacking is unchanged; this is observational/logging-only instrumentation
## 2026-03-05 - 4.1.19 - fix(mod_fs)
use synchronous rm to avoid XFS metadata corruption when removing directories
- Replaced async fs.promises.rm with synchronous fs.rmSync in removeDirectory to avoid observed XFS metadata corruption affecting sibling entries under libuv thread-pool and signal pressure
- Retains previous options: recursive, force, maxRetries, retryDelay
- Adds inline comment documenting the rationale for using a synchronous removal
## 2026-03-05 - 4.1.18 - fix(mod_compiler)
add diagnostic logging of output directory states after compilation and after import-path rewriting to aid debugging
- Imported fs to allow reading output directories for diagnostics
- Logs entries and directory counts for each successful output directory both pre- and post-import-path-rewrite
- Diagnostics are gated by !isQuiet && !isJson and are read-only (no behavior change)
- Tags used: 'diag' (post-compilation) and 'diag-post-rewrite' (after rewriting) to help identify missing or unexpected output folders
## 2026-03-05 - 4.1.17 - fix(tsunpacker)
use synchronous fs operations in tsunpacker to avoid readdir race conditions
- Replaced async fs.promises.readdir/rename/rm/rmdir loops with fs.readdirSync/renameSync/rmSync/rmdirSync
- Removed readdir retry loops that attempted to handle partial/stale readdir results
- Updated comment to document rationale: avoid race conditions under signal pressure and XFS metadata lag
- Note: function remains async but now performs blocking sync filesystem calls which may block the event loop during unpack
## 2026-03-05 - 4.1.16 - fix(mod_unpack)
handle partial readdir results from signal-interrupted getdents64 when unpacking to ensure sibling removal and nested moves complete
- Loop readdir calls for destination directory until only the source folder remains to avoid partial-listing leftovers
- Loop readdir calls for nested directory and repeatedly rename entries until the nested directory is empty
- Prevents leftover files and incomplete moves when readdir returns partial results under signals
## 2026-03-05 - 4.1.15 - fix(mod_unpack)
flatten nested output directory without temporary rename steps to avoid race conditions
- Replace rename-rm-rename strategy with: remove sibling entries in destination, move nested source entries up into the destination, then remove the now-empty nested folder.
- Avoid creating temporary sibling directories and avoid removing the destination directory to reduce filesystem race conditions and metadata lag issues (XFS/NFS/etc.).
- Remove removed removeEmptyDirectory helper and stop using FsHelpers.move/removeDirectory in unpack; import and use fs.promises methods (readdir, rm, rename, rmdir) directly.
## 2026-03-05 - 4.1.14 - fix(fs)
replace execSync and fsync workarounds with atomic async FsHelpers operations to avoid XFS races and shell dependencies
- Removed child_process.execSync usage and shell mv/rm commands in mod_unpack and mod_compiler.
- Removed syncDirectoryTree and fsync-based workaround from the compiler module.
- Use FsHelpers.move and FsHelpers.removeDirectory (async rename/remove) for atomic filesystem operations during unpack.
- Await performUnpack directly and simplify unpack flow to improve portability and reliability on XFS and other filesystems.
## 2026-03-05 - 4.1.13 - fix(mod_unpack)
Use child_process.execSync (mv/rm) to perform unpack atomically, replacing async fs operations and logs to avoid ENOENT/partial directory listings on XFS
- Replaced async fs.promises.rename/rm and readdir/stat debugging with execSync rm -rf and mv operations for sequential, atomic moves
- Imported execSync from child_process and removed verbose console logging and extra fs checks
- Addresses race conditions observed on filesystems like XFS where libuv async operations can return partial results or ENOENT errors
## 2026-03-05 - 4.1.12 - fix(mod_compiler)
replace runtime require calls with top-level imports and use execSync/path.join for filesystem sync and traversal
- Added top-level imports: path and execSync from child_process
- Replaced require('child_process').execSync('sync') with execSync('sync') to force fs sync
- Replaced require('path').join(...) with path.join(...) when recursing directories
- Refactor is purely local/maintenance-focused (consistency and slight performance/readability improvement); no functional change expected
## 2026-03-05 - 4.1.11 - fix(mod_compiler)
flush directory entries before unpack to avoid XFS delayed-log causing partial readdir results
- Add fs import and call child_process.execSync('sync') before unpacking compiled output to force kernel-level sync
- Add syncDirectoryTree(dir) to recursively open and fsync directory file descriptors and traverse subdirectories
- Call syncDirectoryTree on the destination directory before performing unpack to ensure TypeScript writeFileSync entries are committed (prevents partial readdir results on XFS)
- Errors during directory sync are ignored to avoid breaking normal flow
## 2026-03-05 - 4.1.10 - fix(unpack)
use atomic renames to flatten nested output and make unpacking more reliable
- Replace per-entry moves with an atomic 3-step strategy: rename nested dir to a temp location, remove the destination dir, then rename temp back to the destination to avoid partial readdir/move under signal pressure.
- FsHelpers.move switched from sync rename to fs.promises.rename to work with async flows.
- Use fs.promises.rm with retries and explicit temp-dir cleanup to handle previous failed runs.
- Add diagnostic logging and verification of intermediate states.
- Removed the older removeSiblingDirectories and moveNestedContentsUp methods in favor of the new rename-based approach.
## 2026-03-05 - 4.1.9 - fix(fs)
improve filesystem helpers: use sync rename for reliability on certain filesystems; retry rmdir with delays and avoid recursive rm; bump @push.rocks/smartfs to ^1.3.2
- move(): use fs.renameSync to improve reliability on XFS/mounted filesystems where async rename can be interrupted by process managers
- removeEmptyDirectory(): retry fs.promises.rmdir up to 5 attempts with incremental delays; return on ENOENT; avoid recursive rm on ENOTEMPTY to prevent removing still-valid references
- Dependency bump: @push.rocks/smartfs from ^1.3.1 to ^1.3.2
## 2026-03-05 - 4.1.8 - fix(unpack)
catch unpack errors and add verbose unpack logging
- Wrap performUnpack call in the compiler to catch and log exceptions so unpack failures don't crash the process
- Log the number of directories and files being unpacked when moving nested contents to aid debugging
## 2026-03-05 - 4.1.7 - fix(fs/compiler/unpack)
robustify directory removal and remove noisy diagnostic logging
- Use fs.promises.rm with force, maxRetries and retryDelay in removeDirectory to reduce intermittent failures during removals.
- Handle ENOTEMPTY in removeEmptyDirectory by falling back to a recursive rm and ignore ENOENT to avoid errors from transient filesystem metadata lag.
- Remove several diagnostic/verbose console logs in the compiler and unpacker paths to reduce noisy output and simplify flow.
- Short-circuit unpack logic when no nesting is detected to avoid unnecessary work and logs.
- No public API or behavior-breaking changes; suitable for a patch release.
## 2026-03-05 - 4.1.6 - fix(mod_compiler)
add diagnostic logging to report dist_ts and output directory contents after each compilation task and after import-path rewriting
- Adds DIAG-CROSSTASK logs to inspect dist_ts subdirectories after each task when not in quiet or JSON mode
- Adds DIAG-FINAL logs to report directory names and file counts for each successful output dir after import-path rewriting
- Diagnostics use dynamic fs import and are non-intrusive: gated by isQuiet/isJson and errors are ignored
## 2026-03-05 - 4.1.5 - fix(diagnostics) ## 2026-03-05 - 4.1.5 - fix(diagnostics)
add diagnostic logging around compilation and unpack to aid troubleshooting add diagnostic logging around compilation and unpack to aid troubleshooting
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Task Venture Capital GmbH
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-37
View File
@@ -1,37 +0,0 @@
{
"@git.zone/cli": {
"projectType": "npm",
"module": {
"githost": "gitlab.com",
"gitscope": "gitzone",
"gitrepo": "tsbuild",
"description": "A tool for compiling TypeScript files using the latest nightly features, offering flexible APIs and a CLI for streamlined development.",
"npmPackagename": "@git.zone/tsbuild",
"license": "MIT",
"keywords": [
"TypeScript",
"compilation",
"nightly features",
"CLI tool",
"file compilation",
"glob patterns",
"compiler options",
"development",
"API"
]
},
"release": {
"registries": [
"https://verdaccio.lossless.digital",
"https://registry.npmjs.org"
],
"accessLevel": "public"
}
},
"@git.zone/tsdoc": {
"legal": "\n## License and Legal Information\n\nThis repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository. \n\n**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or if you require further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n"
},
"@ship.zone/szci": {
"npmGlobalTools": []
}
}
+13 -13
View File
@@ -1,6 +1,6 @@
{ {
"name": "@git.zone/tsbuild", "name": "@git.zone/tsbuild",
"version": "4.1.5", "version": "4.4.1",
"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",
@@ -36,21 +36,21 @@
}, },
"homepage": "https://code.foss.global/git.zone/tsbuild#README", "homepage": "https://code.foss.global/git.zone/tsbuild#README",
"dependencies": { "dependencies": {
"@git.zone/tspublish": "^1.11.2", "@git.zone/tspublish": "^1.11.6",
"@push.rocks/early": "^4.0.4", "@push.rocks/early": "^4.0.4",
"@push.rocks/smartcli": "^4.0.20", "@push.rocks/smartcli": "^4.0.21",
"@push.rocks/smartdelay": "^3.0.5", "@push.rocks/smartdelay": "^3.1.0",
"@push.rocks/smartfile": "^13.1.2", "@push.rocks/smartfile": "^13.1.3",
"@push.rocks/smartfs": "^1.3.1", "@push.rocks/smartfs": "^1.5.1",
"@push.rocks/smartlog": "^3.2.1", "@push.rocks/smartlog": "^3.2.2",
"@push.rocks/smartpath": "^6.0.0", "@push.rocks/smartpath": "^6.0.0",
"@push.rocks/smartpromise": "^4.2.3", "@push.rocks/smartpromise": "^4.2.4",
"typescript": "5.9.3" "typescript": "^6.0.3"
}, },
"devDependencies": { "devDependencies": {
"@git.zone/tsrun": "^2.0.1", "@git.zone/tsrun": "^2.0.3",
"@git.zone/tstest": "^3.2.0", "@git.zone/tstest": "^3.6.5",
"@types/node": "^25.3.3" "@types/node": "^25.6.2"
}, },
"files": [ "files": [
"ts/**/*", "ts/**/*",
@@ -61,7 +61,7 @@
"dist_ts_web/**/*", "dist_ts_web/**/*",
"assets/**/*", "assets/**/*",
"cli.js", "cli.js",
"npmextra.json", ".smartconfig.json",
"readme.md" "readme.md"
], ],
"browserslist": [ "browserslist": [
+2661 -4706
View File
File diff suppressed because it is too large Load Diff
+49 -28
View File
@@ -7,11 +7,13 @@
2. **TsConfig** - TypeScript configuration management (tsconfig.json handling) 2. **TsConfig** - TypeScript configuration management (tsconfig.json handling)
3. **TsPublishConfig** - TsPublish configuration (tspublish.json handling) 3. **TsPublishConfig** - TsPublish configuration (tspublish.json handling)
4. **TsUnpacker** - Output directory flattening 4. **TsUnpacker** - Output directory flattening
5. **FsHelpers** - Filesystem utilities (static methods) 5. **TsPathRewriter** - Cross-module import path rewriting in compiled output
6. **TsBuildCli** - CLI command handler 6. **FsHelpers** - Filesystem utilities (static methods)
7. **TsBuildLogger** - Centralized structured console output (4-level hierarchy)
8. **TsBuildCli** - CLI command handler
### CLI Commands (5) ### CLI Commands (5)
1. **tsbuild** (default) - Compiles ./ts/**/*.ts ./dist_ts/ 1. **tsbuild** (default) - Compiles ./ts/**/*.ts -> ./dist_ts/
2. **tsbuild custom <dir1> <dir2>** - Custom directory compilation 2. **tsbuild custom <dir1> <dir2>** - Custom directory compilation
3. **tsbuild tsfolders** - Auto-discover ts_* folders, compile in order 3. **tsbuild tsfolders** - Auto-discover ts_* folders, compile in order
4. **tsbuild emitcheck <pattern>** - Validate emit without output 4. **tsbuild emitcheck <pattern>** - Validate emit without output
@@ -32,6 +34,7 @@
ts/ ts/
index.ts # Main entry, re-exports all modules index.ts # Main entry, re-exports all modules
plugins.ts # Dependency imports only plugins.ts # Dependency imports only
00_commitinfo_data.ts # Commit info data
mod_fs/ mod_fs/
index.ts # exports index.ts # exports
@@ -46,9 +49,17 @@ ts/
index.ts # exports index.ts # exports
classes.tsunpacker.ts # TsUnpacker - output flattening classes.tsunpacker.ts # TsUnpacker - output flattening
mod_pathrewrite/
index.ts # exports
classes.tspathrewriter.ts # TsPathRewriter - import path rewriting
mod_logger/
index.ts # exports
classes.logger.ts # TsBuildLogger - structured console output
mod_compiler/ mod_compiler/
index.ts # exports index.ts # exports
classes.tscompiler.ts # TsCompiler + legacy compatibility functions classes.tscompiler.ts # TsCompiler - main compilation engine
mod_cli/ mod_cli/
index.ts # exports index.ts # exports
@@ -62,6 +73,7 @@ ts/
- Type checking with `checkTypes()`, `checkEmit()` - Type checking with `checkTypes()`, `checkEmit()`
- Configuration via TsConfig - Configuration via TsConfig
- Automatic unpacking via TsUnpacker - Automatic unpacking via TsUnpacker
- Import path rewriting via TsPathRewriter (in compileGlob)
**TsConfig** (`mod_config/classes.tsconfig.ts`) **TsConfig** (`mod_config/classes.tsconfig.ts`)
- Load and parse tsconfig.json - Load and parse tsconfig.json
@@ -78,8 +90,18 @@ ts/
- Flatten output directories - Flatten output directories
- Configurable via TsPublishConfig - Configurable via TsPublishConfig
**TsPathRewriter** (`mod_pathrewrite/classes.tspathrewriter.ts`)
- Auto-detect ts_* folders in project
- Rewrite ES module imports, dynamic imports, and require() calls
- Transforms `../ts_*` references to `../dist_ts_*` in compiled output
**TsBuildLogger** (`mod_logger/classes.logger.ts`)
- 4-level visual hierarchy: HEADER, STEP, DETAIL, SUCCESS/ERROR/WARN
- ANSI color output
- Static methods (no instantiation needed)
**FsHelpers** (`mod_fs/classes.fshelpers.ts`) **FsHelpers** (`mod_fs/classes.fshelpers.ts`)
- `listFilesWithGlob()` - Glob pattern file listing - `listFilesWithGlob()` - Glob pattern file listing via smartfs
- `extractSourceFolder()` - Pattern parsing - `extractSourceFolder()` - Pattern parsing
- File/directory existence checks - File/directory existence checks
- Directory operations - Directory operations
@@ -103,8 +125,8 @@ Cannot be overridden by tsconfig.json alone:
- `inlineSourceMap: true` - Debugging - `inlineSourceMap: true` - Debugging
### Path Transformation ### Path Transformation
- Automatic: `./ts_interfaces` `./dist_ts_interfaces` - Automatic: `./ts_interfaces` -> `./dist_ts_interfaces`
- In tsconfig paths: `./ts_*` `./dist_ts_*` (first array element only) - In tsconfig paths: `./ts_*` -> `./dist_ts_*` (first array element only)
## Default Compiler Options ## Default Compiler Options
- Module: NodeNext (ESM with CommonJS fallback) - Module: NodeNext (ESM with CommonJS fallback)
@@ -137,14 +159,16 @@ interface ICompileResult {
``` ```
## Dependencies Used ## Dependencies Used
- @git.zone/tspublish@^1.10.3 - Module ordering - @git.zone/tspublish@^1.11.3 - Module ordering
- @push.rocks/smartcli@^4.0.19 - CLI framework - @push.rocks/early@^4.0.4 - Early initialization
- @push.rocks/smartcli@^4.0.20 - CLI framework
- @push.rocks/smartdelay@^3.0.5 - Delay utilities
- @push.rocks/smartfile@^13.1.2 - File content handling - @push.rocks/smartfile@^13.1.2 - File content handling
- @push.rocks/smartfs@^1.2.0 - Filesystem operations - @push.rocks/smartfs@^1.5.0 - Filesystem operations
- @push.rocks/smartlog@^3.2.1 - Logging
- @push.rocks/smartpath@^6.0.0 - Path transformation utilities - @push.rocks/smartpath@^6.0.0 - Path transformation utilities
- @push.rocks/smartpromise@^4.2.3 - Promise utilities - @push.rocks/smartpromise@^4.2.3 - Promise utilities
- @push.rocks/smartdelay@^3.0.5 - Delay utilities - typescript@^6.0.2 - TypeScript compiler
- typescript@5.9.3 - TypeScript compiler
### smartfs Usage ### smartfs Usage
- File listing: `smartfs.directory(path).recursive().filter(pattern).list()` - File listing: `smartfs.directory(path).recursive().filter(pattern).list()`
@@ -152,29 +176,32 @@ interface ICompileResult {
- Directory existence: `smartfs.directory(path).exists()` - Directory existence: `smartfs.directory(path).exists()`
- FsHelpers wraps smartfs for glob pattern support - FsHelpers wraps smartfs for glob pattern support
## Compilation Pipeline (compileGlob)
1. **Phase 1: Resolve & Clean** - Resolve glob patterns, list files, clear all output dirs
2. **Phase 2: Compile** - Compile each task sequentially with error tracking
3. **Phase 3: Unpack** - Flatten nested output directories for successful compilations
4. **Phase 4: Path Rewrite** - Rewrite cross-module import paths in output dirs
## Unpack Feature ## Unpack Feature
When TypeScript compiles files that import from sibling directories, it creates nested output: When TypeScript compiles files that import from sibling directories, it creates nested output:
``` ```
dist_ts_core/ dist_ts_core/
ts_core/ nested output ts_core/ <- nested output
ts_shared/ pulled-in dependency ts_shared/ <- pulled-in dependency
``` ```
The unpack feature automatically flattens this to: The unpack feature automatically flattens this to:
``` ```
dist_ts_core/ dist_ts_core/
index.js flat index.js <- flat
``` ```
### Configuration ### Configuration
- Reads `tspublish.json` from source folder - Reads `tspublish.json` from source folder
- `unpack: true` (default if not present) flatten output - `unpack: true` (default if not present) -> flatten output
- `unpack: false` skip unpacking - `unpack: false` -> skip unpacking
### Implementation
- `TsUnpacker` class in `mod_unpack/classes.tsunpacker.ts`
- Called automatically after each successful compilation in `TsCompiler.compileGlob()`
## Special Behaviors ## Special Behaviors
@@ -206,10 +233,4 @@ Two-phase check:
- Protected options ensure integrity - Protected options ensure integrity
- Pre-emit checks before emit phase - Pre-emit checks before emit phase
- CLI exit code handling (0=success, 1=error) - CLI exit code handling (0=success, 1=error)
- Event loop yield after emit for XFS filesystem reliability
## 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
+136 -115
View File
@@ -1,57 +1,54 @@
# @git.zone/tsbuild # @git.zone/tsbuild
A powerful, modern TypeScript build tool with smart defaults, full tsconfig.json support, and automatic output directory management. 🚀 A powerful, modern TypeScript build tool with smart defaults, full tsconfig.json support, automatic output directory management, and cross-module import path rewriting.
## Issue Reporting and Security ## Issue Reporting and Security
For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly. For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
## 📦 Install ## Install
```bash ```bash
# Using pnpm (recommended) pnpm add --save-dev @git.zone/tsbuild
pnpm install @git.zone/tsbuild --save-dev
# Using npm
npm install @git.zone/tsbuild --save-dev
``` ```
## Why tsbuild? ## Why tsbuild?
| Feature | Description | | Feature | Description |
|---------|-------------| |---------|-------------|
| 🔧 **Smart tsconfig.json Integration** | Respects all your compiler options with intelligent merging | | **Smart tsconfig.json Integration** | Respects all your compiler options with intelligent merging |
| 🛡️ **Protected Defaults** | Critical build settings are safeguarded while staying flexible | | **Protected Defaults** | Critical build settings are safeguarded while staying flexible |
| 🎯 **Zero Config** | Works perfectly without tsconfig.json | | **Zero Config** | Works perfectly without tsconfig.json |
| 🌐 **Glob Pattern Support** | Compile multiple directories with a single command | | **Glob Pattern Support** | Compile multiple directories with a single command |
| 📊 **Dependency-Aware** | Automatically orders compilation based on module dependencies | | **Dependency-Aware** | Automatically orders compilation based on module dependencies |
| **Type Checking** | Validate code without emitting files | | **Type Checking** | Validate code without emitting files |
| 🧹 **Clean Builds** | Automatically clears output directories before compilation | | **Clean Builds** | Automatically clears output directories before compilation |
| 📁 **Auto-Unpack** | Flattens nested output directories automatically | | **Auto-Unpack** | Flattens nested output directories automatically |
| 🔄 **CI/CD Ready** | JSON output mode and proper exit codes | | **Import Path Rewriting** | Rewrites cross-module imports to point at compiled output |
| **Modern Defaults** | ESNext, NodeNext modules, decorators out of the box | | **CI/CD Ready** | JSON output mode and proper exit codes |
| **Modern Defaults** | ESNext, NodeNext modules, decorators out of the box |
## 🚀 Quick Start ## Quick Start
### CLI Usage ### CLI Usage
**Compile your TypeScript project:** **Compile your TypeScript project:**
```bash ```bash
npx tsbuild pnpm exec tsbuild
``` ```
Compiles `./ts/**/*.ts` to `./dist_ts/` Compiles `./ts/**/*.ts` to `./dist_ts/`
**Custom directories:** **Custom directories:**
```bash ```bash
npx tsbuild custom src utils pnpm exec tsbuild custom src utils
``` ```
Compiles: Compiles:
- `./src/**/*.ts` `./dist_src/` - `./src/**/*.ts` -> `./dist_src/`
- `./utils/**/*.ts` `./dist_utils/` - `./utils/**/*.ts` -> `./dist_utils/`
**Auto-discover and compile in dependency order:** **Auto-discover and compile in dependency order:**
```bash ```bash
npx tsbuild tsfolders pnpm exec tsbuild tsfolders
``` ```
Finds all `ts_*` folders and compiles them respecting dependencies. Finds all `ts_*` folders and compiles them respecting dependencies.
@@ -97,12 +94,12 @@ await compiler.compileGlob({
}); });
``` ```
## 🛠️ CLI Commands ## CLI Commands
### 1. Default Build ### 1. Default Build
```bash ```bash
npx tsbuild [options] pnpm exec tsbuild [options]
``` ```
Compiles all TypeScript files from `./ts/` to `./dist_ts/` Compiles all TypeScript files from `./ts/` to `./dist_ts/`
@@ -120,39 +117,39 @@ Compiles all TypeScript files from `./ts/` to `./dist_ts/`
**Examples:** **Examples:**
```bash ```bash
# Standard build # Standard build
npx tsbuild pnpm exec tsbuild
# Build with JSON output for CI # Build with JSON output for CI
npx tsbuild --json --quiet pnpm exec tsbuild --json --quiet
# CommonJS build # CommonJS build
npx tsbuild --commonjs pnpm exec tsbuild --commonjs
# Strict mode # Strict mode
npx tsbuild --disallowimplicitany pnpm exec tsbuild --disallowimplicitany
``` ```
### 2. Custom Directories ### 2. Custom Directories
```bash ```bash
npx tsbuild custom <dir1> <dir2> ... [options] pnpm exec tsbuild custom <dir1> <dir2> ... [options]
``` ```
Compile specific directories to their corresponding `dist_` folders. Compile specific directories to their corresponding `dist_` folders.
```bash ```bash
# Compile src and utils # Compile src and utils
npx tsbuild custom src utils pnpm exec tsbuild custom src utils
# Creates: ./dist_src/ and ./dist_utils/ # Creates: ./dist_src/ and ./dist_utils/
# Multiple directories with options # Multiple directories with options
npx tsbuild custom api models services --commonjs pnpm exec tsbuild custom api models services --commonjs
``` ```
### 3. TSFolders (Dependency-Aware) 📊 ### 3. TSFolders (Dependency-Aware)
```bash ```bash
npx tsbuild tsfolders [options] pnpm exec tsbuild tsfolders [options]
``` ```
Automatically discovers and compiles all `ts_*` folders in dependency order: Automatically discovers and compiles all `ts_*` folders in dependency order:
@@ -165,45 +162,45 @@ Automatically discovers and compiles all `ts_*` folders in dependency order:
**Example output:** **Example output:**
``` ```
TypeScript Folder Compilation Plan (5 folders) TypeScript Folder Compilation Plan (5 folders)
1/5 ts_interfaces 1. ts_interfaces
2/5 ts_shared 2. ts_shared
3/5 ts_core 3. ts_core
4/5 ts_utils 4. ts_utils
5/5 ts_modules 5. ts_modules
``` ```
### 4. Emit Check ### 4. Emit Check
```bash ```bash
npx tsbuild emitcheck <file_or_pattern> [more...] [options] pnpm exec tsbuild emitcheck <file_or_pattern> [more...] [options]
``` ```
Validates TypeScript files can be compiled without actually emitting them. Validates TypeScript files can be compiled without actually emitting them.
```bash ```bash
# Check specific files # Check specific files
npx tsbuild emitcheck src/main.ts src/utils.ts pnpm exec tsbuild emitcheck src/main.ts src/utils.ts
# Check with glob patterns # Check with glob patterns
npx tsbuild emitcheck "src/**/*.ts" "test/**/*.ts" pnpm exec tsbuild emitcheck "src/**/*.ts" "test/**/*.ts"
``` ```
**Exit codes:** **Exit codes:**
- `0` - All files can be emitted - `0` - All files can be emitted
- `1` - One or more files have errors - `1` - One or more files have errors
### 5. Type Check 🔍 ### 5. Type Check
```bash ```bash
npx tsbuild check [pattern] [more...] [options] pnpm exec tsbuild check [pattern] [more...] [options]
``` ```
Performs type checking without emitting files. Performs type checking without emitting files.
**With arguments:** Check specified files/patterns **With arguments:** Check specified files/patterns
```bash ```bash
npx tsbuild check "ts/**/*.ts" pnpm exec tsbuild check "ts/**/*.ts"
npx tsbuild check "src/**/*.ts" "test/**/*.ts" pnpm exec tsbuild check "src/**/*.ts" "test/**/*.ts"
``` ```
**Without arguments:** Two-phase default check **Without arguments:** Two-phase default check
@@ -211,14 +208,14 @@ npx tsbuild check "src/**/*.ts" "test/**/*.ts"
2. Phase 2: Type check `test/**/*` (relaxed, skipLibCheck: true) 2. Phase 2: Type check `test/**/*` (relaxed, skipLibCheck: true)
```bash ```bash
npx tsbuild check pnpm exec tsbuild check
# Running default type checking sequence... # Running default type checking sequence...
# Checking ts/**/* files... # Checking ts/**/* files...
# Checking test/**/* files with --skiplibcheck... # Checking test/**/* files with --skiplibcheck...
# All default type checks passed! # All default type checks passed!
``` ```
## 📖 API Reference ## API Reference
### TsCompiler Class ### TsCompiler Class
@@ -285,7 +282,7 @@ try {
#### compileGlob(globPatterns, customOptions?) #### compileGlob(globPatterns, customOptions?)
Compile multiple glob patterns to different destinations. Automatically clears output directories before compilation and unpacks nested output. Compile multiple glob patterns to different destinations. Automatically clears output directories before compilation, unpacks nested output, and rewrites cross-module import paths.
```typescript ```typescript
const result = await compiler.compileGlob({ const result = await compiler.compileGlob({
@@ -367,10 +364,28 @@ Flattens nested TypeScript output directories.
```typescript ```typescript
import { TsUnpacker } from '@git.zone/tsbuild'; import { TsUnpacker } from '@git.zone/tsbuild';
const unpacker = new TsUnpacker('./dist_ts', './ts'); const unpacker = new TsUnpacker('ts_core', './dist_ts_core');
await unpacker.unpack(); await unpacker.unpack();
``` ```
#### TsPathRewriter
Rewrites cross-module import paths in compiled output files. When TypeScript compiles files that import from sibling directories (e.g., `../ts_shared/helper.js`), TsPathRewriter rewrites those paths to reference the compiled output directories (`../dist_ts_shared/helper.js`).
```typescript
import { TsPathRewriter } from '@git.zone/tsbuild';
// Auto-detect all ts_* folders in the project
const rewriter = await TsPathRewriter.fromProjectDirectory(process.cwd());
const filesModified = await rewriter.rewriteDirectory('./dist_ts_core');
// Or from explicit glob patterns
const rewriter2 = TsPathRewriter.fromGlobPatterns({
'./ts_core/**/*.ts': './dist_ts_core',
'./ts_shared/**/*.ts': './dist_ts_shared',
});
```
#### FsHelpers #### FsHelpers
Static filesystem utilities. Static filesystem utilities.
@@ -398,7 +413,7 @@ const cli = new TsBuildCli('/path/to/project');
cli.run(); cli.run();
``` ```
## ⚙️ Configuration ## Configuration
### tsconfig.json Support ### tsconfig.json Support
@@ -419,19 +434,19 @@ tsbuild fully supports all compiler options from `tsconfig.json`. Your project c
} }
``` ```
### Configuration Priority (5 Levels) 📋 ### Configuration Priority (5 Levels)
When multiple configuration sources exist, they merge in this order (later overrides earlier): When multiple configuration sources exist, they merge in this order (later overrides earlier):
| Priority | Source | Description | | Priority | Source | Description |
|----------|--------|-------------| |----------|--------|-------------|
| 1️⃣ | **Default Options** | tsbuild's sensible defaults | | 1 | **Default Options** | tsbuild's sensible defaults |
| 2️⃣ | **tsconfig.json** | All options from your tsconfig.json (if present) | | 2 | **tsconfig.json** | All options from your tsconfig.json (if present) |
| 3️⃣ | **Protected Defaults** | Critical options for build integrity | | 3 | **Protected Defaults** | Critical options for build integrity |
| 4️⃣ | **Programmatic Options** | Options passed to API functions | | 4 | **Programmatic Options** | Options passed to API functions |
| 5️⃣ | **CLI Flags** | Command-line arguments (highest priority) | | 5 | **CLI Flags** | Command-line arguments (highest priority) |
### Protected Options 🛡️ ### Protected Options
These options cannot be overridden by tsconfig.json alone (but can be overridden programmatically or via CLI): These options cannot be overridden by tsconfig.json alone (but can be overridden programmatically or via CLI):
@@ -449,8 +464,6 @@ When no tsconfig.json exists:
```typescript ```typescript
{ {
declaration: true, // Generate .d.ts files declaration: true, // Generate .d.ts files
emitDecoratorMetadata: true, // Support DI frameworks
experimentalDecorators: true, // Enable decorators
inlineSourceMap: true, // Debug-friendly inlineSourceMap: true, // Debug-friendly
noEmitOnError: true, // Fail-fast on errors noEmitOnError: true, // Fail-fast on errors
outDir: 'dist_ts/', // Output directory outDir: 'dist_ts/', // Output directory
@@ -463,7 +476,7 @@ When no tsconfig.json exists:
} }
``` ```
### Path Transformation 🔄 ### Path Transformation
tsbuild automatically transforms path mappings: tsbuild automatically transforms path mappings:
@@ -480,12 +493,12 @@ tsbuild automatically transforms path mappings:
**Automatic transformation:** **Automatic transformation:**
``` ```
./ts_models/* ./dist_ts_models/* ./ts_models/* -> ./dist_ts_models/*
``` ```
## Features ## Features
### Clean Builds 🧹 ### Clean Builds
Output directories are automatically cleared before compilation: Output directories are automatically cleared before compilation:
@@ -496,7 +509,23 @@ Compiling 14 files from ./ts/**/*.ts
This ensures no stale files remain from previous builds. This ensures no stale files remain from previous builds.
### Monorepo Support with tspublish 📦 ### Import Path Rewriting
When working with multi-module projects (multiple `ts_*` folders), TypeScript compiles import paths relative to source directories. After compilation and unpacking, these paths would be wrong because the directory structure changes.
tsbuild automatically detects all `ts_*` folders in the project and rewrites import paths in the compiled output:
```
# Source code
import { helper } from '../ts_shared/helper.js';
# Compiled output (after rewriting)
import { helper } from '../dist_ts_shared/helper.js';
```
This works for ES module imports, dynamic `import()` calls, and CommonJS `require()` statements. The rewriting happens automatically as part of `compileGlob()`.
### Monorepo Support with tspublish
tsbuild is designed to work seamlessly with [@git.zone/tspublish](https://code.foss.global/git.zone/tspublish) for monorepo workflows. This enables building and publishing multiple packages from a single repository. tsbuild is designed to work seamlessly with [@git.zone/tspublish](https://code.foss.global/git.zone/tspublish) for monorepo workflows. This enables building and publishing multiple packages from a single repository.
@@ -504,42 +533,46 @@ tsbuild is designed to work seamlessly with [@git.zone/tspublish](https://code.f
``` ```
my-project/ my-project/
├── ts/ # Main package source ts/ # Main package source
├── ts_interfaces/ # Shared interfaces (order: 1) ts_interfaces/ # Shared interfaces (order: 1)
├── ts_shared/ # Shared utilities (order: 2) ts_shared/ # Shared utilities (order: 2)
├── ts_core/ # Core logic (order: 3) ts_core/ # Core logic (order: 3)
├── ts_web/ # Web-specific code (order: 4) ts_web/ # Web-specific code (order: 4)
└── ts_node/ # Node-specific code (order: 5) ts_node/ # Node-specific code (order: 5)
``` ```
Each `ts_*` folder can contain its own `tspublish.json` to configure compilation and publishing behavior. Each `ts_*` folder can contain its own `tspublish.json` to configure compilation and publishing behavior.
#### tspublish.json Configuration #### tspublish.json Configuration
Create a `tspublish.json` in each `ts_*` folder: Create a `tspublish.json` in each publishable `ts_*` folder. `@git.zone/tspublish` expects publishing fields, while tsbuild additionally reads `order` and `unpack` for build ordering and output flattening:
```json ```json
{ {
"name": "@myorg/core", "name": "@myorg/core",
"order": 3, "order": 3,
"unpack": true, "unpack": true,
"dependencies": ["ts_interfaces", "ts_shared"] "dependencies": ["@myorg/interfaces", "@myorg/shared"],
"registries": ["useBase"],
"bin": []
} }
``` ```
| Option | Type | Default | Description | | Option | Type | Default | Description |
|--------|------|---------|-------------| |--------|------|---------|-------------|
| `name` | string | | Package name for publishing | | `name` | string | -- | Package name for publishing |
| `order` | number | `Infinity` | Build sequence (lower builds first) | | `order` | number | `Infinity` | Build sequence (lower builds first) |
| `unpack` | boolean | `true` | Flatten nested output directories | | `unpack` | boolean | `true` | Flatten nested output directories |
| `dependencies` | string[] | — | Monorepo dependencies to bundle | | `dependencies` | string[] | `[]` | Published package dependencies resolved from the monorepo `package.json` |
| `registries` | string[] | -- | Publish registries, for example `useBase` to read `.smartconfig.json` |
| `bin` | string[] | `[]` | CLI binary names generated by `@git.zone/tspublish` |
#### Build Order #### Build Order
The `tsfolders` command respects the `order` property: The `tsfolders` command respects the `order` property:
```bash ```bash
npx tsbuild tsfolders pnpm exec tsbuild tsfolders
``` ```
**Default ordering (without tspublish.json):** **Default ordering (without tspublish.json):**
@@ -548,31 +581,21 @@ npx tsbuild tsfolders
3. Other folders sorted by `order` property 3. Other folders sorted by `order` property
4. Folders without `order` are built last 4. Folders without `order` are built last
**Example output:** ### Auto-Unpack
```
TypeScript Folder Compilation Plan (5 folders)
1/5 ts_interfaces (order: 1)
2/5 ts_shared (order: 2)
3/5 ts_core (order: 3)
4/5 ts_web (order: 4)
5/5 ts_node (order: 5)
```
### Auto-Unpack 📁
When TypeScript compiles files that import from sibling directories, it creates nested output: When TypeScript compiles files that import from sibling directories, it creates nested output:
``` ```
dist_ts_core/ dist_ts_core/
ts_core/ nested output ts_core/ <-- nested output
ts_shared/ pulled-in dependency ts_shared/ <-- pulled-in dependency
``` ```
tsbuild automatically flattens this to: tsbuild automatically flattens this to:
``` ```
dist_ts_core/ dist_ts_core/
index.js flat, clean structure index.js <-- flat, clean structure
``` ```
This is especially important for monorepos where packages import from each other. This is especially important for monorepos where packages import from each other.
@@ -591,11 +614,11 @@ This is especially important for monorepos where packages import from each other
- The source folder's contents (`ts_core/`) are moved to the root of `dist_ts_core/` - The source folder's contents (`ts_core/`) are moved to the root of `dist_ts_core/`
- Sibling folders (`ts_shared/`) that were pulled in are removed (they have their own dist) - Sibling folders (`ts_shared/`) that were pulled in are removed (they have their own dist)
### Decorator Support 🎨 ### Decorator Support
tsbuild supports both **TC39 standard decorators** (recommended) and legacy experimental decorators. tsbuild supports both **TC39 standard decorators** (recommended) and legacy experimental decorators.
**TC39 Standard Decorators (Preferred)** 🌟 **TC39 Standard Decorators (Preferred)**
We strongly recommend using TC39 standard decorators for all new code. They are the official JavaScript standard and provide better semantics: We strongly recommend using TC39 standard decorators for all new code. They are the official JavaScript standard and provide better semantics:
@@ -618,19 +641,8 @@ class UserService {
**Legacy Decorators (Backwards Compatibility)** **Legacy Decorators (Backwards Compatibility)**
For existing projects using frameworks like NestJS, TypeORM, or Angular that still require experimental decorators: For existing projects using frameworks that still require experimental decorators:
```typescript
@Injectable()
class UserService {
constructor(
@Inject('CONFIG') private config: Config,
private logger: Logger
) {}
}
```
Enable legacy decorators in your `tsconfig.json`:
```json ```json
{ {
"compilerOptions": { "compilerOptions": {
@@ -640,9 +652,18 @@ Enable legacy decorators in your `tsconfig.json`:
} }
``` ```
> 💡 **Tip:** When starting new projects or migrating existing ones, prefer TC39 decorators. They're the future of JavaScript and don't require experimental flags. ### Structured Logging
## 🔄 CI/CD Integration tsbuild uses a 4-level visual hierarchy for console output, making it easy to follow compilation progress:
- **HEADER** - Top-level section start (emoji + bold text + separator)
- **STEP** - Major action within a section (emoji + text)
- **DETAIL** - Supplementary info under a step (indented)
- **SUCCESS/ERROR/WARN** - Outcome indicators with color coding
All output uses ANSI color codes for terminal readability. Use `--quiet` to suppress output or `--json` for machine-readable format.
## CI/CD Integration
### GitHub Actions ### GitHub Actions
@@ -657,16 +678,16 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: '20' node-version: '22'
- run: npm install - run: pnpm install
- run: npx tsbuild - run: pnpm exec tsbuild
``` ```
### JSON Output ### JSON Output
```bash ```bash
npx tsbuild --json --quiet pnpm exec tsbuild --json --quiet
``` ```
```json ```json
@@ -694,7 +715,7 @@ npx tsbuild --json --quiet
} }
``` ```
## 🔧 Troubleshooting ## Troubleshooting
### Common Issues ### Common Issues
@@ -718,14 +739,14 @@ Ensure your tsconfig.json has:
Use `--skiplibcheck` to skip declaration file checking: Use `--skiplibcheck` to skip declaration file checking:
```bash ```bash
npx tsbuild --skiplibcheck pnpm exec tsbuild --skiplibcheck
``` ```
Only use this if you trust your dependencies' type definitions. Only use this if you trust your dependencies' type definitions.
## License and Legal Information ## License and Legal Information
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file. This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [license](./license) file.
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file. **Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
+6 -4
View File
@@ -5,17 +5,19 @@ import { TsCompiler } from '../ts/index.js';
const assetfiles: string[] = ['./test/assets/tocompile.ts', './test/assets/tocompile2.ts']; const assetfiles: string[] = ['./test/assets/tocompile.ts', './test/assets/tocompile2.ts'];
const assetfiles2 = { const assetfiles2 = {
'./test/assets/**/!(*.d.ts|*.js|output)': './test/assets/output', './test/assets/tocompile*.ts': './test/assets/output',
}; };
tap.test('should compile files from an array', async () => { tap.test('should compile files from an array', async () => {
const compiler = new TsCompiler(); const compiler = new TsCompiler();
await compiler.compileFilesOrThrow(assetfiles, { outDir: './test/assets/output' }); const emittedFiles = await compiler.compileFilesOrThrow(assetfiles, { outDir: './test/assets/output' });
expect(emittedFiles.length).toBeGreaterThan(0);
}); });
tap.test('should compile files from glob pattern object', async () => { tap.test('should compile files from glob pattern object', async () => {
const compiler = new TsCompiler(); const compiler = new TsCompiler();
await compiler.compileGlob(assetfiles2); const result = await compiler.compileGlob(assetfiles2);
expect(result.emittedFiles.length).toBeGreaterThan(0);
}); });
tap.start(); export default tap.start();
+1 -1
View File
@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@git.zone/tsbuild', name: '@git.zone/tsbuild',
version: '4.1.5', version: '4.4.1',
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.'
} }
+1
View File
@@ -6,6 +6,7 @@ export * from './mod_fs/index.js';
export * from './mod_config/index.js'; export * from './mod_config/index.js';
export * from './mod_unpack/index.js'; export * from './mod_unpack/index.js';
export * from './mod_pathrewrite/index.js'; export * from './mod_pathrewrite/index.js';
export * from './mod_logger/index.js';
export * from './mod_compiler/index.js'; export * from './mod_compiler/index.js';
export * from './mod_cli/index.js'; export * from './mod_cli/index.js';
+27 -35
View File
@@ -5,6 +5,7 @@ import * as tspublish from '@git.zone/tspublish';
import { TsCompiler } from '../mod_compiler/index.js'; import { TsCompiler } from '../mod_compiler/index.js';
import { FsHelpers } from '../mod_fs/index.js'; import { FsHelpers } from '../mod_fs/index.js';
import { TsBuildLogger as log } from '../mod_logger/index.js';
/** /**
* TsBuildCli handles all CLI commands for tsbuild. * TsBuildCli handles all CLI commands for tsbuild.
@@ -78,21 +79,20 @@ export class TsBuildCli {
const patterns = argvArg._.slice(1); const patterns = argvArg._.slice(1);
if (patterns.length === 0) { if (patterns.length === 0) {
console.error('\n❌ Error: Please provide at least one TypeScript file path or glob pattern'); log.error('Please provide at least one TypeScript file path or glob pattern');
console.error(' Usage: tsbuild emitcheck <file_or_glob_pattern> [additional_patterns ...]\n'); log.indent('Usage: tsbuild emitcheck <pattern> [...]');
console.error(' Example: tsbuild emitcheck "src/**/*.ts" "test/**/*.ts"\n'); log.indent('Example: tsbuild emitcheck "src/**/*.ts" "test/**/*.ts"');
process.exit(1); process.exit(1);
} }
const allFiles = await this.collectFilesFromPatterns(patterns); const allFiles = await this.collectFilesFromPatterns(patterns);
if (allFiles.length === 0) { if (allFiles.length === 0) {
console.error('\n❌ Error: No TypeScript files found to check'); log.error('No TypeScript files found to check');
console.error(' Please verify your file paths or glob patterns.\n');
process.exit(1); process.exit(1);
} }
console.log(`\n🔎 Found ${allFiles.length} TypeScript file${allFiles.length !== 1 ? 's' : ''} to check`); log.step('🔎', `Found ${allFiles.length} TypeScript file${allFiles.length !== 1 ? 's' : ''} to check`);
const compiler = new TsCompiler(this.cwd, argvArg); const compiler = new TsCompiler(this.cwd, argvArg);
const success = await compiler.checkEmit(allFiles); const success = await compiler.checkEmit(allFiles);
@@ -153,19 +153,12 @@ export class TsBuildCli {
// Display compilation plan // Display compilation plan
const folderCount = sortedTsFolders.length; const folderCount = sortedTsFolders.length;
console.log(`\n📂 TypeScript Folder Compilation Plan (${folderCount} folder${folderCount !== 1 ? 's' : ''})`); log.header('📂', `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) => { sortedTsFolders.forEach((folder, index) => {
const prefix = index === folderCount - 1 ? '└─' : '├─'; log.indent(`${index + 1}. ${folder}`);
const position = `${index + 1}/${folderCount}`;
console.log(`${prefix} ${position.padStart(5)} ${folder.padEnd(46)}`);
}); });
console.log('└' + '─'.repeat(60) + '┘\n');
// Build compilation object // Build compilation object
const compilationCommandObject: Record<string, string> = {}; const compilationCommandObject: Record<string, string> = {};
for (const tsFolder of sortedTsFolders) { for (const tsFolder of sortedTsFolders) {
@@ -198,12 +191,11 @@ export class TsBuildCli {
const allFiles = await this.collectFilesFromPatterns(patterns); const allFiles = await this.collectFilesFromPatterns(patterns);
if (allFiles.length === 0) { if (allFiles.length === 0) {
console.error('\n❌ Error: No TypeScript files found to check'); log.error('No TypeScript files found to check');
console.error(' Please verify your file paths or glob patterns.\n');
process.exit(1); process.exit(1);
} }
console.log(`\n🔎 Found ${allFiles.length} TypeScript file${allFiles.length !== 1 ? 's' : ''} to check`); log.step('🔎', `Found ${allFiles.length} TypeScript file${allFiles.length !== 1 ? 's' : ''} to check`);
const compiler = new TsCompiler(this.cwd, argvArg); const compiler = new TsCompiler(this.cwd, argvArg);
const success = await compiler.checkTypes(allFiles); const success = await compiler.checkTypes(allFiles);
@@ -216,34 +208,34 @@ export class TsBuildCli {
* Run default type checks for ts/ and test/ directories * Run default type checks for ts/ and test/ directories
*/ */
private async runDefaultTypeChecks(argvArg: any): Promise<void> { private async runDefaultTypeChecks(argvArg: any): Promise<void> {
console.log('\n🔬 Running default type checking sequence...\n'); log.header('🔬', 'Default type checking sequence');
// First check ts/**/* without skiplibcheck // First check ts/**/* without skiplibcheck
console.log('📂 Checking ts/**/* files...'); log.step('📂', 'Checking ts/**/* files...');
const tsTsFiles = await FsHelpers.listFilesWithGlob(this.cwd, 'ts/**/*.ts'); const tsTsFiles = await FsHelpers.listFilesWithGlob(this.cwd, 'ts/**/*.ts');
if (tsTsFiles.length > 0) { if (tsTsFiles.length > 0) {
console.log(` Found ${tsTsFiles.length} TypeScript files in ts/`); log.detail('📄', `${tsTsFiles.length} TypeScript files`);
const tsAbsoluteFiles = smartpath.transform.toAbsolute(tsTsFiles, this.cwd) as string[]; const tsAbsoluteFiles = smartpath.transform.toAbsolute(tsTsFiles, this.cwd) as string[];
const tsCompiler = new TsCompiler(this.cwd, argvArg); const tsCompiler = new TsCompiler(this.cwd, argvArg);
const tsSuccess = await tsCompiler.checkTypes(tsAbsoluteFiles); const tsSuccess = await tsCompiler.checkTypes(tsAbsoluteFiles);
if (!tsSuccess) { if (!tsSuccess) {
console.error('Type checking failed for ts/**/*'); log.error('Type checking failed for ts/**/*');
process.exit(1); process.exit(1);
} }
console.log('Type checking passed for ts/**/*\n'); log.success('Type checking passed for ts/**/*');
} else { } else {
console.log(' No TypeScript files found in ts/\n'); log.detail('📄', 'No TypeScript files found in ts/');
} }
// Then check test/**/* with skiplibcheck // Then check test/**/* with skiplibcheck
console.log('📂 Checking test/**/* files with --skiplibcheck...'); log.step('📂', 'Checking test/**/* files with --skiplibcheck...');
const testTsFiles = await FsHelpers.listFilesWithGlob(this.cwd, 'test/**/*.ts'); const testTsFiles = await FsHelpers.listFilesWithGlob(this.cwd, 'test/**/*.ts');
if (testTsFiles.length > 0) { if (testTsFiles.length > 0) {
console.log(` Found ${testTsFiles.length} TypeScript files in test/`); log.detail('📄', `${testTsFiles.length} TypeScript files`);
const testAbsoluteFiles = smartpath.transform.toAbsolute(testTsFiles, this.cwd) as string[]; const testAbsoluteFiles = smartpath.transform.toAbsolute(testTsFiles, this.cwd) as string[];
const testArgvArg = { ...argvArg, skiplibcheck: true }; const testArgvArg = { ...argvArg, skiplibcheck: true };
@@ -251,15 +243,15 @@ export class TsBuildCli {
const testSuccess = await testCompiler.checkTypes(testAbsoluteFiles); const testSuccess = await testCompiler.checkTypes(testAbsoluteFiles);
if (!testSuccess) { if (!testSuccess) {
console.error('Type checking failed for test/**/*'); log.error('Type checking failed for test/**/*');
process.exit(1); process.exit(1);
} }
console.log('Type checking passed for test/**/*\n'); log.success('Type checking passed for test/**/*');
} else { } else {
console.log(' No TypeScript files found in test/\n'); log.detail('📄', 'No TypeScript files found in test/');
} }
console.log('All default type checks passed!\n'); log.success('All default type checks passed!');
process.exit(0); process.exit(0);
} }
@@ -272,19 +264,19 @@ export class TsBuildCli {
for (const pattern of patterns) { for (const pattern of patterns) {
if (pattern.includes('*') || pattern.includes('{') || pattern.includes('?')) { if (pattern.includes('*') || pattern.includes('{') || pattern.includes('?')) {
// Handle as glob pattern // Handle as glob pattern
console.log(`Processing glob pattern: ${pattern}`); log.step('🔎', `Processing glob pattern: ${pattern}`);
try { try {
const stringMatchedFiles = await FsHelpers.listFilesWithGlob(this.cwd, pattern); const stringMatchedFiles = await FsHelpers.listFilesWithGlob(this.cwd, pattern);
if (stringMatchedFiles.length === 0) { if (stringMatchedFiles.length === 0) {
console.warn(`⚠️ Warning: No files matched the pattern '${pattern}'`); log.warn(`No files matched pattern '${pattern}'`);
} else { } else {
console.log(`📂 Found ${stringMatchedFiles.length} files matching pattern '${pattern}'`); log.detail('📂', `${stringMatchedFiles.length} files matching '${pattern}'`);
const absoluteMatchedFiles = smartpath.transform.toAbsolute(stringMatchedFiles, this.cwd) as string[]; const absoluteMatchedFiles = smartpath.transform.toAbsolute(stringMatchedFiles, this.cwd) as string[];
allFiles = allFiles.concat(absoluteMatchedFiles); allFiles = allFiles.concat(absoluteMatchedFiles);
} }
} catch (err) { } catch (err) {
console.error(`Error processing glob pattern '${pattern}': ${err}`); log.error(`Error processing glob pattern '${pattern}': ${err}`);
} }
} else { } else {
// Handle as direct file path // Handle as direct file path
@@ -293,7 +285,7 @@ export class TsBuildCli {
if (fileExists) { if (fileExists) {
allFiles.push(filePath); allFiles.push(filePath);
} else { } else {
console.error(`❌ Error: File not found: ${filePath}`); log.error(`File not found: ${filePath}`);
process.exit(1); process.exit(1);
} }
} }
+86 -127
View File
@@ -8,6 +8,7 @@ import { TsConfig } from '../mod_config/index.js';
import { FsHelpers } from '../mod_fs/index.js'; import { FsHelpers } from '../mod_fs/index.js';
import { performUnpack } from '../mod_unpack/index.js'; import { performUnpack } from '../mod_unpack/index.js';
import { TsPathRewriter } from '../mod_pathrewrite/index.js'; import { TsPathRewriter } from '../mod_pathrewrite/index.js';
import { TsBuildLogger as log } from '../mod_logger/index.js';
/** /**
* Interface for error summary data * Interface for error summary data
@@ -117,33 +118,23 @@ export class TsCompiler {
} }
const { errorsByFile, generalErrors, totalErrors, totalFiles } = errorSummary; const { errorsByFile, generalErrors, totalErrors, totalFiles } = errorSummary;
const c = log.c;
// Print error summary header console.log('');
console.log('\n' + '='.repeat(80)); console.log(c.dim + log.separator() + c.reset);
console.log( console.log(
`❌ Found ${totalErrors} error${totalErrors !== 1 ? 's' : ''} in ${totalFiles} file${totalFiles !== 1 ? 's' : ''}:` `${c.red}❌ Found ${totalErrors} error${totalErrors !== 1 ? 's' : ''} in ${totalFiles} file${totalFiles !== 1 ? 's' : ''}${c.reset}`
); );
console.log('='.repeat(80)); console.log(c.dim + log.separator() + c.reset);
// 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 // Print file-specific errors
Object.entries(errorsByFile).forEach(([fileName, fileErrors]) => { Object.entries(errorsByFile).forEach(([fileName, fileErrors]) => {
// Show relative path if possible for cleaner output
const displayPath = fileName.replace(process.cwd(), '').replace(/^\//, ''); const displayPath = fileName.replace(process.cwd(), '').replace(/^\//, '');
console.log( console.log(
`\n${colors.cyan}File: ${displayPath} ${colors.yellow}(${fileErrors.length} error${fileErrors.length !== 1 ? 's' : ''})${colors.reset}` `\n ${c.cyan}File: ${displayPath}${c.reset} ${c.yellow}(${fileErrors.length} error${fileErrors.length !== 1 ? 's' : ''})${c.reset}`
); );
console.log('-'.repeat(80)); console.log(` ${c.dim}${''.repeat(40)}${c.reset}`);
fileErrors.forEach((diagnostic) => { fileErrors.forEach((diagnostic) => {
if (diagnostic.file && diagnostic.start !== undefined) { if (diagnostic.file && diagnostic.start !== undefined) {
@@ -152,19 +143,18 @@ export class TsCompiler {
const errorCode = diagnostic.code ? `TS${diagnostic.code}` : 'Error'; const errorCode = diagnostic.code ? `TS${diagnostic.code}` : 'Error';
console.log( console.log(
`${colors.white}Line ${line + 1}, Col ${character + 1}${colors.reset}: ${colors.brightRed}${errorCode}${colors.reset} - ${message}` ` ${c.white}Line ${line + 1}, Col ${character + 1}${c.reset}: ${c.brightRed}${errorCode}${c.reset} - ${message}`
); );
// Try to show the code snippet if possible
try { try {
const lineContent = diagnostic.file.text.split('\n')[line]; const lineContent = diagnostic.file.text.split('\n')[line];
if (lineContent) { if (lineContent) {
console.log(` ${lineContent.trimEnd()}`); console.log(` ${lineContent.trimEnd()}`);
const indicator = ' '.repeat(character) + `${colors.red}^${colors.reset}`; const indicator = ' '.repeat(character) + `${c.red}^${c.reset}`;
console.log(` ${indicator}`); console.log(` ${indicator}`);
} }
} catch { } catch {
// Failed to get source text, skip showing the code snippet // Failed to get source text
} }
} }
}); });
@@ -172,17 +162,18 @@ export class TsCompiler {
// Print general errors // Print general errors
if (generalErrors.length > 0) { if (generalErrors.length > 0) {
console.log(`\n${colors.yellow}General Errors:${colors.reset}`); console.log(`\n ${c.yellow}General Errors:${c.reset}`);
console.log('-'.repeat(80)); console.log(` ${c.dim}${''.repeat(40)}${c.reset}`);
generalErrors.forEach((diagnostic) => { generalErrors.forEach((diagnostic) => {
const message = typescript.flattenDiagnosticMessageText(diagnostic.messageText, '\n'); const message = typescript.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
const errorCode = diagnostic.code ? `TS${diagnostic.code}` : 'Error'; const errorCode = diagnostic.code ? `TS${diagnostic.code}` : 'Error';
console.log(`${colors.brightRed}${errorCode}${colors.reset}: ${message}`); console.log(` ${c.brightRed}${errorCode}${c.reset}: ${message}`);
}); });
} }
console.log('\n' + '='.repeat(80) + '\n'); console.log('');
console.log(c.dim + log.separator() + c.reset);
} }
/** /**
@@ -190,12 +181,11 @@ export class TsCompiler {
*/ */
private async handleSkipLibCheckWarning(): Promise<void> { private async handleSkipLibCheckWarning(): Promise<void> {
if (this.argvArg?.confirmskiplibcheck) { if (this.argvArg?.confirmskiplibcheck) {
console.log('\n⚠️ WARNING ⚠️'); log.warn('WARNING: You are skipping libcheck... Is that really wanted?');
console.log('You are skipping libcheck... Is that really wanted?'); log.indent('Continuing in 5 seconds...');
console.log('Continuing in 5 seconds...\n');
await smartdelay.delayFor(5000); await smartdelay.delayFor(5000);
} else if (!this.argvArg?.quiet && !this.argvArg?.json) { } else if (!this.argvArg?.quiet && !this.argvArg?.json) {
console.log('⚠️ skipLibCheck enabled; use --confirmskiplibcheck to pause with warning.'); log.warn('skipLibCheck enabled; use --confirmskiplibcheck to pause with warning.');
} }
} }
@@ -207,23 +197,23 @@ export class TsCompiler {
customOptions: CompilerOptions = {}, customOptions: CompilerOptions = {},
taskInfo?: ITaskInfo taskInfo?: ITaskInfo
): Promise<ICompileResult> { ): Promise<ICompileResult> {
const options = this.createOptions(customOptions); const options = {
...this.createOptions(customOptions),
listEmittedFiles: true,
};
if (options.skipLibCheck) { if (options.skipLibCheck) {
await this.handleSkipLibCheckWarning(); await this.handleSkipLibCheckWarning();
} }
// Enhanced logging with task info
const startTime = Date.now(); const startTime = Date.now();
if (taskInfo) { if (taskInfo) {
const { taskNumber, totalTasks, sourcePattern, fileCount } = taskInfo; const { taskNumber, totalTasks, sourcePattern, fileCount } = taskInfo;
const relativeDestDir = taskInfo.destDir.replace(process.cwd(), '').replace(/^\//, ''); const relativeDestDir = taskInfo.destDir.replace(process.cwd(), '').replace(/^\//, '');
console.log( log.step('🔨', `[${taskNumber}/${totalTasks}] Compiling ${fileCount} file${fileCount !== 1 ? 's' : ''} from ${sourcePattern}`);
`\n🔨 [${taskNumber}/${totalTasks}] Compiling ${fileCount} file${fileCount !== 1 ? 's' : ''} from ${sourcePattern}` log.detail('📁', `Output: ${relativeDestDir}`);
);
console.log(` 📁 Output: ${relativeDestDir}`);
} else { } else {
console.log(`🔨 Compiling ${fileNames.length} files...`); log.step('🔨', `Compiling ${fileNames.length} files...`);
} }
const done = smartpromise.defer<ICompileResult>(); const done = smartpromise.defer<ICompileResult>();
@@ -233,17 +223,21 @@ export class TsCompiler {
const preEmitDiagnostics = typescript.getPreEmitDiagnostics(program); const preEmitDiagnostics = typescript.getPreEmitDiagnostics(program);
const preEmitErrorSummary = this.processDiagnostics(preEmitDiagnostics); const preEmitErrorSummary = this.processDiagnostics(preEmitDiagnostics);
// Only continue to emit phase if no pre-emit errors
if (preEmitErrorSummary.totalErrors > 0) { if (preEmitErrorSummary.totalErrors > 0) {
this.displayErrorSummary(preEmitErrorSummary); this.displayErrorSummary(preEmitErrorSummary);
console.error('\n❌ TypeScript pre-emit checks failed. Please fix the issues listed above before proceeding.'); log.error('Pre-emit checks failed');
console.error(' Type errors must be resolved before the compiler can emit output files.\n');
done.resolve({ emittedFiles: [], errorSummary: preEmitErrorSummary }); done.resolve({ emittedFiles: [], errorSummary: preEmitErrorSummary });
return done.promise; return done.promise;
} }
// If no pre-emit errors, proceed with emit // If no pre-emit errors, proceed with emit
const emitResult = program.emit(); const emitResult = program.emit();
// Yield to the event loop so any pending microtasks, nextTick callbacks,
// or deferred I/O from TypeScript's emit (e.g. libuv write completions)
// can settle before we read or modify the output directory.
await new Promise<void>((resolve) => process.nextTick(resolve));
const emitErrorSummary = this.processDiagnostics(emitResult.diagnostics); const emitErrorSummary = this.processDiagnostics(emitResult.diagnostics);
// Combine error summaries // Combine error summaries
@@ -261,9 +255,9 @@ export class TsCompiler {
if (taskInfo) { if (taskInfo) {
const { taskNumber, totalTasks } = taskInfo; const { taskNumber, totalTasks } = taskInfo;
console.log(`[${taskNumber}/${totalTasks}] Task completed in ${duration}ms`); log.success(`[${taskNumber}/${totalTasks}] Completed in ${duration}ms`);
} else { } else {
console.log(`TypeScript emit succeeded! (${duration}ms)`); log.success(`TypeScript emit succeeded (${duration}ms)`);
} }
// Get count of emitted files by type // Get count of emitted files by type
@@ -272,16 +266,13 @@ export class TsCompiler {
const mapFiles = emitResult.emittedFiles?.filter((f) => f.endsWith('.map')).length || 0; const mapFiles = emitResult.emittedFiles?.filter((f) => f.endsWith('.map')).length || 0;
if (emitResult.emittedFiles && emitResult.emittedFiles.length > 0) { if (emitResult.emittedFiles && emitResult.emittedFiles.length > 0) {
console.log( log.detail('📄', `Generated ${emitResult.emittedFiles.length} files: ${jsFiles} .js, ${dtsFiles} .d.ts, ${mapFiles} source maps`);
` 📄 Generated ${emitResult.emittedFiles.length} files: ${jsFiles} .js, ${dtsFiles} .d.ts, ${mapFiles} source maps`
);
} }
done.resolve({ emittedFiles: emitResult.emittedFiles || [], errorSummary: combinedErrorSummary }); done.resolve({ emittedFiles: emitResult.emittedFiles || [], errorSummary: combinedErrorSummary });
} else { } else {
this.displayErrorSummary(combinedErrorSummary); this.displayErrorSummary(combinedErrorSummary);
console.error('\n❌ TypeScript emit failed. Please investigate the errors listed above!'); log.error('TypeScript emit failed');
console.error(' No output files have been generated.\n');
done.resolve({ emittedFiles: [], errorSummary: combinedErrorSummary }); done.resolve({ emittedFiles: [], errorSummary: combinedErrorSummary });
} }
@@ -317,38 +308,46 @@ export class TsCompiler {
const isJson = this.argvArg?.json === true; const isJson = this.argvArg?.json === true;
if (!isQuiet && !isJson) { if (!isQuiet && !isJson) {
console.log(`\n👷 TypeScript Compilation Tasks (${totalTasks} task${totalTasks !== 1 ? 's' : ''}):`); log.header('👷', `TypeScript Compilation Tasks (${totalTasks} task${totalTasks !== 1 ? 's' : ''})`);
Object.entries(globPatterns).forEach(([source, dest]) => { Object.entries(globPatterns).forEach(([source, dest]) => {
console.log(` 📂 ${source}${dest}`); log.detail('📂', `${source}${dest}`);
}); });
console.log('');
} }
// Phase 1: Resolve glob patterns and clean ALL output directories upfront.
interface IResolvedTask {
pattern: string;
destPath: string;
destDir: string;
absoluteFiles: string[];
}
const resolvedTasks: IResolvedTask[] = [];
for (const pattern of Object.keys(globPatterns)) { for (const pattern of Object.keys(globPatterns)) {
const destPath = globPatterns[pattern]; const destPath = globPatterns[pattern];
if (!pattern || !destPath) continue; if (!pattern || !destPath) continue;
// Get files matching the glob pattern
const files = await FsHelpers.listFilesWithGlob(this.cwd, pattern); const files = await FsHelpers.listFilesWithGlob(this.cwd, pattern);
// Transform to absolute paths
const absoluteFiles = smartpath.transform.toAbsolute(files, this.cwd) as string[]; 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; const destDir = smartpath.transform.toAbsolute(destPath, this.cwd) as string;
// Clear the destination directory before compilation if it exists
if (await FsHelpers.directoryExists(destDir)) { if (await FsHelpers.directoryExists(destDir)) {
if (!isQuiet && !isJson) { if (!isQuiet && !isJson) {
console.log(`🧹 Clearing output directory: ${destPath}`); log.step('🧹', `Clearing output directory: ${destPath}`);
} }
await FsHelpers.removeDirectory(destDir); await FsHelpers.removeDirectory(destDir);
} }
// Update compiler options with the output directory resolvedTasks.push({ pattern, destPath, destDir, absoluteFiles });
}
// Phase 2: Compile all tasks.
const pendingUnpacks: Array<{ pattern: string; destDir: string }> = [];
for (const task of resolvedTasks) {
const options: CompilerOptions = { const options: CompilerOptions = {
...customOptions, ...customOptions,
outDir: destDir, outDir: task.destDir,
listEmittedFiles: true, listEmittedFiles: true,
}; };
@@ -356,49 +355,27 @@ export class TsCompiler {
const taskInfo: ITaskInfo = { const taskInfo: ITaskInfo = {
taskNumber: currentTask, taskNumber: currentTask,
totalTasks, totalTasks,
sourcePattern: pattern, sourcePattern: task.pattern,
destDir: destPath, destDir: task.destPath,
fileCount: absoluteFiles.length, fileCount: task.absoluteFiles.length,
}; };
const result = await this.compileFiles(absoluteFiles, options, taskInfo); const result = await this.compileFiles(task.absoluteFiles, options, taskInfo);
emittedFiles.push(...result.emittedFiles); emittedFiles.push(...result.emittedFiles);
errorSummaries.push(result.errorSummary); errorSummaries.push(result.errorSummary);
// DIAGNOSTIC: Log directory contents after compilation
if (!isQuiet && !isJson) {
try {
const fs = await import('fs');
const dirEntries = fs.readdirSync(destDir, { withFileTypes: true });
const dirs = dirEntries.filter((e: any) => e.isDirectory()).map((e: any) => e.name).sort();
const fileCount = dirEntries.filter((e: any) => e.isFile()).length;
console.log(` 📋 [DIAG] After emit: ${dirs.length} dirs [${dirs.join(', ')}], ${fileCount} top-level files`);
console.log(` 📋 [DIAG] emittedFiles.length=${result.emittedFiles.length}, totalErrors=${result.errorSummary.totalErrors}`);
} catch (e: any) { console.log(` 📋 [DIAG] Error reading destDir: ${e.message}`); }
}
// Perform unpack if compilation succeeded
if (result.errorSummary.totalErrors === 0) { if (result.errorSummary.totalErrors === 0) {
pendingUnpacks.push({ pattern: task.pattern, destDir: task.destDir });
successfulOutputDirs.push(task.destDir);
}
}
// Phase 3: Perform all unpacks after all compilations are done.
for (const { pattern, destDir } of pendingUnpacks) {
await performUnpack(pattern, destDir, this.cwd); await performUnpack(pattern, destDir, this.cwd);
// DIAGNOSTIC: Log directory contents after unpack
if (!isQuiet && !isJson) {
try {
const fs = await import('fs');
const dirEntries = fs.readdirSync(destDir, { withFileTypes: true });
const dirs = dirEntries.filter((e: any) => e.isDirectory()).map((e: any) => e.name).sort();
const fileCount = dirEntries.filter((e: any) => e.isFile()).length;
console.log(` 📋 [DIAG] After unpack: ${dirs.length} dirs [${dirs.join(', ')}], ${fileCount} top-level files`);
} catch (e: any) { console.log(` 📋 [DIAG] Error reading destDir after unpack: ${e.message}`); }
} }
successfulOutputDirs.push(destDir); // Rewrite import paths in all output directories
}
}
// Rewrite import paths in all output directories to handle cross-module references
// This must happen after ALL compilations so all destination folders exist
// Use fromProjectDirectory to detect ALL ts_* folders, not just the ones being compiled
if (successfulOutputDirs.length > 0) { if (successfulOutputDirs.length > 0) {
const rewriter = await TsPathRewriter.fromProjectDirectory(this.cwd); const rewriter = await TsPathRewriter.fromProjectDirectory(this.cwd);
let totalRewritten = 0; let totalRewritten = 0;
@@ -406,7 +383,7 @@ export class TsCompiler {
totalRewritten += await rewriter.rewriteDirectory(outputDir); totalRewritten += await rewriter.rewriteDirectory(outputDir);
} }
if (totalRewritten > 0 && !isQuiet && !isJson) { if (totalRewritten > 0 && !isQuiet && !isJson) {
console.log(` 🔄 Rewrote import paths in ${totalRewritten} file${totalRewritten !== 1 ? 's' : ''}`); log.detail('🔄', `Rewrote import paths in ${totalRewritten} file${totalRewritten !== 1 ? 's' : ''}`);
} }
} }
@@ -455,7 +432,7 @@ export class TsCompiler {
const options = { ...this.createOptions(customOptions), noEmit: true }; const options = { ...this.createOptions(customOptions), noEmit: true };
const fileCount = fileNames.length; const fileCount = fileNames.length;
console.log(`\n🔍 Checking if ${fileCount} file${fileCount !== 1 ? 's' : ''} can be emitted...`); log.step('🔍', `Checking if ${fileCount} file${fileCount !== 1 ? 's' : ''} can be emitted...`);
const program = this.createProgram(fileNames, options); const program = this.createProgram(fileNames, options);
@@ -475,12 +452,10 @@ export class TsCompiler {
const success = combinedErrorSummary.totalErrors === 0 && !emitResult.emitSkipped; const success = combinedErrorSummary.totalErrors === 0 && !emitResult.emitSkipped;
if (success) { if (success) {
console.log('\n✅ TypeScript emit check passed! All files can be emitted successfully.'); log.success(`Emit check passed (${fileCount} file${fileCount !== 1 ? 's' : ''})`);
console.log(` ${fileCount} file${fileCount !== 1 ? 's' : ''} ${fileCount !== 1 ? 'are' : 'is'} ready to be compiled.\n`);
} else { } else {
this.displayErrorSummary(combinedErrorSummary); this.displayErrorSummary(combinedErrorSummary);
console.error('\n❌ TypeScript emit check failed. Please fix the issues listed above.'); log.error('Emit check failed');
console.error(' The compilation cannot proceed until these errors are resolved.\n');
} }
return success; return success;
@@ -493,7 +468,7 @@ export class TsCompiler {
const options = { ...this.createOptions(customOptions), noEmit: true }; const options = { ...this.createOptions(customOptions), noEmit: true };
const fileCount = fileNames.length; const fileCount = fileNames.length;
console.log(`\n🔍 Type checking ${fileCount} TypeScript file${fileCount !== 1 ? 's' : ''}...`); log.step('🔍', `Type checking ${fileCount} file${fileCount !== 1 ? 's' : ''}...`);
const program = this.createProgram(fileNames, options); const program = this.createProgram(fileNames, options);
const diagnostics = typescript.getPreEmitDiagnostics(program); const diagnostics = typescript.getPreEmitDiagnostics(program);
@@ -502,12 +477,10 @@ export class TsCompiler {
const success = errorSummary.totalErrors === 0; const success = errorSummary.totalErrors === 0;
if (success) { if (success) {
console.log('\n✅ TypeScript type check passed! No type errors found.'); log.success(`Type check passed (${fileCount} file${fileCount !== 1 ? 's' : ''})`);
console.log(` All ${fileCount} file${fileCount !== 1 ? 's' : ''} passed type checking successfully.\n`);
} else { } else {
this.displayErrorSummary(errorSummary); this.displayErrorSummary(errorSummary);
console.error('\n❌ TypeScript type check failed. Please fix the type errors listed above.'); log.error('Type check failed');
console.error(' The type checker found issues that need to be resolved.\n');
} }
return success; return success;
@@ -545,42 +518,28 @@ export class TsCompiler {
* Display final compilation summary * Display final compilation summary
*/ */
private displayFinalSummary(errorSummary: IErrorSummary): void { private displayFinalSummary(errorSummary: IErrorSummary): void {
const c = log.c;
if (errorSummary.totalErrors === 0) { if (errorSummary.totalErrors === 0) {
console.log('\n📊 \x1b[32mCompilation Summary: All tasks completed successfully! ✅\x1b[0m\n'); log.header('📊', `${c.green}Compilation Summary: All tasks completed successfully! ✅${c.reset}`);
return; return;
} }
const colors = { log.header('📊', 'Compilation Summary');
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(
console.log(`📊 ${colors.brightYellow}Final Compilation Summary${colors.reset}`); `${c.brightRed} ${errorSummary.totalErrors} error${errorSummary.totalErrors !== 1 ? 's' : ''} across ${errorSummary.totalFiles} file${errorSummary.totalFiles !== 1 ? 's' : ''}${c.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]) => { Object.entries(errorSummary.errorsByFile).forEach(([fileName, errors]) => {
const displayPath = fileName.replace(process.cwd(), '').replace(/^\//, ''); const displayPath = fileName.replace(process.cwd(), '').replace(/^\//, '');
console.log( log.indent(
` ${colors.red}${colors.reset} ${colors.cyan}${displayPath}${colors.reset} ${colors.yellow}(${errors.length} error${errors.length !== 1 ? 's' : ''})${colors.reset}` `${c.red}${c.reset} ${c.cyan}${displayPath}${c.reset} ${c.yellow}(${errors.length} error${errors.length !== 1 ? 's' : ''})${c.reset}`
); );
}); });
}
if (errorSummary.generalErrors.length > 0) { if (errorSummary.generalErrors.length > 0) {
console.log(`${colors.brightRed}❌ General errors: ${errorSummary.generalErrors.length}${colors.reset}`); console.log(`${c.brightRed}❌ General errors: ${errorSummary.generalErrors.length}${c.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');
} }
} }
+5 -5
View File
@@ -18,7 +18,6 @@ export const compilerOptionsDefault: CompilerOptions = {
noImplicitAny: false, noImplicitAny: false,
esModuleInterop: true, esModuleInterop: true,
verbatimModuleSyntax: true, verbatimModuleSyntax: true,
baseUrl: './',
}; };
/** /**
@@ -98,12 +97,13 @@ export class TsConfig {
// Apply path transformations (ts_ → dist_ts_) // Apply path transformations (ts_ → dist_ts_)
if (tsconfig.compilerOptions.paths) { if (tsconfig.compilerOptions.paths) {
returnObject.paths = { ...tsconfig.compilerOptions.paths }; const paths: Record<string, string[]> = { ...tsconfig.compilerOptions.paths };
for (const pathKey of Object.keys(returnObject.paths)) { for (const pathKey of Object.keys(paths)) {
if (Array.isArray(returnObject.paths[pathKey]) && returnObject.paths[pathKey].length > 0) { if (Array.isArray(paths[pathKey]) && paths[pathKey].length > 0) {
returnObject.paths[pathKey][0] = returnObject.paths[pathKey][0].replace('./ts_', './dist_ts_'); paths[pathKey][0] = paths[pathKey][0].replace('./ts_', './dist_ts_');
} }
} }
returnObject.paths = paths;
} }
this.cachedTsConfig = returnObject; this.cachedTsConfig = returnObject;
+10 -4
View File
@@ -17,7 +17,8 @@ export interface ITsPublishJson {
*/ */
export class TsPublishConfig { export class TsPublishConfig {
private folderPath: string; private folderPath: string;
private cachedConfig: ITsPublishJson | null | undefined = undefined; private cachedConfig: ITsPublishJson | null = null;
private cacheLoaded = false;
constructor(folderPath: string) { constructor(folderPath: string) {
this.folderPath = folderPath; this.folderPath = folderPath;
@@ -35,7 +36,7 @@ export class TsPublishConfig {
* Returns null if file doesn't exist or is invalid * Returns null if file doesn't exist or is invalid
*/ */
public async load(): Promise<ITsPublishJson | null> { public async load(): Promise<ITsPublishJson | null> {
if (this.cachedConfig !== undefined) { if (this.cacheLoaded) {
return this.cachedConfig; return this.cachedConfig;
} }
@@ -43,9 +44,11 @@ export class TsPublishConfig {
const configPath = path.join(this.folderPath, 'tspublish.json'); const configPath = path.join(this.folderPath, 'tspublish.json');
const content = await fs.promises.readFile(configPath, 'utf8'); const content = await fs.promises.readFile(configPath, 'utf8');
this.cachedConfig = JSON.parse(content); this.cachedConfig = JSON.parse(content);
this.cacheLoaded = true;
return this.cachedConfig; return this.cachedConfig;
} catch { } catch {
this.cachedConfig = null; this.cachedConfig = null;
this.cacheLoaded = true;
return null; return null;
} }
} }
@@ -55,7 +58,7 @@ export class TsPublishConfig {
* Returns null if file doesn't exist or is invalid * Returns null if file doesn't exist or is invalid
*/ */
public loadSync(): ITsPublishJson | null { public loadSync(): ITsPublishJson | null {
if (this.cachedConfig !== undefined) { if (this.cacheLoaded) {
return this.cachedConfig; return this.cachedConfig;
} }
@@ -63,9 +66,11 @@ export class TsPublishConfig {
const configPath = path.join(this.folderPath, 'tspublish.json'); const configPath = path.join(this.folderPath, 'tspublish.json');
const content = fs.readFileSync(configPath, 'utf8'); const content = fs.readFileSync(configPath, 'utf8');
this.cachedConfig = JSON.parse(content); this.cachedConfig = JSON.parse(content);
this.cacheLoaded = true;
return this.cachedConfig; return this.cachedConfig;
} catch { } catch {
this.cachedConfig = null; this.cachedConfig = null;
this.cacheLoaded = true;
return null; return null;
} }
} }
@@ -111,6 +116,7 @@ export class TsPublishConfig {
* Clear the cached config (useful for reloading) * Clear the cached config (useful for reloading)
*/ */
public clearCache(): void { public clearCache(): void {
this.cachedConfig = undefined; this.cachedConfig = null;
this.cacheLoaded = false;
} }
} }
+2 -9
View File
@@ -122,10 +122,10 @@ export class FsHelpers {
} }
/** /**
* Remove a directory recursively * Remove a directory recursively.
*/ */
public static async removeDirectory(dirPath: string): Promise<void> { public static async removeDirectory(dirPath: string): Promise<void> {
await fs.promises.rm(dirPath, { recursive: true }); fs.rmSync(dirPath, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
} }
/** /**
@@ -134,11 +134,4 @@ export class FsHelpers {
public static async move(src: string, dest: string): Promise<void> { public static async move(src: string, dest: string): Promise<void> {
await fs.promises.rename(src, dest); await fs.promises.rename(src, dest);
} }
/**
* Remove an empty directory
*/
public static async removeEmptyDirectory(dirPath: string): Promise<void> {
await fs.promises.rmdir(dirPath);
}
} }
+72
View File
@@ -0,0 +1,72 @@
/**
* Centralized console output for tsbuild.
*
* Visual hierarchy (4 levels):
* HEADER — top-level section start (emoji + bold text + separator line)
* STEP — major action within a section (emoji + text, no indent)
* DETAIL — supplementary info under a step (3-space indent + emoji + text)
* SUCCESS/ERROR/WARN — outcome indicators (emoji + text, no indent)
*/
export class TsBuildLogger {
static readonly c = {
reset: '\x1b[0m',
bold: '\x1b[1m',
dim: '\x1b[2m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
cyan: '\x1b[36m',
white: '\x1b[37m',
brightRed: '\x1b[91m',
brightGreen: '\x1b[92m',
brightYellow: '\x1b[93m',
};
static readonly SEPARATOR_WIDTH = 70;
static separator(char = '─'): string {
return char.repeat(this.SEPARATOR_WIDTH);
}
/** Level 1: Section header. Blank line before, separator after. */
static header(emoji: string, text: string): void {
console.log('');
console.log(`${emoji} ${this.c.bold}${text}${this.c.reset}`);
console.log(this.c.dim + this.separator() + this.c.reset);
}
/** Level 2: Step within a section. No indent. */
static step(emoji: string, text: string): void {
console.log(`${emoji} ${text}`);
}
/** Level 3: Detail under a step. 3-space indent. */
static detail(emoji: string, text: string): void {
console.log(` ${emoji} ${text}`);
}
/** Outcome: success */
static success(text: string): void {
console.log(`${this.c.green}${text}${this.c.reset}`);
}
/** Outcome: error (goes to stderr) */
static error(text: string): void {
console.error(`${this.c.red}${text}${this.c.reset}`);
}
/** Outcome: warning */
static warn(text: string): void {
console.log(`${this.c.yellow}⚠️ ${text}${this.c.reset}`);
}
/** Plain indented line (for code snippets, list items, etc.) */
static indent(text: string, level = 1): void {
console.log(' '.repeat(level) + text);
}
/** Blank line */
static blank(): void {
console.log('');
}
}
+1
View File
@@ -0,0 +1 @@
export * from './classes.logger.js';
+34 -50
View File
@@ -2,6 +2,7 @@ import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import { TsPublishConfig } from '../mod_config/index.js'; import { TsPublishConfig } from '../mod_config/index.js';
import { FsHelpers } from '../mod_fs/index.js'; import { FsHelpers } from '../mod_fs/index.js';
import { TsBuildLogger as log } from '../mod_logger/index.js';
/** /**
* TsUnpacker handles flattening of nested TypeScript output directories. * TsUnpacker handles flattening of nested TypeScript output directories.
@@ -81,73 +82,56 @@ export class TsUnpacker {
} }
/** /**
* Perform the unpack operation - flatten nested output directories * Perform the unpack operation - flatten nested output directories.
* Returns true if unpacking was performed, false if skipped *
* When TypeScript compiles files that import from sibling directories,
* it creates a nested structure like dist_ts/ts/ with siblings like
* dist_ts/ts_interfaces/. This method flattens by:
* 1. Removing sibling directories (non-source folders)
* 2. Moving contents of the nested source folder up to the dest dir
* 3. Removing the now-empty nested source folder
*
* Uses synchronous fs operations for reliability.
* Called after all compilations are complete (not between compilations)
* to avoid filesystem metadata issues on XFS.
*
* Returns true if unpacking was performed, false if skipped.
*/ */
public async unpack(): Promise<boolean> { public async unpack(): Promise<boolean> {
// Check if we should unpack based on config
if (!(await this.shouldUnpack())) { if (!(await this.shouldUnpack())) {
console.log(` 📋 [DIAG-UNPACK] Skipping unpack: shouldUnpack=false`);
return false; return false;
} }
// Check if nested structure exists if (!(await this.detectNesting())) {
const hasNesting = await this.detectNesting();
console.log(` 📋 [DIAG-UNPACK] detectNesting=${hasNesting}, nestedPath=${this.getNestedPath()}`);
if (!hasNesting) {
return false; return false;
} }
const nestedPath = this.getNestedPath(); const nestedPath = this.getNestedPath();
// DIAGNOSTIC: Log contents before unpack // Step 1: Remove sibling entries (everything in dest except the source folder)
try { const destEntries = fs.readdirSync(this.destDir);
const destEntries = fs.readdirSync(this.destDir, { withFileTypes: true }); for (const entry of destEntries) {
const destDirs = destEntries.filter(e => e.isDirectory()).map(e => e.name).sort(); if (entry !== this.sourceFolderName) {
console.log(` 📋 [DIAG-UNPACK] Before removeSibling: destDir dirs=[${destDirs.join(', ')}]`); fs.rmSync(path.join(this.destDir, entry), { recursive: true, force: true });
const nestedEntries = fs.readdirSync(nestedPath, { withFileTypes: true }); }
const nestedDirs = nestedEntries.filter(e => e.isDirectory()).map(e => e.name).sort(); }
const nestedFiles = nestedEntries.filter(e => e.isFile()).length;
console.log(` 📋 [DIAG-UNPACK] Nested dir (${this.sourceFolderName}/): ${nestedDirs.length} dirs [${nestedDirs.join(', ')}], ${nestedFiles} files`);
} catch (e: any) { console.log(` 📋 [DIAG-UNPACK] Error reading: ${e.message}`); }
// Delete sibling folders (not the source folder) // Step 2: Move all contents from nested dir up to dest dir
await this.removeSiblingDirectories(); const nestedEntries = fs.readdirSync(nestedPath);
for (const entry of nestedEntries) {
fs.renameSync(
path.join(nestedPath, entry),
path.join(this.destDir, entry),
);
}
// Move contents from nested folder up // Step 3: Remove the now-empty nested directory
await this.moveNestedContentsUp(); fs.rmdirSync(nestedPath);
// Remove empty nested folder log.detail('📦', `Unpacked ${this.sourceFolderName}: ${nestedEntries.length} entries`);
await FsHelpers.removeEmptyDirectory(nestedPath);
return true; 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);
}
}
} }
/** /**