Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8bc4f173e5 | |||
| fba2cba8e8 | |||
| 1033996cb5 | |||
| 7e8b5c4467 | |||
| a738716d98 | |||
| d9c79ae4eb | |||
| a3255fd1fb | |||
| d6fb6e527e | |||
| 96bafec720 | |||
| 86f47ff743 | |||
| 38c134f084 | |||
| 25372bf97d | |||
| f521530eed | |||
| dd81d65958 | |||
| d1ef48560d | |||
| 49d62e20a4 | |||
| 4f8443d33f | |||
| 2fb838d1bd | |||
| b4a1ff5eab | |||
| d3e3905e7f | |||
| fe60a21c59 | |||
| 920095d008 | |||
| e1198c0a78 | |||
| 01b2cfe69c | |||
| b38a8236da | |||
| f136fe2e40 | |||
| 5fa93923d2 | |||
| 856099b5ed | |||
| 7aec1548eb | |||
| c269039050 | |||
| 75cb613d5e | |||
| cf05229b86 | |||
| 10b8d47a55 | |||
| 52e1a25948 | |||
| 3b8ebd41f4 | |||
| 9e839b3047 | |||
| 7ce22b0c4d | |||
| b2ce02a4fb | |||
| ca27db1d75 | |||
| c8a1582ec4 | |||
| 1e40bd5882 | |||
| 4ccb7ea9d6 | |||
| 0040325914 | |||
| f2980dc00f |
150
changelog.md
150
changelog.md
@@ -1,5 +1,155 @@
|
||||
# Changelog
|
||||
|
||||
## 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)
|
||||
add diagnostic logging around compilation and unpack to aid troubleshooting
|
||||
|
||||
- Enable TypeScript CompilerOptions.listEmittedFiles to surface emitted file info
|
||||
- Log destination directory contents and emitted file/error counts after compile and after unpack (only when not quiet and not json)
|
||||
- Add unpack-specific diagnostic logs: shouldUnpack skip, detectNesting result and nestedPath, and nested/destination directory listings before removing sibling directories
|
||||
- All logs use console.log and are wrapped in try/catch to avoid throwing; changes are informational and non-breaking
|
||||
|
||||
## 2026-03-05 - 4.1.4 - fix(deps)
|
||||
bump @git.zone/tspublish dependency to ^1.11.2
|
||||
|
||||
- Updated @git.zone/tspublish from ^1.11.0 to ^1.11.2 in package.json
|
||||
|
||||
## 2026-03-04 - 4.1.3 - fix(deps)
|
||||
bump dependencies: @push.rocks/smartcli, @push.rocks/smartlog, @git.zone/tstest, and @types/node to their newer versions
|
||||
|
||||
- @push.rocks/smartcli: ^4.0.19 -> ^4.0.20
|
||||
- @push.rocks/smartlog: ^3.1.10 -> ^3.2.1
|
||||
- @git.zone/tstest: ^3.1.4 -> ^3.2.0
|
||||
- @types/node: ^25.0.3 -> ^25.3.3
|
||||
- Non-breaking dependency version bumps
|
||||
|
||||
## 2026-01-04 - 4.1.0 - feat(docs)
|
||||
update README with improved docs and monorepo/tspublish guidance; namespace and extend npmextra.json with release registries; bump several dependencies
|
||||
|
||||
- Bumped dependencies: @git.zone/tspublish ^1.10.3 → ^1.11.0, @push.rocks/smartfs ^1.2.0 → ^1.3.1, @git.zone/tstest ^3.1.3 → ^3.1.4, @types/node ^25.0.1 → ^25.0.3
|
||||
- npmextra.json reorganized: replaced legacy keys with namespaced entries (@git.zone/cli, @git.zone/tsdoc) and added @ship.zone/szci; added release configuration with registries [https://verdaccio.lossless.digital, https://registry.npmjs.org] and public accessLevel
|
||||
- README rewritten: improved formatting, added emojis and tables, new 'Issue Reporting and Security' section, monorepo/tspublish usage docs, auto-unpack and decorator guidance, CI/CD and troubleshooting improvements
|
||||
- Non-breaking changes; recommended next semantic version bump is minor
|
||||
|
||||
## 2025-12-14 - 4.0.2 - fix(TsCompiler)
|
||||
Clear output directories before compilation to ensure clean builds and avoid stale files
|
||||
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
{
|
||||
"npmci": {
|
||||
"npmGlobalTools": [],
|
||||
"npmAccessLevel": "public"
|
||||
},
|
||||
"gitzone": {
|
||||
"@git.zone/cli": {
|
||||
"projectType": "npm",
|
||||
"module": {
|
||||
"githost": "gitlab.com",
|
||||
@@ -23,9 +19,19 @@
|
||||
"development",
|
||||
"API"
|
||||
]
|
||||
},
|
||||
"release": {
|
||||
"registries": [
|
||||
"https://verdaccio.lossless.digital",
|
||||
"https://registry.npmjs.org"
|
||||
],
|
||||
"accessLevel": "public"
|
||||
}
|
||||
},
|
||||
"tsdoc": {
|
||||
"@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": []
|
||||
}
|
||||
}
|
||||
14
package.json
14
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@git.zone/tsbuild",
|
||||
"version": "4.0.2",
|
||||
"version": "4.1.21",
|
||||
"private": false,
|
||||
"description": "A tool for compiling TypeScript files using the latest nightly features, offering flexible APIs and a CLI for streamlined development.",
|
||||
"main": "dist_ts/index.js",
|
||||
@@ -36,21 +36,21 @@
|
||||
},
|
||||
"homepage": "https://code.foss.global/git.zone/tsbuild#README",
|
||||
"dependencies": {
|
||||
"@git.zone/tspublish": "^1.10.3",
|
||||
"@git.zone/tspublish": "^1.11.2",
|
||||
"@push.rocks/early": "^4.0.4",
|
||||
"@push.rocks/smartcli": "^4.0.19",
|
||||
"@push.rocks/smartcli": "^4.0.20",
|
||||
"@push.rocks/smartdelay": "^3.0.5",
|
||||
"@push.rocks/smartfile": "^13.1.2",
|
||||
"@push.rocks/smartfs": "^1.2.0",
|
||||
"@push.rocks/smartlog": "^3.1.10",
|
||||
"@push.rocks/smartfs": "^1.3.2",
|
||||
"@push.rocks/smartlog": "^3.2.1",
|
||||
"@push.rocks/smartpath": "^6.0.0",
|
||||
"@push.rocks/smartpromise": "^4.2.3",
|
||||
"typescript": "5.9.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@git.zone/tsrun": "^2.0.1",
|
||||
"@git.zone/tstest": "^3.1.3",
|
||||
"@types/node": "^25.0.1"
|
||||
"@git.zone/tstest": "^3.2.0",
|
||||
"@types/node": "^25.3.3"
|
||||
},
|
||||
"files": [
|
||||
"ts/**/*",
|
||||
|
||||
4284
pnpm-lock.yaml
generated
4284
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
238
readme.md
238
readme.md
@@ -1,8 +1,12 @@
|
||||
# @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, and automatic output directory management. 🚀
|
||||
|
||||
## Install
|
||||
## 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.
|
||||
|
||||
## 📦 Install
|
||||
|
||||
```bash
|
||||
# Using pnpm (recommended)
|
||||
@@ -12,20 +16,22 @@ pnpm install @git.zone/tsbuild --save-dev
|
||||
npm install @git.zone/tsbuild --save-dev
|
||||
```
|
||||
|
||||
## Why tsbuild?
|
||||
## ⚡ Why tsbuild?
|
||||
|
||||
- **Smart tsconfig.json Integration** - Respects all your compiler options with intelligent merging
|
||||
- **Protected Defaults** - Critical build settings are safeguarded while staying flexible
|
||||
- **Zero Config** - Works perfectly without tsconfig.json
|
||||
- **Glob Pattern Support** - Compile multiple directories with a single command
|
||||
- **Dependency-Aware** - Automatically orders compilation based on module dependencies
|
||||
- **Type Checking** - Validate code without emitting files
|
||||
- **Clean Builds** - Automatically clears output directories before compilation
|
||||
- **Auto-Unpack** - Flattens nested output directories automatically
|
||||
- **CI/CD Ready** - JSON output mode and proper exit codes
|
||||
- **Modern Defaults** - ESNext, NodeNext modules, decorators out of the box
|
||||
| Feature | Description |
|
||||
|---------|-------------|
|
||||
| 🔧 **Smart tsconfig.json Integration** | Respects all your compiler options with intelligent merging |
|
||||
| 🛡️ **Protected Defaults** | Critical build settings are safeguarded while staying flexible |
|
||||
| 🎯 **Zero Config** | Works perfectly without tsconfig.json |
|
||||
| 🌐 **Glob Pattern Support** | Compile multiple directories with a single command |
|
||||
| 📊 **Dependency-Aware** | Automatically orders compilation based on module dependencies |
|
||||
| ✅ **Type Checking** | Validate code without emitting files |
|
||||
| 🧹 **Clean Builds** | Automatically clears output directories before compilation |
|
||||
| 📁 **Auto-Unpack** | Flattens nested output directories automatically |
|
||||
| 🔄 **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
|
||||
|
||||
@@ -40,8 +46,8 @@ Compiles `./ts/**/*.ts` to `./dist_ts/`
|
||||
npx tsbuild custom src utils
|
||||
```
|
||||
Compiles:
|
||||
- `./src/**/*.ts` to `./dist_src/`
|
||||
- `./utils/**/*.ts` to `./dist_utils/`
|
||||
- `./src/**/*.ts` → `./dist_src/`
|
||||
- `./utils/**/*.ts` → `./dist_utils/`
|
||||
|
||||
**Auto-discover and compile in dependency order:**
|
||||
```bash
|
||||
@@ -91,7 +97,7 @@ await compiler.compileGlob({
|
||||
});
|
||||
```
|
||||
|
||||
## CLI Commands
|
||||
## 🛠️ CLI Commands
|
||||
|
||||
### 1. Default Build
|
||||
|
||||
@@ -102,12 +108,14 @@ npx tsbuild [options]
|
||||
Compiles all TypeScript files from `./ts/` to `./dist_ts/`
|
||||
|
||||
**Options:**
|
||||
- `--skiplibcheck` - Skip type checking of declaration files
|
||||
- `--confirmskiplibcheck` - Skip lib check with extended warning (5s pause)
|
||||
- `--disallowimplicitany` - Disallow implicit `any` types
|
||||
- `--commonjs` - Use CommonJS instead of ESNext modules
|
||||
- `--json` - Output results as JSON (for CI/CD)
|
||||
- `--quiet` - Suppress console output
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--skiplibcheck` | Skip type checking of declaration files |
|
||||
| `--confirmskiplibcheck` | Skip lib check with extended warning (5s pause) |
|
||||
| `--disallowimplicitany` | Disallow implicit `any` types |
|
||||
| `--commonjs` | Use CommonJS instead of ESNext modules |
|
||||
| `--json` | Output results as JSON (for CI/CD) |
|
||||
| `--quiet` | Suppress console output |
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
@@ -141,7 +149,7 @@ npx tsbuild custom src utils
|
||||
npx tsbuild custom api models services --commonjs
|
||||
```
|
||||
|
||||
### 3. TSFolders (Dependency-Aware)
|
||||
### 3. TSFolders (Dependency-Aware) 📊
|
||||
|
||||
```bash
|
||||
npx tsbuild tsfolders [options]
|
||||
@@ -164,7 +172,7 @@ TypeScript Folder Compilation Plan (5 folders)
|
||||
5/5 ts_modules
|
||||
```
|
||||
|
||||
### 4. Emit Check
|
||||
### 4. Emit Check ✓
|
||||
|
||||
```bash
|
||||
npx tsbuild emitcheck <file_or_pattern> [more...] [options]
|
||||
@@ -184,7 +192,7 @@ npx tsbuild emitcheck "src/**/*.ts" "test/**/*.ts"
|
||||
- `0` - All files can be emitted
|
||||
- `1` - One or more files have errors
|
||||
|
||||
### 5. Type Check
|
||||
### 5. Type Check 🔍
|
||||
|
||||
```bash
|
||||
npx tsbuild check [pattern] [more...] [options]
|
||||
@@ -210,7 +218,7 @@ npx tsbuild check
|
||||
# All default type checks passed!
|
||||
```
|
||||
|
||||
## API Reference
|
||||
## 📖 API Reference
|
||||
|
||||
### TsCompiler Class
|
||||
|
||||
@@ -390,7 +398,7 @@ const cli = new TsBuildCli('/path/to/project');
|
||||
cli.run();
|
||||
```
|
||||
|
||||
## Configuration
|
||||
## ⚙️ Configuration
|
||||
|
||||
### tsconfig.json Support
|
||||
|
||||
@@ -411,24 +419,28 @@ 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):
|
||||
|
||||
1. **Default Options** - tsbuild's sensible defaults
|
||||
2. **tsconfig.json** - All options from your tsconfig.json (if present)
|
||||
3. **Protected Defaults** - Critical options for build integrity
|
||||
4. **Programmatic Options** - Options passed to API functions
|
||||
5. **CLI Flags** - Command-line arguments (highest priority)
|
||||
| Priority | Source | Description |
|
||||
|----------|--------|-------------|
|
||||
| 1️⃣ | **Default Options** | tsbuild's sensible defaults |
|
||||
| 2️⃣ | **tsconfig.json** | All options from your tsconfig.json (if present) |
|
||||
| 3️⃣ | **Protected Defaults** | Critical options for build integrity |
|
||||
| 4️⃣ | **Programmatic Options** | Options passed to API functions |
|
||||
| 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):
|
||||
|
||||
- **`outDir: 'dist_ts/'`** - Required for automatic path transformations
|
||||
- **`noEmitOnError: true`** - Prevents broken builds from being emitted
|
||||
- **`declaration: true`** - Ensures `.d.ts` files for library consumers
|
||||
- **`inlineSourceMap: true`** - Consistent debugging experience
|
||||
| Option | Value | Reason |
|
||||
|--------|-------|--------|
|
||||
| `outDir` | `'dist_ts/'` | Required for automatic path transformations |
|
||||
| `noEmitOnError` | `true` | Prevents broken builds from being emitted |
|
||||
| `declaration` | `true` | Ensures `.d.ts` files for library consumers |
|
||||
| `inlineSourceMap` | `true` | Consistent debugging experience |
|
||||
|
||||
### Default Compiler Options
|
||||
|
||||
@@ -451,7 +463,7 @@ When no tsconfig.json exists:
|
||||
}
|
||||
```
|
||||
|
||||
### Path Transformation
|
||||
### Path Transformation 🔄
|
||||
|
||||
tsbuild automatically transforms path mappings:
|
||||
|
||||
@@ -468,12 +480,12 @@ tsbuild automatically transforms path mappings:
|
||||
|
||||
**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:
|
||||
|
||||
@@ -484,35 +496,129 @@ Compiling 14 files from ./ts/**/*.ts
|
||||
|
||||
This ensures no stale files remain from previous builds.
|
||||
|
||||
### Auto-Unpack
|
||||
### 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.
|
||||
|
||||
#### Directory Structure
|
||||
|
||||
```
|
||||
my-project/
|
||||
├── ts/ # Main package source
|
||||
├── ts_interfaces/ # Shared interfaces (order: 1)
|
||||
├── ts_shared/ # Shared utilities (order: 2)
|
||||
├── ts_core/ # Core logic (order: 3)
|
||||
├── ts_web/ # Web-specific code (order: 4)
|
||||
└── ts_node/ # Node-specific code (order: 5)
|
||||
```
|
||||
|
||||
Each `ts_*` folder can contain its own `tspublish.json` to configure compilation and publishing behavior.
|
||||
|
||||
#### tspublish.json Configuration
|
||||
|
||||
Create a `tspublish.json` in each `ts_*` folder:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "@myorg/core",
|
||||
"order": 3,
|
||||
"unpack": true,
|
||||
"dependencies": ["ts_interfaces", "ts_shared"]
|
||||
}
|
||||
```
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
|--------|------|---------|-------------|
|
||||
| `name` | string | — | Package name for publishing |
|
||||
| `order` | number | `Infinity` | Build sequence (lower builds first) |
|
||||
| `unpack` | boolean | `true` | Flatten nested output directories |
|
||||
| `dependencies` | string[] | — | Monorepo dependencies to bundle |
|
||||
|
||||
#### Build Order
|
||||
|
||||
The `tsfolders` command respects the `order` property:
|
||||
|
||||
```bash
|
||||
npx tsbuild tsfolders
|
||||
```
|
||||
|
||||
**Default ordering (without tspublish.json):**
|
||||
1. `ts_interfaces` - always first (shared types)
|
||||
2. `ts_shared` - always second (shared utilities)
|
||||
3. Other folders sorted by `order` property
|
||||
4. Folders without `order` are built last
|
||||
|
||||
**Example output:**
|
||||
```
|
||||
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:
|
||||
|
||||
```
|
||||
dist_ts_core/
|
||||
ts_core/ <- nested output
|
||||
ts_shared/ <- pulled-in dependency
|
||||
ts_core/ ← nested output
|
||||
ts_shared/ ← pulled-in dependency
|
||||
```
|
||||
|
||||
tsbuild automatically flattens this to:
|
||||
|
||||
```
|
||||
dist_ts_core/
|
||||
index.js <- flat
|
||||
index.js ← flat, clean structure
|
||||
```
|
||||
|
||||
Configure via `tspublish.json` in source folder:
|
||||
This is especially important for monorepos where packages import from each other.
|
||||
|
||||
**Control via tspublish.json:**
|
||||
```json
|
||||
{
|
||||
"unpack": true
|
||||
}
|
||||
```
|
||||
|
||||
Set `"unpack": false` to disable.
|
||||
- `"unpack": true` (default) - Flatten nested directories after compilation
|
||||
- `"unpack": false` - Keep original nested structure
|
||||
|
||||
### Decorator Support
|
||||
**What gets unpacked:**
|
||||
- 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)
|
||||
|
||||
First-class decorator support out of the box:
|
||||
### Decorator Support 🎨
|
||||
|
||||
tsbuild supports both **TC39 standard decorators** (recommended) and legacy experimental decorators.
|
||||
|
||||
**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:
|
||||
|
||||
```typescript
|
||||
// TC39 standard decorator
|
||||
function log(target: any, context: ClassMethodDecoratorContext) {
|
||||
return function (...args: any[]) {
|
||||
console.log(`Calling ${String(context.name)}`);
|
||||
return target.apply(this, args);
|
||||
};
|
||||
}
|
||||
|
||||
class UserService {
|
||||
@log
|
||||
getUser(id: string) {
|
||||
return { id, name: 'John' };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Legacy Decorators (Backwards Compatibility)**
|
||||
|
||||
For existing projects using frameworks like NestJS, TypeORM, or Angular that still require experimental decorators:
|
||||
|
||||
```typescript
|
||||
@Injectable()
|
||||
@@ -524,9 +630,19 @@ class UserService {
|
||||
}
|
||||
```
|
||||
|
||||
Works with NestJS, TypeORM, Inversify, Angular, and other DI frameworks.
|
||||
Enable legacy decorators in your `tsconfig.json`:
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## CI/CD Integration
|
||||
> 💡 **Tip:** When starting new projects or migrating existing ones, prefer TC39 decorators. They're the future of JavaScript and don't require experimental flags.
|
||||
|
||||
## 🔄 CI/CD Integration
|
||||
|
||||
### GitHub Actions
|
||||
|
||||
@@ -578,7 +694,7 @@ npx tsbuild --json --quiet
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
## 🔧 Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
@@ -607,27 +723,23 @@ npx tsbuild --skiplibcheck
|
||||
|
||||
Only use this if you trust your dependencies' type definitions.
|
||||
|
||||
## Issue Reporting
|
||||
|
||||
For reporting issues or vulnerabilities, please visit our community at [community.foss.global](https://community.foss.global). We're looking forward to your contribution!
|
||||
|
||||
For repository access: [code.foss.global/git.zone/tsbuild](https://code.foss.global/git.zone/tsbuild)
|
||||
|
||||
## License and Legal Information
|
||||
|
||||
This 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.
|
||||
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.
|
||||
|
||||
### Trademarks
|
||||
|
||||
This 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.
|
||||
This 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.
|
||||
|
||||
Use 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.
|
||||
|
||||
### Company Information
|
||||
|
||||
Task Venture Capital GmbH
|
||||
Registered at District Court Bremen HRB 35230 HB, Germany
|
||||
|
||||
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
|
||||
For any legal inquiries or further information, please contact us via email at hello@task.vc.
|
||||
|
||||
By 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.
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@git.zone/tsbuild',
|
||||
version: '4.0.2',
|
||||
version: '4.1.21',
|
||||
description: 'A tool for compiling TypeScript files using the latest nightly features, offering flexible APIs and a CLI for streamlined development.'
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ early.start('tsbuild');
|
||||
export * from './mod_fs/index.js';
|
||||
export * from './mod_config/index.js';
|
||||
export * from './mod_unpack/index.js';
|
||||
export * from './mod_pathrewrite/index.js';
|
||||
export * from './mod_compiler/index.js';
|
||||
export * from './mod_cli/index.js';
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { CompilerOptions, Diagnostic, Program } from 'typescript';
|
||||
import typescript from 'typescript';
|
||||
import * as fs from 'fs';
|
||||
import * as smartdelay from '@push.rocks/smartdelay';
|
||||
import * as smartpromise from '@push.rocks/smartpromise';
|
||||
import * as smartpath from '@push.rocks/smartpath';
|
||||
@@ -7,6 +8,7 @@ import * as smartpath from '@push.rocks/smartpath';
|
||||
import { TsConfig } from '../mod_config/index.js';
|
||||
import { FsHelpers } from '../mod_fs/index.js';
|
||||
import { performUnpack } from '../mod_unpack/index.js';
|
||||
import { TsPathRewriter } from '../mod_pathrewrite/index.js';
|
||||
|
||||
/**
|
||||
* Interface for error summary data
|
||||
@@ -307,6 +309,7 @@ export class TsCompiler {
|
||||
): Promise<ICompileResult> {
|
||||
const emittedFiles: string[] = [];
|
||||
const errorSummaries: IErrorSummary[] = [];
|
||||
const successfulOutputDirs: string[] = [];
|
||||
|
||||
const totalTasks = Object.keys(globPatterns).length;
|
||||
let currentTask = 0;
|
||||
@@ -335,18 +338,36 @@ export class TsCompiler {
|
||||
// Get destination directory as absolute path
|
||||
const destDir = smartpath.transform.toAbsolute(destPath, this.cwd) as string;
|
||||
|
||||
// Diagnostic helper
|
||||
const diagSnap = (label: string) => {
|
||||
if (!isQuiet && !isJson) {
|
||||
for (const prevDir of successfulOutputDirs) {
|
||||
try {
|
||||
const entries = fs.readdirSync(prevDir);
|
||||
const dirs = entries.filter(e => { try { return fs.statSync(prevDir + '/' + e).isDirectory(); } catch { return false; } });
|
||||
console.log(` 📋 [${label}] ${prevDir.replace(this.cwd + '/', '')}: ${entries.length} entries, ${dirs.length} dirs`);
|
||||
} catch {
|
||||
console.log(` 📋 [${label}] ${prevDir.replace(this.cwd + '/', '')}: MISSING!`);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Clear the destination directory before compilation if it exists
|
||||
diagSnap('pre-clear');
|
||||
if (await FsHelpers.directoryExists(destDir)) {
|
||||
if (!isQuiet && !isJson) {
|
||||
console.log(`🧹 Clearing output directory: ${destPath}`);
|
||||
}
|
||||
await FsHelpers.removeDirectory(destDir);
|
||||
}
|
||||
diagSnap('post-clear');
|
||||
|
||||
// Update compiler options with the output directory
|
||||
const options: CompilerOptions = {
|
||||
...customOptions,
|
||||
outDir: destDir,
|
||||
listEmittedFiles: true,
|
||||
};
|
||||
|
||||
currentTask++;
|
||||
@@ -361,10 +382,71 @@ export class TsCompiler {
|
||||
const result = await this.compileFiles(absoluteFiles, options, taskInfo);
|
||||
emittedFiles.push(...result.emittedFiles);
|
||||
errorSummaries.push(result.errorSummary);
|
||||
diagSnap('post-compile');
|
||||
|
||||
// Diagnostic: log emitted files that went to unexpected directories
|
||||
if (!isQuiet && !isJson && result.emittedFiles.length > 0) {
|
||||
const unexpectedFiles = result.emittedFiles.filter(f => !f.startsWith(destDir + '/') && !f.startsWith(destDir + '\\'));
|
||||
if (unexpectedFiles.length > 0) {
|
||||
console.log(` ⚠️ [diag] ${unexpectedFiles.length} files emitted OUTSIDE ${destPath}:`);
|
||||
for (const f of unexpectedFiles.slice(0, 20)) {
|
||||
console.log(` ${f.replace(this.cwd + '/', '')}`);
|
||||
}
|
||||
if (unexpectedFiles.length > 20) {
|
||||
console.log(` ... and ${unexpectedFiles.length - 20} more`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Perform unpack if compilation succeeded
|
||||
if (result.errorSummary.totalErrors === 0) {
|
||||
await performUnpack(pattern, destDir, this.cwd);
|
||||
successfulOutputDirs.push(destDir);
|
||||
}
|
||||
|
||||
// Diagnostic: log all output directory states after each compilation
|
||||
if (!isQuiet && !isJson) {
|
||||
for (const prevDir of successfulOutputDirs) {
|
||||
try {
|
||||
const entries = fs.readdirSync(prevDir);
|
||||
const dirs = entries.filter(e => {
|
||||
try { return fs.statSync(prevDir + '/' + e).isDirectory(); } catch { return false; }
|
||||
});
|
||||
console.log(` 📋 [diag] ${prevDir.replace(this.cwd + '/', '')}: ${entries.length} entries, ${dirs.length} dirs`);
|
||||
} catch {
|
||||
console.log(` 📋 [diag] ${prevDir.replace(this.cwd + '/', '')}: MISSING!`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 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) {
|
||||
const rewriter = await TsPathRewriter.fromProjectDirectory(this.cwd);
|
||||
let totalRewritten = 0;
|
||||
for (const outputDir of successfulOutputDirs) {
|
||||
totalRewritten += await rewriter.rewriteDirectory(outputDir);
|
||||
}
|
||||
if (totalRewritten > 0 && !isQuiet && !isJson) {
|
||||
console.log(` 🔄 Rewrote import paths in ${totalRewritten} file${totalRewritten !== 1 ? 's' : ''}`);
|
||||
}
|
||||
|
||||
// Diagnostic: log output directory states after path rewriting
|
||||
if (!isQuiet && !isJson) {
|
||||
for (const dir of successfulOutputDirs) {
|
||||
try {
|
||||
const entries = fs.readdirSync(dir);
|
||||
const dirs = entries.filter(e => {
|
||||
try { return fs.statSync(dir + '/' + e).isDirectory(); } catch { return false; }
|
||||
});
|
||||
console.log(` 📋 [diag-post-rewrite] ${dir.replace(this.cwd + '/', '')}: ${entries.length} entries, ${dirs.length} dirs`);
|
||||
} catch {
|
||||
console.log(` 📋 [diag-post-rewrite] ${dir.replace(this.cwd + '/', '')}: MISSING!`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -122,10 +122,13 @@ export class FsHelpers {
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a directory recursively
|
||||
* Remove a directory recursively.
|
||||
* Uses synchronous rm to avoid XFS metadata corruption observed with
|
||||
* async fs.promises.rm affecting sibling directory entries on the
|
||||
* libuv thread pool under signal pressure.
|
||||
*/
|
||||
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 +137,4 @@ export class FsHelpers {
|
||||
public static async move(src: string, dest: string): Promise<void> {
|
||||
await fs.promises.rename(src, dest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an empty directory
|
||||
*/
|
||||
public static async removeEmptyDirectory(dirPath: string): Promise<void> {
|
||||
await fs.promises.rmdir(dirPath);
|
||||
}
|
||||
}
|
||||
|
||||
233
ts/mod_pathrewrite/classes.tspathrewriter.ts
Normal file
233
ts/mod_pathrewrite/classes.tspathrewriter.ts
Normal file
@@ -0,0 +1,233 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { FsHelpers } from '../mod_fs/index.js';
|
||||
|
||||
/**
|
||||
* Interface for folder mapping between source and destination
|
||||
*/
|
||||
export interface IFolderMapping {
|
||||
sourceFolder: string; // e.g., 'ts_shared'
|
||||
destFolder: string; // e.g., 'dist_ts_shared'
|
||||
}
|
||||
|
||||
/**
|
||||
* TsPathRewriter handles rewriting import paths in compiled JavaScript files.
|
||||
*
|
||||
* When TypeScript compiles files that import from sibling directories like:
|
||||
* import { helper } from '../ts_shared/helper.js';
|
||||
*
|
||||
* This class rewrites them to point to the compiled output directories:
|
||||
* import { helper } from '../dist_ts_shared/helper.js';
|
||||
*
|
||||
* This is necessary because the unpack feature flattens nested output structures,
|
||||
* changing the relative paths between modules.
|
||||
*/
|
||||
export class TsPathRewriter {
|
||||
private mappings: IFolderMapping[];
|
||||
|
||||
constructor(mappings: IFolderMapping[]) {
|
||||
this.mappings = mappings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a TsPathRewriter from glob patterns used in compilation
|
||||
*
|
||||
* @param globPatterns - Map of source patterns to destination directories
|
||||
* e.g., { './ts_core/**\/*.ts': './dist_ts_core', './ts_shared/**\/*.ts': './dist_ts_shared' }
|
||||
* @returns TsPathRewriter instance with extracted folder mappings
|
||||
*/
|
||||
public static fromGlobPatterns(globPatterns: Record<string, string>): TsPathRewriter {
|
||||
const mappings: IFolderMapping[] = [];
|
||||
|
||||
for (const [sourcePattern, destDir] of Object.entries(globPatterns)) {
|
||||
const sourceFolder = FsHelpers.extractSourceFolder(sourcePattern);
|
||||
const destFolder = FsHelpers.extractSourceFolder(destDir);
|
||||
|
||||
if (sourceFolder && destFolder) {
|
||||
mappings.push({ sourceFolder, destFolder });
|
||||
}
|
||||
}
|
||||
|
||||
return new TsPathRewriter(mappings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a TsPathRewriter by auto-detecting all ts_* folders in the project directory.
|
||||
* This ensures that cross-module imports are rewritten even when compiling a single module.
|
||||
*
|
||||
* For example, if the project has ts/, ts_shared/, ts_web/ folders:
|
||||
* - ts → dist_ts
|
||||
* - ts_shared → dist_ts_shared
|
||||
* - ts_web → dist_ts_web
|
||||
*
|
||||
* @param cwd - The project root directory
|
||||
* @returns TsPathRewriter instance with all detected folder mappings
|
||||
*/
|
||||
public static async fromProjectDirectory(cwd: string): Promise<TsPathRewriter> {
|
||||
const mappings: IFolderMapping[] = [];
|
||||
|
||||
try {
|
||||
const entries = await FsHelpers.listDirectory(cwd);
|
||||
|
||||
for (const entry of entries) {
|
||||
// Match folders starting with 'ts' (ts, ts_shared, ts_web, ts_core, etc.)
|
||||
if (entry.isDirectory && /^ts(_|$)/.test(entry.name)) {
|
||||
const sourceFolder = entry.name;
|
||||
const destFolder = `dist_${sourceFolder}`;
|
||||
mappings.push({ sourceFolder, destFolder });
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Directory listing failed, return empty mappings
|
||||
}
|
||||
|
||||
return new TsPathRewriter(mappings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current folder mappings
|
||||
*/
|
||||
public getMappings(): IFolderMapping[] {
|
||||
return [...this.mappings];
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrite import paths in a single file
|
||||
*
|
||||
* @param filePath - Absolute path to the file to rewrite
|
||||
* @returns true if file was modified, false otherwise
|
||||
*/
|
||||
public async rewriteFile(filePath: string): Promise<boolean> {
|
||||
// Only process .js and .d.ts files
|
||||
if (!filePath.endsWith('.js') && !filePath.endsWith('.d.ts')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const content = await fs.promises.readFile(filePath, 'utf8');
|
||||
const rewritten = this.rewriteContent(content);
|
||||
|
||||
if (rewritten !== content) {
|
||||
await fs.promises.writeFile(filePath, rewritten, 'utf8');
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (error) {
|
||||
// File doesn't exist or other error
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrite import paths in all .js and .d.ts files in a directory (recursively)
|
||||
*
|
||||
* @param dirPath - Absolute path to the directory
|
||||
* @returns Number of files modified
|
||||
*/
|
||||
public async rewriteDirectory(dirPath: string): Promise<number> {
|
||||
let modifiedCount = 0;
|
||||
|
||||
const processDir = async (currentDir: string): Promise<void> => {
|
||||
const entries = await FsHelpers.listDirectory(currentDir);
|
||||
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(currentDir, entry.name);
|
||||
|
||||
if (entry.isDirectory) {
|
||||
await processDir(fullPath);
|
||||
} else if (entry.isFile && (entry.name.endsWith('.js') || entry.name.endsWith('.d.ts'))) {
|
||||
const wasModified = await this.rewriteFile(fullPath);
|
||||
if (wasModified) {
|
||||
modifiedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (await FsHelpers.directoryExists(dirPath)) {
|
||||
await processDir(dirPath);
|
||||
}
|
||||
|
||||
return modifiedCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrite import paths in content string
|
||||
*
|
||||
* This method only modifies actual import/export/require statements,
|
||||
* not arbitrary strings or comments that might contain similar patterns.
|
||||
*
|
||||
* @param content - File content to process
|
||||
* @returns Rewritten content
|
||||
*/
|
||||
public rewriteContent(content: string): string {
|
||||
if (this.mappings.length === 0) {
|
||||
return content;
|
||||
}
|
||||
|
||||
let result = content;
|
||||
|
||||
for (const { sourceFolder, destFolder } of this.mappings) {
|
||||
// Skip if source and dest are the same
|
||||
if (sourceFolder === destFolder) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Pattern 1: ES Module imports/exports with 'from'
|
||||
// Matches: from '../ts_shared/...' or from '../../ts_shared/...'
|
||||
// Also handles: export { x } from '../ts_shared/...'
|
||||
const fromPattern = new RegExp(
|
||||
`(from\\s*['"])((?:\\.\\.\\/)+)${this.escapeRegex(sourceFolder)}(\\/[^'"]*['"])`,
|
||||
'g'
|
||||
);
|
||||
result = result.replace(fromPattern, `$1$2${destFolder}$3`);
|
||||
|
||||
// Pattern 2: Dynamic imports
|
||||
// Matches: import('../ts_shared/...')
|
||||
const dynamicImportPattern = new RegExp(
|
||||
`(import\\s*\\(\\s*['"])((?:\\.\\.\\/)+)${this.escapeRegex(sourceFolder)}(\\/[^'"]*['"]\\s*\\))`,
|
||||
'g'
|
||||
);
|
||||
result = result.replace(dynamicImportPattern, `$1$2${destFolder}$3`);
|
||||
|
||||
// Pattern 3: CommonJS require
|
||||
// Matches: require('../ts_shared/...')
|
||||
const requirePattern = new RegExp(
|
||||
`(require\\s*\\(\\s*['"])((?:\\.\\.\\/)+)${this.escapeRegex(sourceFolder)}(\\/[^'"]*['"]\\s*\\))`,
|
||||
'g'
|
||||
);
|
||||
result = result.replace(requirePattern, `$1$2${destFolder}$3`);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape special regex characters in a string
|
||||
*/
|
||||
private escapeRegex(str: string): string {
|
||||
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to rewrite import paths in output directories
|
||||
*
|
||||
* @param globPatterns - Map of source patterns to destination directories
|
||||
* @param outputDirs - List of output directories to process
|
||||
* @returns Total number of files modified
|
||||
*/
|
||||
export async function rewriteImportPaths(
|
||||
globPatterns: Record<string, string>,
|
||||
outputDirs: string[]
|
||||
): Promise<number> {
|
||||
const rewriter = TsPathRewriter.fromGlobPatterns(globPatterns);
|
||||
let totalModified = 0;
|
||||
|
||||
for (const dir of outputDirs) {
|
||||
totalModified += await rewriter.rewriteDirectory(dir);
|
||||
}
|
||||
|
||||
return totalModified;
|
||||
}
|
||||
1
ts/mod_pathrewrite/index.ts
Normal file
1
ts/mod_pathrewrite/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './classes.tspathrewriter.js';
|
||||
@@ -81,58 +81,53 @@ export class TsUnpacker {
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the unpack operation - flatten nested output directories
|
||||
* Returns true if unpacking was performed, false if skipped
|
||||
* Perform the unpack operation - flatten nested output directories.
|
||||
*
|
||||
* 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 to avoid race conditions with
|
||||
* async readdir returning partial/stale results under signal pressure
|
||||
* or XFS metadata lag (observed in process-group environments like gitzone).
|
||||
*
|
||||
* Returns true if unpacking was performed, false if skipped.
|
||||
*/
|
||||
public async unpack(): Promise<boolean> {
|
||||
// Check if we should unpack based on config
|
||||
if (!(await this.shouldUnpack())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if nested structure exists
|
||||
if (!(await this.detectNesting())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const nestedPath = this.getNestedPath();
|
||||
|
||||
// Delete sibling folders (not the source folder)
|
||||
await this.removeSiblingDirectories();
|
||||
|
||||
// Move contents from nested folder up
|
||||
await this.moveNestedContentsUp();
|
||||
|
||||
// Remove empty nested folder
|
||||
await FsHelpers.removeEmptyDirectory(nestedPath);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove sibling directories in the destination folder
|
||||
* (directories other than the source folder being unpacked)
|
||||
*/
|
||||
private async removeSiblingDirectories(): Promise<void> {
|
||||
const entries = await FsHelpers.listDirectory(this.destDir);
|
||||
for (const entry of entries) {
|
||||
if (entry.isDirectory && entry.name !== this.sourceFolderName) {
|
||||
await FsHelpers.removeDirectory(path.join(this.destDir, entry.name));
|
||||
// Step 1: Remove sibling entries (everything in dest except the source folder)
|
||||
const destEntries = fs.readdirSync(this.destDir);
|
||||
for (const entry of destEntries) {
|
||||
if (entry !== this.sourceFolderName) {
|
||||
fs.rmSync(path.join(this.destDir, entry), { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
// Step 2: Move all contents from nested dir up to dest dir
|
||||
const nestedEntries = fs.readdirSync(nestedPath);
|
||||
for (const entry of nestedEntries) {
|
||||
fs.renameSync(
|
||||
path.join(nestedPath, entry),
|
||||
path.join(this.destDir, entry),
|
||||
);
|
||||
}
|
||||
|
||||
// Step 3: Remove the now-empty nested directory
|
||||
fs.rmdirSync(nestedPath);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user