Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 681f2b4c54 | |||
| fb6bd614d3 | |||
| 0eaca1f3d1 | |||
| aeaa957efa | |||
| e5fcbb9a09 | |||
| 4ebc37fa5a | |||
| 5bfe60927e | |||
| 9e6b91b891 | |||
| e40e8f6a77 | |||
| ebe7afce82 | |||
| 27aa318054 | |||
| 9a071ce82f | |||
| ad227ded73 | |||
| c66a941aaf | |||
| a3b58dda39 | |||
| 4b29107130 | |||
| 09adbc1965 | |||
| 7fb571d4f6 | |||
| 6b32dced5a | |||
| df39aa48d0 | |||
| a4863bc761 | |||
| b38ef6cf82 | |||
| 8b50cd3090 | |||
| c6ab493efc | |||
| 82ae8a0e4a | |||
| 787becc4d3 | |||
| 746ca8767a | |||
| 55c1a2953a | |||
| 8e9fcb8135 | |||
| 18573c777d | |||
| fa654b83e3 | |||
| aa3c83cd95 | |||
| 20ed41df42 | |||
| 7162476f7f | |||
| 1b012628eb | |||
| 4081086621 | |||
| b9cf09ccba | |||
| 8463fbc78a | |||
| 0164eb51a1 | |||
| a305dd89dd | |||
| fa6b053ee0 | |||
| adfba21c67 | |||
| 050e41cdf9 | |||
| f220a11caa | |||
| 0909fa306a | |||
| 88c0601c03 | |||
| 9645f27942 | |||
| b73aa4f21f | |||
| d9d6878a9f |
164
changelog.md
164
changelog.md
@@ -1,5 +1,169 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-12-14 - 4.0.2 - fix(TsCompiler)
|
||||||
|
Clear output directories before compilation to ensure clean builds and avoid stale files
|
||||||
|
|
||||||
|
- TsCompiler.compileGlob now clears the destination directory (if it exists) before compiling each glob pattern.
|
||||||
|
- Clearing is logged unless --quiet or --json flags are set (e.g. "🧹 Clearing output directory: <dest>").
|
||||||
|
- Uses FsHelpers.removeDirectory to remove previous output, preventing stale or duplicate emitted files.
|
||||||
|
- Documentation (readme.md) updated to advertise automatic output directory management / clean builds.
|
||||||
|
- Removed stale compiled test artifacts from test/assets/output to avoid interference with tests.
|
||||||
|
|
||||||
|
## 2025-12-13 - 3.1.3 - fix(npmextra)
|
||||||
|
Align npmextra.json package name with package.json (@git.zone/tsbuild)
|
||||||
|
|
||||||
|
- Corrected npmPackagename in npmextra.json from "@gitzone/tsbuild" to "@git.zone/tsbuild" to match package.json and README
|
||||||
|
- Metadata-only change: no code or API behavior affected
|
||||||
|
|
||||||
|
## 2025-11-28 - 3.1.2 - fix(TsBuild)
|
||||||
|
Set default TypeScript target to ESNext
|
||||||
|
|
||||||
|
- Default compiler target changed from ScriptTarget.ES2024 to ScriptTarget.ESNext in ts/tsbuild.classes.tsbuild.ts
|
||||||
|
- Aligns the default compiler options with documentation and ensures use of the latest language features
|
||||||
|
|
||||||
|
## 2025-11-27 - 3.1.1 - fix(compiler)
|
||||||
|
Update default TypeScript target to ES2024
|
||||||
|
|
||||||
|
- Default compiler option 'target' changed from ScriptTarget.ESNext to ScriptTarget.ES2024 in ts/tsbuild.classes.tsbuild.ts
|
||||||
|
- Aligns emitted code to ES2024 language features by default
|
||||||
|
|
||||||
|
## 2025-11-17 - 3.1.0 - feat(tsbuild.classes)
|
||||||
|
Update default TypeScript lib to lib.esnext.d.ts
|
||||||
|
|
||||||
|
- Changed default compilerOptions.lib from ['lib.dom.d.ts', 'lib.es2022.d.ts'] to ['lib.dom.d.ts', 'lib.esnext.d.ts'] in compilerOptionsDefault.
|
||||||
|
- Allows newer ECMAScript/DOM features by default when compiling with tsbuild (affects emitted types and available globals).
|
||||||
|
- Behavioral default change only — no public API changes; callers can still override lib via tsconfig, programmatic options, or CLI.
|
||||||
|
|
||||||
|
## 2025-11-17 - 3.0.0 - BREAKING CHANGE(TsBuild)
|
||||||
|
Stop forcing emitDecoratorMetadata in protected compiler defaults
|
||||||
|
|
||||||
|
- Removed emitDecoratorMetadata from the set of protected/critical default compiler options.
|
||||||
|
- Projects that relied on tsbuild automatically enabling emitDecoratorMetadata (for DI frameworks like NestJS, TypeORM, Inversify) must now enable it explicitly in their tsconfig.json or via programmatic/CLI options.
|
||||||
|
- No other protected defaults were changed; outDir, noEmitOnError, declaration and inlineSourceMap remain enforced.
|
||||||
|
- Behavior is now more permissive: decorator metadata generation is controlled by user configuration rather than being forced by tsbuild.
|
||||||
|
|
||||||
|
## 2025-11-17 - 2.7.3 - fix(tsbuild.classes)
|
||||||
|
Remove duplicate emitDecoratorMetadata from default compiler options and centralize it in protected defaults
|
||||||
|
|
||||||
|
- Removed emitDecoratorMetadata from compilerOptionsDefault in ts/tsbuild.classes.tsbuild.ts to avoid duplicate configuration.
|
||||||
|
- emitDecoratorMetadata remains enforced via getCriticalDefaults(), ensuring decorator metadata support is protected from tsconfig.json overrides.
|
||||||
|
- Prevents inconsistencies during compiler option merging by centralizing the decorator-related setting.
|
||||||
|
|
||||||
|
## 2025-11-17 - 2.7.2 - fix(compilerOptions)
|
||||||
|
Remove experimentalDecorators and useDefineForClassFields from default TypeScript compiler options
|
||||||
|
|
||||||
|
- Removed experimentalDecorators from compilerOptionsDefault in ts/tsbuild.classes.tsbuild.ts
|
||||||
|
- Removed useDefineForClassFields from compilerOptionsDefault in ts/tsbuild.classes.tsbuild.ts
|
||||||
|
- Default compiler options now rely on TypeScript's upstream defaults for decorator and class field behavior
|
||||||
|
- If your project relies on these settings, re-enable them in your tsconfig.json or pass them via the programmatic API / CLI
|
||||||
|
|
||||||
|
## 2025-11-02 - 2.7.1 - fix(readme)
|
||||||
|
Update documentation: expand README with usage, CLI and API examples; add readme.hints.md project memory
|
||||||
|
|
||||||
|
- Add readme.hints.md: new project memory / quick reference with public API and CLI summaries
|
||||||
|
- Expand and restructure readme.md: more comprehensive Quick Start, CLI Commands, API Reference, configuration, examples and troubleshooting
|
||||||
|
- Clarify protected compiler options, default compiler options, path transformation behavior and error-handling patterns
|
||||||
|
- Docs-only change — no source code or behavioral changes
|
||||||
|
|
||||||
|
## 2025-11-02 - 2.7.0 - feat(tsbuild)
|
||||||
|
Add tsconfig.json support and safer compiler option merging; protect critical options; apply path and enum transforms; bump dependencies.
|
||||||
|
|
||||||
|
- Add robust tsconfig.json reading with graceful fallback when no tsconfig is present or it is invalid
|
||||||
|
- Merge compiler options in a clear priority order (defaults -> tsconfig -> protected defaults -> programmatic -> CLI flags)
|
||||||
|
- Introduce protected (critical) compiler options that cannot be overridden by tsconfig.json: outDir, noEmitOnError, declaration, emitDecoratorMetadata, inlineSourceMap
|
||||||
|
- Convert string values from tsconfig (target, module, moduleResolution) to TypeScript enum values where applicable; special-case NodeNext
|
||||||
|
- Transform tsconfig path mappings by replacing './ts_' with './dist_ts_' to keep runtime path resolution consistent with compiled output
|
||||||
|
- Expose getCriticalDefaults helper and adjust mergeCompilerOptions to apply protected defaults before programmatic and CLI overrides
|
||||||
|
- Update README with documentation for tsconfig support, merge order, protected compiler options, and example tsconfig
|
||||||
|
- Bump dependencies/devDependencies: @push.rocks/smartcli ^4.0.19, @push.rocks/smartlog ^3.1.10, typescript 5.9.3, @git.zone/tsrun ^1.6.2, @git.zone/tstest ^2.7.0
|
||||||
|
|
||||||
|
## 2025-08-29 - 2.6.8 - fix(tsbuild)
|
||||||
|
Avoid process.exit in library, add confirmskiplibcheck flag, improve CLI exit handling and JSON/quiet modes, update test script
|
||||||
|
|
||||||
|
- Changed package.json test script from "tsrun test/test.ts --verbose" to "tstest test/test.ts --verbose".
|
||||||
|
- Library no longer calls process.exit from compile and compileWithErrorTracking; errors are returned or thrown so callers can decide process termination.
|
||||||
|
- skipLibCheck behavior updated: delay/warning only happens when --confirmskiplibcheck is present; otherwise a short informational note is printed (suppressed in --quiet/--json).
|
||||||
|
- CLI now awaits compileGlobStringObject calls and inspects a final error summary attached to argv to decide process.exit(1) when errors occurred.
|
||||||
|
- compileGlobStringObject/exports now respect --quiet and --json modes, emit a JSON summary when --json is used, and attach the final error summary to argv so the CLI can determine exit behavior.
|
||||||
|
|
||||||
|
## 2025-08-18 - 2.6.7 - fix(tspublish)
|
||||||
|
Bump @git.zone/tspublish dependency to ^1.10.3
|
||||||
|
|
||||||
|
- Updated dependency @git.zone/tspublish from ^1.10.2 to ^1.10.3 in package.json
|
||||||
|
|
||||||
|
## 2025-08-18 - 2.6.6 - fix(dependencies)
|
||||||
|
Update dependency @git.zone/tspublish to ^1.10.2
|
||||||
|
|
||||||
|
- Bumped @git.zone/tspublish in package.json from ^1.10.1 to ^1.10.2
|
||||||
|
|
||||||
|
## 2025-08-18 - 2.6.5 - fix(dependencies)
|
||||||
|
Bump dependencies and add pnpm-workspace configuration
|
||||||
|
|
||||||
|
- Updated @git.zone/tspublish from ^1.9.1 to ^1.10.1
|
||||||
|
- Updated @push.rocks/smartfile from ^11.2.4 to ^11.2.7
|
||||||
|
- Updated @push.rocks/smartpath from ^5.0.18 to ^6.0.0
|
||||||
|
- Updated typescript from 5.8.3 to 5.9.2
|
||||||
|
- Updated devDependency @git.zone/tstest from ^1.10.1 to ^2.3.4
|
||||||
|
- Added pnpm-workspace.yaml with onlyBuiltDependencies list (esbuild, mongodb-memory-server, puppeteer)
|
||||||
|
|
||||||
|
## 2025-05-24 - 2.6.4 - fix(dependencies)
|
||||||
|
Add .npmrc and update dependency versions for smartfile and tstest
|
||||||
|
|
||||||
|
- Add .npmrc with registry configuration for npm
|
||||||
|
- Bump @push.rocks/smartfile version from ^11.2.3 to ^11.2.4
|
||||||
|
- Bump @git.zone/tstest version from ^1.9.0 to ^1.10.1
|
||||||
|
|
||||||
|
## 2025-05-21 - 2.6.3 - fix(tsbuild)
|
||||||
|
minor maintenance updates and documentation improvements
|
||||||
|
|
||||||
|
- Updated commit metadata to align with project version
|
||||||
|
- Refined CLI command parsing and diagnostics logging for better clarity
|
||||||
|
- Improved code readability in compiler options merging
|
||||||
|
|
||||||
|
## 2025-05-21 - 2.6.2 - fix(npm configuration)
|
||||||
|
Remove .npmrc file to default npm registry behavior
|
||||||
|
|
||||||
|
- Deleted .npmrc file that hard-coded the npm registry URL to https://registry.npmjs.org/
|
||||||
|
- This change leverages npm's default registry settings and reduces configuration clutter
|
||||||
|
|
||||||
|
## 2025-05-21 - 2.6.1 - fix(tsbuild.classes)
|
||||||
|
Improve error diagnostics handling by removing legacy helper and integrating more robust error summaries in the compilation process
|
||||||
|
|
||||||
|
- Removed the handleDiagnostics method and its legacy usage
|
||||||
|
- Replaced legacy calls with inline processDiagnostics checks for pre-emit and emit phases
|
||||||
|
- Combined error summaries for clearer reporting during emit checks and type validation
|
||||||
|
- Enhanced error output to guide users on resolving TypeScript errors before emission
|
||||||
|
|
||||||
|
## 2025-05-21 - 2.6.0 - feat(tsbuild)
|
||||||
|
Improve task logging and update dependencies
|
||||||
|
|
||||||
|
- Add .npmrc file with npm registry configuration
|
||||||
|
- Update test script to use '--verbose' flag
|
||||||
|
- Bump dependency versions for @push.rocks packages and TypeScript
|
||||||
|
- Enhance TsBuild logging by incorporating task info (e.g. task number, total tasks, output folder, and compile durations)
|
||||||
|
- Propagate task info in compileFileArrayWithErrorTracking for better task tracking
|
||||||
|
|
||||||
|
## 2025-05-21 - 2.5.2 - fix(tsbuild)
|
||||||
|
Improve diagnostic error handling and summary reporting for TypeScript compilation by refactoring diagnostic processing and adding pre-emit error checks.
|
||||||
|
|
||||||
|
- Introduce a dedicated processDiagnostics function that categorizes errors by file and computes error totals.
|
||||||
|
- Refactor displayErrorSummary to provide clearer, color-coded output of error details.
|
||||||
|
- Add pre-emit error checking in compileWithErrorTracking to prevent emission when errors exist.
|
||||||
|
- Consolidate error summary merging in compileFileArrayWithErrorTracking for improved reporting.
|
||||||
|
|
||||||
|
## 2025-05-15 - 2.5.1 - fix(commitinfo)
|
||||||
|
Update commit information and metadata to synchronize release data
|
||||||
|
|
||||||
|
- Regenerated the commitinfo file with current version details
|
||||||
|
- Maintained existing functionality with no functional code changes
|
||||||
|
|
||||||
|
## 2025-05-15 - 2.5.0 - feat(cli)
|
||||||
|
Enhance type checking in CLI by adding default file pattern handling
|
||||||
|
|
||||||
|
- When no TypeScript file or glob pattern is provided, the CLI now performs a default type checking sequence.
|
||||||
|
- First checks 'ts/**/*' files with standard options, then checks 'test/**/*' files with skiplibcheck enabled.
|
||||||
|
- Improved logging to indicate file discovery and check results, ensuring clear feedback for users.
|
||||||
|
|
||||||
## 2025-05-15 - 2.4.1 - fix(cli)
|
## 2025-05-15 - 2.4.1 - fix(cli)
|
||||||
Improve TS folder compilation order display in CLI
|
Improve TS folder compilation order display in CLI
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
"gitscope": "gitzone",
|
"gitscope": "gitzone",
|
||||||
"gitrepo": "tsbuild",
|
"gitrepo": "tsbuild",
|
||||||
"description": "A tool for compiling TypeScript files using the latest nightly features, offering flexible APIs and a CLI for streamlined development.",
|
"description": "A tool for compiling TypeScript files using the latest nightly features, offering flexible APIs and a CLI for streamlined development.",
|
||||||
"npmPackagename": "@gitzone/tsbuild",
|
"npmPackagename": "@git.zone/tsbuild",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"TypeScript",
|
"TypeScript",
|
||||||
|
|||||||
27
package.json
27
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@git.zone/tsbuild",
|
"name": "@git.zone/tsbuild",
|
||||||
"version": "2.4.1",
|
"version": "4.0.2",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "A tool for compiling TypeScript files using the latest nightly features, offering flexible APIs and a CLI for streamlined development.",
|
"description": "A tool for compiling TypeScript files using the latest nightly features, offering flexible APIs and a CLI for streamlined development.",
|
||||||
"main": "dist_ts/index.js",
|
"main": "dist_ts/index.js",
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
"tsbuild": "./cli.js"
|
"tsbuild": "./cli.js"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "tsrun test/test.ts",
|
"test": "tstest test/test.ts --verbose",
|
||||||
"build": "node cli.ts.js --web",
|
"build": "node cli.ts.js --web",
|
||||||
"buildDocs": "tsdoc"
|
"buildDocs": "tsdoc"
|
||||||
},
|
},
|
||||||
@@ -29,27 +29,28 @@
|
|||||||
"development",
|
"development",
|
||||||
"API"
|
"API"
|
||||||
],
|
],
|
||||||
"author": "Lossless GmbH",
|
"author": "Task Venture Capital GmbH",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://code.foss.global/git.zone/tsbuild/issues"
|
"url": "https://code.foss.global/git.zone/tsbuild/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://code.foss.global/git.zone/tsbuild#README",
|
"homepage": "https://code.foss.global/git.zone/tsbuild#README",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@git.zone/tspublish": "^1.9.1",
|
"@git.zone/tspublish": "^1.10.3",
|
||||||
"@push.rocks/early": "^4.0.4",
|
"@push.rocks/early": "^4.0.4",
|
||||||
"@push.rocks/smartcli": "^4.0.11",
|
"@push.rocks/smartcli": "^4.0.19",
|
||||||
"@push.rocks/smartdelay": "^3.0.5",
|
"@push.rocks/smartdelay": "^3.0.5",
|
||||||
"@push.rocks/smartfile": "^11.1.5",
|
"@push.rocks/smartfile": "^13.1.2",
|
||||||
"@push.rocks/smartlog": "^3.0.7",
|
"@push.rocks/smartfs": "^1.2.0",
|
||||||
"@push.rocks/smartpath": "^5.0.18",
|
"@push.rocks/smartlog": "^3.1.10",
|
||||||
"@push.rocks/smartpromise": "^4.2.2",
|
"@push.rocks/smartpath": "^6.0.0",
|
||||||
"typescript": "5.7.3"
|
"@push.rocks/smartpromise": "^4.2.3",
|
||||||
|
"typescript": "5.9.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@git.zone/tsrun": "^1.2.47",
|
"@git.zone/tsrun": "^2.0.1",
|
||||||
"@push.rocks/tapbundle": "^5.5.6",
|
"@git.zone/tstest": "^3.1.3",
|
||||||
"@types/node": "^22.12.0"
|
"@types/node": "^25.0.1"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"ts/**/*",
|
"ts/**/*",
|
||||||
|
|||||||
8604
pnpm-lock.yaml
generated
8604
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
215
readme.hints.md
215
readme.hints.md
@@ -0,0 +1,215 @@
|
|||||||
|
# @git.zone/tsbuild - Project Memory
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
### Public API - Classes
|
||||||
|
1. **TsCompiler** - Main compilation class with compileFiles, compileGlob, checkTypes, checkEmit methods
|
||||||
|
2. **TsConfig** - TypeScript configuration management (tsconfig.json handling)
|
||||||
|
3. **TsPublishConfig** - TsPublish configuration (tspublish.json handling)
|
||||||
|
4. **TsUnpacker** - Output directory flattening
|
||||||
|
5. **FsHelpers** - Filesystem utilities (static methods)
|
||||||
|
6. **TsBuildCli** - CLI command handler
|
||||||
|
|
||||||
|
### CLI Commands (5)
|
||||||
|
1. **tsbuild** (default) - Compiles ./ts/**/*.ts → ./dist_ts/
|
||||||
|
2. **tsbuild custom <dir1> <dir2>** - Custom directory compilation
|
||||||
|
3. **tsbuild tsfolders** - Auto-discover ts_* folders, compile in order
|
||||||
|
4. **tsbuild emitcheck <pattern>** - Validate emit without output
|
||||||
|
5. **tsbuild check [pattern]** - Type check only
|
||||||
|
|
||||||
|
### CLI Flags
|
||||||
|
- `--skiplibcheck` - Skip .d.ts type checking (shows warning)
|
||||||
|
- `--confirmskiplibcheck` - Extended warning with 5s pause
|
||||||
|
- `--disallowimplicitany` - Stricter type checking
|
||||||
|
- `--commonjs` - Use CommonJS instead of ESNext
|
||||||
|
- `--quiet` - Suppress non-error output
|
||||||
|
- `--json` - JSON output format
|
||||||
|
|
||||||
|
## Architecture - Modular Structure
|
||||||
|
|
||||||
|
### Module Organization
|
||||||
|
```
|
||||||
|
ts/
|
||||||
|
index.ts # Main entry, re-exports all modules
|
||||||
|
plugins.ts # Dependency imports only
|
||||||
|
|
||||||
|
mod_fs/
|
||||||
|
index.ts # exports
|
||||||
|
classes.fshelpers.ts # FsHelpers - static filesystem utilities
|
||||||
|
|
||||||
|
mod_config/
|
||||||
|
index.ts # exports
|
||||||
|
classes.tsconfig.ts # TsConfig - tsconfig.json handling
|
||||||
|
classes.tspublishconfig.ts # TsPublishConfig - tspublish.json handling
|
||||||
|
|
||||||
|
mod_unpack/
|
||||||
|
index.ts # exports
|
||||||
|
classes.tsunpacker.ts # TsUnpacker - output flattening
|
||||||
|
|
||||||
|
mod_compiler/
|
||||||
|
index.ts # exports
|
||||||
|
classes.tscompiler.ts # TsCompiler + legacy compatibility functions
|
||||||
|
|
||||||
|
mod_cli/
|
||||||
|
index.ts # exports
|
||||||
|
classes.tsbuildcli.ts # TsBuildCli - CLI command handler
|
||||||
|
```
|
||||||
|
|
||||||
|
### Class Responsibilities
|
||||||
|
|
||||||
|
**TsCompiler** (`mod_compiler/classes.tscompiler.ts`)
|
||||||
|
- Core compilation with `compileFiles()`, `compileGlob()`
|
||||||
|
- Type checking with `checkTypes()`, `checkEmit()`
|
||||||
|
- Configuration via TsConfig
|
||||||
|
- Automatic unpacking via TsUnpacker
|
||||||
|
|
||||||
|
**TsConfig** (`mod_config/classes.tsconfig.ts`)
|
||||||
|
- Load and parse tsconfig.json
|
||||||
|
- Merge configuration with priority order
|
||||||
|
- Protected defaults handling
|
||||||
|
|
||||||
|
**TsPublishConfig** (`mod_config/classes.tspublishconfig.ts`)
|
||||||
|
- Load and parse tspublish.json
|
||||||
|
- `shouldUnpack` property (default true)
|
||||||
|
- `order` property for tsfolders ordering
|
||||||
|
|
||||||
|
**TsUnpacker** (`mod_unpack/classes.tsunpacker.ts`)
|
||||||
|
- Detect nested output structure
|
||||||
|
- Flatten output directories
|
||||||
|
- Configurable via TsPublishConfig
|
||||||
|
|
||||||
|
**FsHelpers** (`mod_fs/classes.fshelpers.ts`)
|
||||||
|
- `listFilesWithGlob()` - Glob pattern file listing
|
||||||
|
- `extractSourceFolder()` - Pattern parsing
|
||||||
|
- File/directory existence checks
|
||||||
|
- Directory operations
|
||||||
|
|
||||||
|
**TsBuildCli** (`mod_cli/classes.tsbuildcli.ts`)
|
||||||
|
- All CLI commands registered
|
||||||
|
- Uses TsCompiler for compilation
|
||||||
|
|
||||||
|
## Configuration Priority (5 levels)
|
||||||
|
1. Default options (hardcoded in TsConfig)
|
||||||
|
2. tsconfig.json (if exists)
|
||||||
|
3. Protected defaults (ensure integrity)
|
||||||
|
4. Programmatic options (function params)
|
||||||
|
5. CLI flags (highest priority)
|
||||||
|
|
||||||
|
### Protected Options
|
||||||
|
Cannot be overridden by tsconfig.json alone:
|
||||||
|
- `outDir: 'dist_ts/'` - Path transformation logic
|
||||||
|
- `noEmitOnError: true` - Build integrity
|
||||||
|
- `declaration: true` - Library support
|
||||||
|
- `inlineSourceMap: true` - Debugging
|
||||||
|
|
||||||
|
### Path Transformation
|
||||||
|
- Automatic: `./ts_interfaces` → `./dist_ts_interfaces`
|
||||||
|
- In tsconfig paths: `./ts_*` → `./dist_ts_*` (first array element only)
|
||||||
|
|
||||||
|
## Default Compiler Options
|
||||||
|
- Module: NodeNext (ESM with CommonJS fallback)
|
||||||
|
- Target: ESNext (latest JavaScript)
|
||||||
|
- Source Maps: Inline (no separate .map files)
|
||||||
|
- Declaration Files: ALWAYS generated (protected)
|
||||||
|
- Output: dist_ts/
|
||||||
|
- Implicit any: ALLOWED by default
|
||||||
|
- esModuleInterop: true
|
||||||
|
- verbatimModuleSyntax: true
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
### Error Summary Structure
|
||||||
|
```typescript
|
||||||
|
interface IErrorSummary {
|
||||||
|
errorsByFile: Record<string, Diagnostic[]>
|
||||||
|
generalErrors: Diagnostic[]
|
||||||
|
totalErrors: number
|
||||||
|
totalFiles: number
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compile Result Structure
|
||||||
|
```typescript
|
||||||
|
interface ICompileResult {
|
||||||
|
emittedFiles: string[]
|
||||||
|
errorSummary: IErrorSummary
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dependencies Used
|
||||||
|
- @git.zone/tspublish@^1.10.3 - Module ordering
|
||||||
|
- @push.rocks/smartcli@^4.0.19 - CLI framework
|
||||||
|
- @push.rocks/smartfile@^13.1.2 - File content handling
|
||||||
|
- @push.rocks/smartfs@^1.2.0 - Filesystem operations
|
||||||
|
- @push.rocks/smartpath@^6.0.0 - Path transformation utilities
|
||||||
|
- @push.rocks/smartpromise@^4.2.3 - Promise utilities
|
||||||
|
- @push.rocks/smartdelay@^3.0.5 - Delay utilities
|
||||||
|
- typescript@5.9.3 - TypeScript compiler
|
||||||
|
|
||||||
|
### smartfs Usage
|
||||||
|
- File listing: `smartfs.directory(path).recursive().filter(pattern).list()`
|
||||||
|
- File existence: `smartfs.file(path).exists()`
|
||||||
|
- Directory existence: `smartfs.directory(path).exists()`
|
||||||
|
- FsHelpers wraps smartfs for glob pattern support
|
||||||
|
|
||||||
|
## Unpack Feature
|
||||||
|
|
||||||
|
When TypeScript compiles files that import from sibling directories, it creates nested output:
|
||||||
|
```
|
||||||
|
dist_ts_core/
|
||||||
|
ts_core/ ← nested output
|
||||||
|
ts_shared/ ← pulled-in dependency
|
||||||
|
```
|
||||||
|
|
||||||
|
The unpack feature automatically flattens this to:
|
||||||
|
```
|
||||||
|
dist_ts_core/
|
||||||
|
index.js ← flat
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
- Reads `tspublish.json` from source folder
|
||||||
|
- `unpack: true` (default if not present) → flatten output
|
||||||
|
- `unpack: false` → skip unpacking
|
||||||
|
|
||||||
|
### Implementation
|
||||||
|
- `TsUnpacker` class in `mod_unpack/classes.tsunpacker.ts`
|
||||||
|
- Called automatically after each successful compilation in `TsCompiler.compileGlob()`
|
||||||
|
|
||||||
|
## Special Behaviors
|
||||||
|
|
||||||
|
### tsfolders Command Ordering
|
||||||
|
1. Always: ts_interfaces first (if no tspublish.json)
|
||||||
|
2. Always: ts_shared second (if no tspublish.json)
|
||||||
|
3. Then: Other folders by `order` property in their tspublish.json
|
||||||
|
4. Finally: Folders without order property (Infinity)
|
||||||
|
|
||||||
|
### check Command Default (No Arguments)
|
||||||
|
Two-phase check:
|
||||||
|
1. Phase 1: Type check ts/**/* (strict, include .d.ts)
|
||||||
|
2. Phase 2: Type check test/**/* (relaxed, skipLibCheck: true)
|
||||||
|
|
||||||
|
## Edge Cases
|
||||||
|
|
||||||
|
1. **Empty file list** - Returns [], no error
|
||||||
|
2. **Glob duplicates** - Files compile multiple times
|
||||||
|
3. **Non-existent files** - TypeScript "file not found" errors
|
||||||
|
4. **skipLibCheck warning** - 1-line default, 5s pause with --confirmskiplibcheck
|
||||||
|
5. **Missing tsconfig.json** - Graceful fallback, no error
|
||||||
|
6. **Module resolution** - --commonjs switches to NodeJs (not NodeNext)
|
||||||
|
7. **Source maps** - Inline only (not separate .map files)
|
||||||
|
8. **File filtering** - Only .ts and .tsx; .d.ts and .js ignored
|
||||||
|
|
||||||
|
## Build Safety Features
|
||||||
|
- `noEmitOnError: true` - Prevents broken builds
|
||||||
|
- Error aggregation before final output
|
||||||
|
- Protected options ensure integrity
|
||||||
|
- Pre-emit checks before emit phase
|
||||||
|
- CLI exit code handling (0=success, 1=error)
|
||||||
|
|
||||||
|
## Recent Changes
|
||||||
|
- 3.1.4+ - Major restructuring with mod_* modules
|
||||||
|
- Full OO architecture with TsCompiler, TsConfig, TsPublishConfig, TsUnpacker, TsBuildCli, FsHelpers
|
||||||
|
- Backward compatibility maintained for legacy functions
|
||||||
|
- Automatic "unpack" feature for nested output directories
|
||||||
|
- Migrated filesystem operations from smartfile to smartfs
|
||||||
|
|||||||
712
readme.md
712
readme.md
@@ -1,225 +1,617 @@
|
|||||||
# @git.zone/tsbuild
|
# @git.zone/tsbuild
|
||||||
|
|
||||||
A flexible TypeScript compiler that leverages the latest TypeScript features to streamline your build process.
|
A powerful, modern TypeScript build tool with smart defaults, full tsconfig.json support, and automatic output directory management.
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
Add `@git.zone/tsbuild` to your project using npm or yarn:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install @git.zone/tsbuild
|
# Using pnpm (recommended)
|
||||||
|
pnpm install @git.zone/tsbuild --save-dev
|
||||||
|
|
||||||
|
# Using npm
|
||||||
|
npm install @git.zone/tsbuild --save-dev
|
||||||
```
|
```
|
||||||
|
|
||||||
or
|
## Why tsbuild?
|
||||||
|
|
||||||
```bash
|
- **Smart tsconfig.json Integration** - Respects all your compiler options with intelligent merging
|
||||||
yarn add @git.zone/tsbuild
|
- **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
|
||||||
|
|
||||||
## Key Features
|
## Quick Start
|
||||||
|
|
||||||
- Utilize the latest TypeScript features
|
### CLI Usage
|
||||||
- Flexible API for customized compilation processes
|
|
||||||
- Intuitive CLI for common build tasks
|
|
||||||
- Support for glob patterns to easily target files
|
|
||||||
- Ordered compilation to respect module dependencies
|
|
||||||
|
|
||||||
## API Reference
|
|
||||||
|
|
||||||
### Core Compilation Functions
|
|
||||||
|
|
||||||
#### `compileFileArray(fileStringArrayArg: string[], compilerOptionsArg?: CompilerOptions, argvArg?: any): Promise<any[]>`
|
|
||||||
|
|
||||||
Compiles an array of TypeScript files with customizable compiler options.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { compileFileArray } from '@git.zone/tsbuild';
|
|
||||||
|
|
||||||
const files = [
|
|
||||||
'./src/file1.ts',
|
|
||||||
'./src/file2.ts',
|
|
||||||
];
|
|
||||||
const options = {
|
|
||||||
target: "ES2020",
|
|
||||||
module: "CommonJS"
|
|
||||||
};
|
|
||||||
|
|
||||||
compileFileArray(files, options)
|
|
||||||
.then(compiledFiles => {
|
|
||||||
console.log('Compiled Files:', compiledFiles);
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Compilation Error:', error);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `compileGlobStringObject(globStringObjectArg: Record<string, string>, tsOptionsArg?: CompilerOptions, cwdArg?: string, argvArg?: any): Promise<any[]>`
|
|
||||||
|
|
||||||
Compiles files matching glob patterns to specified output directories.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { compileGlobStringObject } from '@git.zone/tsbuild';
|
|
||||||
|
|
||||||
const globPattern = {
|
|
||||||
'./src/**/*.ts': './dist',
|
|
||||||
};
|
|
||||||
const compilerOptions = {
|
|
||||||
target: "ESNext",
|
|
||||||
module: "ESNext",
|
|
||||||
};
|
|
||||||
|
|
||||||
compileGlobStringObject(globPattern, compilerOptions)
|
|
||||||
.then(compiledFiles => {
|
|
||||||
console.log('Compilation complete:', compiledFiles);
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error during compilation:', error);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Command Line Interface
|
|
||||||
|
|
||||||
The CLI provides convenient commands for common compilation tasks.
|
|
||||||
|
|
||||||
### Standard Command
|
|
||||||
|
|
||||||
Compiles all TypeScript files in the `ts/` directory to the `dist_ts` directory:
|
|
||||||
|
|
||||||
|
**Compile your TypeScript project:**
|
||||||
```bash
|
```bash
|
||||||
npx tsbuild
|
npx tsbuild
|
||||||
```
|
```
|
||||||
|
Compiles `./ts/**/*.ts` to `./dist_ts/`
|
||||||
|
|
||||||
### Custom Command
|
**Custom directories:**
|
||||||
|
|
||||||
Compile specific directories to corresponding `dist_` directories:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx tsbuild custom <Dir1> <Dir2> ...
|
npx tsbuild custom src utils
|
||||||
```
|
```
|
||||||
|
Compiles:
|
||||||
|
- `./src/**/*.ts` to `./dist_src/`
|
||||||
|
- `./utils/**/*.ts` to `./dist_utils/`
|
||||||
|
|
||||||
Example: `npx tsbuild custom src utils` compiles:
|
**Auto-discover and compile in dependency order:**
|
||||||
- `./src/**/*.ts` → `./dist_src`
|
|
||||||
- `./utils/**/*.ts` → `./dist_utils`
|
|
||||||
|
|
||||||
### TSFolders Command
|
|
||||||
|
|
||||||
Compiles TypeScript folders in a specific order based on dependencies:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx tsbuild tsfolders
|
npx tsbuild tsfolders
|
||||||
```
|
```
|
||||||
|
Finds all `ts_*` folders and compiles them respecting dependencies.
|
||||||
|
|
||||||
This command:
|
### Programmatic Usage
|
||||||
1. Identifies all folders starting with `ts` in the current directory
|
|
||||||
2. Prioritizes `ts_interfaces` and `ts_shared` to be compiled first
|
|
||||||
3. Orders other folders based on the `order` property in their `tspublish.json` files (if available)
|
|
||||||
4. Compiles each folder to its corresponding `dist_` folder
|
|
||||||
|
|
||||||
Example compilation order output:
|
**Basic compilation:**
|
||||||
```
|
```typescript
|
||||||
compiling in this order:
|
import { TsCompiler } from '@git.zone/tsbuild';
|
||||||
[ 'ts_interfaces', 'ts_shared', 'ts_core', 'ts_utils', 'ts_modules' ]
|
|
||||||
|
const compiler = new TsCompiler();
|
||||||
|
await compiler.compileFilesOrThrow([
|
||||||
|
'./src/index.ts',
|
||||||
|
'./src/utils.ts'
|
||||||
|
], { outDir: './dist' });
|
||||||
```
|
```
|
||||||
|
|
||||||
### EmitCheck Command
|
**Production-ready with error tracking (recommended):**
|
||||||
|
```typescript
|
||||||
|
import { TsCompiler } from '@git.zone/tsbuild';
|
||||||
|
|
||||||
Checks if TypeScript files can be emitted without actually emitting them:
|
const compiler = new TsCompiler();
|
||||||
|
const result = await compiler.compileFiles([
|
||||||
|
'./src/index.ts',
|
||||||
|
'./src/utils.ts'
|
||||||
|
], { outDir: './dist' });
|
||||||
|
|
||||||
|
if (result.errorSummary.totalErrors > 0) {
|
||||||
|
console.error(`Compilation failed with ${result.errorSummary.totalErrors} errors`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Compiled ${result.emittedFiles.length} files successfully!`);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Glob pattern compilation:**
|
||||||
|
```typescript
|
||||||
|
import { TsCompiler } from '@git.zone/tsbuild';
|
||||||
|
|
||||||
|
const compiler = new TsCompiler();
|
||||||
|
await compiler.compileGlob({
|
||||||
|
'./ts/**/*.ts': './dist_ts',
|
||||||
|
'./ts_web/**/*.ts': './dist_web'
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## CLI Commands
|
||||||
|
|
||||||
|
### 1. Default Build
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx tsbuild emitcheck <file_or_glob_pattern> [additional_patterns ...]
|
npx tsbuild [options]
|
||||||
```
|
```
|
||||||
|
|
||||||
This command:
|
Compiles all TypeScript files from `./ts/` to `./dist_ts/`
|
||||||
1. Performs type checking on the specified TypeScript file(s)
|
|
||||||
2. Supports both direct file paths and glob patterns
|
|
||||||
3. Reports any errors that would prevent successful compilation
|
|
||||||
4. Exits with code 0 if all files can be emitted, or 1 if any cannot
|
|
||||||
5. Doesn't produce any output files
|
|
||||||
|
|
||||||
Example usage with specific files:
|
**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
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
```bash
|
```bash
|
||||||
|
# Standard build
|
||||||
|
npx tsbuild
|
||||||
|
|
||||||
|
# Build with JSON output for CI
|
||||||
|
npx tsbuild --json --quiet
|
||||||
|
|
||||||
|
# CommonJS build
|
||||||
|
npx tsbuild --commonjs
|
||||||
|
|
||||||
|
# Strict mode
|
||||||
|
npx tsbuild --disallowimplicitany
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Custom Directories
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx tsbuild custom <dir1> <dir2> ... [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
Compile specific directories to their corresponding `dist_` folders.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Compile src and utils
|
||||||
|
npx tsbuild custom src utils
|
||||||
|
# Creates: ./dist_src/ and ./dist_utils/
|
||||||
|
|
||||||
|
# Multiple directories with options
|
||||||
|
npx tsbuild custom api models services --commonjs
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. TSFolders (Dependency-Aware)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx tsbuild tsfolders [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
Automatically discovers and compiles all `ts_*` folders in dependency order:
|
||||||
|
|
||||||
|
1. Prioritizes `ts_interfaces` first (if no tspublish.json)
|
||||||
|
2. Prioritizes `ts_shared` second (if no tspublish.json)
|
||||||
|
3. Reads `tspublish.json` in each folder for `order` property
|
||||||
|
4. Compiles in correct sequence
|
||||||
|
|
||||||
|
**Example output:**
|
||||||
|
```
|
||||||
|
TypeScript Folder Compilation Plan (5 folders)
|
||||||
|
1/5 ts_interfaces
|
||||||
|
2/5 ts_shared
|
||||||
|
3/5 ts_core
|
||||||
|
4/5 ts_utils
|
||||||
|
5/5 ts_modules
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Emit Check
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx tsbuild emitcheck <file_or_pattern> [more...] [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
Validates TypeScript files can be compiled without actually emitting them.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check specific files
|
||||||
npx tsbuild emitcheck src/main.ts src/utils.ts
|
npx tsbuild emitcheck src/main.ts src/utils.ts
|
||||||
```
|
|
||||||
|
|
||||||
Example usage with glob patterns:
|
# Check with glob patterns
|
||||||
```bash
|
|
||||||
npx tsbuild emitcheck "src/**/*.ts" "test/**/*.ts"
|
npx tsbuild emitcheck "src/**/*.ts" "test/**/*.ts"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Check Command
|
**Exit codes:**
|
||||||
|
- `0` - All files can be emitted
|
||||||
|
- `1` - One or more files have errors
|
||||||
|
|
||||||
Performs type checking on TypeScript files specified by glob patterns without emitting them:
|
### 5. Type Check
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx tsbuild check <glob_pattern> [additional_patterns ...]
|
npx tsbuild check [pattern] [more...] [options]
|
||||||
```
|
```
|
||||||
|
|
||||||
This command:
|
Performs type checking without emitting files.
|
||||||
1. Efficiently type checks TypeScript files matching the given glob patterns
|
|
||||||
2. Supports multiple glob patterns and direct file paths
|
|
||||||
3. Reports any type errors found in the matched files
|
|
||||||
4. Exits with code 0 if all files pass type checking, or 1 if any have errors
|
|
||||||
5. Doesn't produce any output files
|
|
||||||
|
|
||||||
Example usage:
|
**With arguments:** Check specified files/patterns
|
||||||
```bash
|
|
||||||
npx tsbuild check ts/**/*
|
|
||||||
```
|
|
||||||
|
|
||||||
Example usage with multiple patterns:
|
|
||||||
```bash
|
```bash
|
||||||
|
npx tsbuild check "ts/**/*.ts"
|
||||||
npx tsbuild check "src/**/*.ts" "test/**/*.ts"
|
npx tsbuild check "src/**/*.ts" "test/**/*.ts"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Compiler Options
|
**Without arguments:** Two-phase default check
|
||||||
|
1. Phase 1: Type check `ts/**/*` (strict, includes .d.ts)
|
||||||
|
2. Phase 2: Type check `test/**/*` (relaxed, skipLibCheck: true)
|
||||||
|
|
||||||
Additional flags can be passed to any command to modify the compilation behavior:
|
|
||||||
|
|
||||||
- `--skiplibcheck`: Skip type checking of declaration files (shows warning)
|
|
||||||
- `--disallowimplicitany`: Disallow variables to be implicitly typed as `any` (implicit any is allowed by default)
|
|
||||||
- `--commonjs`: Use CommonJS module format instead of ESNext
|
|
||||||
|
|
||||||
Example:
|
|
||||||
```bash
|
```bash
|
||||||
npx tsbuild --skiplibcheck --disallowimplicitany
|
npx tsbuild check
|
||||||
|
# Running default type checking sequence...
|
||||||
|
# Checking ts/**/* files...
|
||||||
|
# Checking test/**/* files with --skiplibcheck...
|
||||||
|
# All default type checks passed!
|
||||||
```
|
```
|
||||||
|
|
||||||
## Default Compiler Options
|
## API Reference
|
||||||
|
|
||||||
By default, `@git.zone/tsbuild` uses the following compiler options:
|
### TsCompiler Class
|
||||||
|
|
||||||
|
The main class for TypeScript compilation.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
{
|
import { TsCompiler } from '@git.zone/tsbuild';
|
||||||
declaration: true,
|
|
||||||
emitDecoratorMetadata: true,
|
const compiler = new TsCompiler(cwd?: string, argvArg?: any);
|
||||||
experimentalDecorators: true,
|
```
|
||||||
inlineSourceMap: true,
|
|
||||||
noEmitOnError: true,
|
**Constructor Parameters:**
|
||||||
outDir: 'dist_ts/',
|
- `cwd` - Working directory (defaults to `process.cwd()`)
|
||||||
module: ModuleKind.NodeNext,
|
- `argvArg` - CLI arguments object for flags like `--skiplibcheck`, `--quiet`, etc.
|
||||||
target: ScriptTarget.ESNext,
|
|
||||||
moduleResolution: ModuleResolutionKind.NodeNext,
|
#### compileFiles(fileNames, customOptions?, taskInfo?)
|
||||||
lib: ['lib.dom.d.ts', 'lib.es2022.d.ts'],
|
|
||||||
noImplicitAny: false, // Now allowing implicit any by default
|
Compile files with error tracking. Returns result instead of throwing.
|
||||||
esModuleInterop: true,
|
|
||||||
useDefineForClassFields: false,
|
```typescript
|
||||||
verbatimModuleSyntax: true,
|
const result = await compiler.compileFiles(
|
||||||
baseUrl: './',
|
['./src/index.ts', './src/utils.ts'],
|
||||||
|
{ outDir: './dist' }
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`Emitted: ${result.emittedFiles.length} files`);
|
||||||
|
console.log(`Errors: ${result.errorSummary.totalErrors}`);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Returns:** `Promise<ICompileResult>`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface ICompileResult {
|
||||||
|
emittedFiles: string[];
|
||||||
|
errorSummary: IErrorSummary;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IErrorSummary {
|
||||||
|
errorsByFile: Record<string, Diagnostic[]>;
|
||||||
|
generalErrors: Diagnostic[];
|
||||||
|
totalErrors: number;
|
||||||
|
totalFiles: number;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
These options can be overridden by providing a custom `CompilerOptions` object.
|
#### compileFilesOrThrow(fileNames, customOptions?)
|
||||||
|
|
||||||
## Path Resolution
|
Compile files and throw on error. For simple scripts.
|
||||||
|
|
||||||
The package automatically detects and applies path mappings from your `tsconfig.json`. When it finds path mappings, it adjusts them to work with the compiled output by replacing `./ts_` with `./dist_ts_` in path aliases.
|
```typescript
|
||||||
|
try {
|
||||||
|
const emittedFiles = await compiler.compileFilesOrThrow(
|
||||||
|
['./src/index.ts'],
|
||||||
|
{ outDir: './dist' }
|
||||||
|
);
|
||||||
|
console.log('Compiled:', emittedFiles);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Compilation failed!');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Notes and Best Practices
|
**Returns:** `Promise<string[]>` - Array of emitted file paths
|
||||||
|
|
||||||
- Each glob pattern compilation runs in its own pass, which may cause duplicate error messages if shared files are included in multiple patterns
|
#### compileGlob(globPatterns, customOptions?)
|
||||||
- Use the `--skiplibcheck` option cautiously as it will pause for 5 seconds with a warning before continuing
|
|
||||||
- If you need different output configurations for different file sets, use multiple calls to `compileGlobStringObject`
|
|
||||||
|
|
||||||
|
Compile multiple glob patterns to different destinations. Automatically clears output directories before compilation and unpacks nested output.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const result = await compiler.compileGlob({
|
||||||
|
'./ts/**/*.ts': './dist_ts',
|
||||||
|
'./ts_web/**/*.ts': './dist_web',
|
||||||
|
'./ts_node/**/*.ts': './dist_node'
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**Returns:** `Promise<ICompileResult>`
|
||||||
|
|
||||||
|
#### checkTypes(fileNames, customOptions?)
|
||||||
|
|
||||||
|
Type check files without emitting. Fast validation.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const success = await compiler.checkTypes(['./src/**/*.ts']);
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
console.error('Type errors found!');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Returns:** `Promise<boolean>` - `true` if no errors
|
||||||
|
|
||||||
|
#### checkEmit(fileNames, customOptions?)
|
||||||
|
|
||||||
|
Validate files can be emitted without actually emitting.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const canEmit = await compiler.checkEmit(['./src/index.ts']);
|
||||||
|
|
||||||
|
if (!canEmit) {
|
||||||
|
console.error('Cannot emit these files!');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Returns:** `Promise<boolean>` - `true` if can emit
|
||||||
|
|
||||||
|
#### createOptions(customOptions?)
|
||||||
|
|
||||||
|
Get merged compiler options (useful for debugging).
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const options = compiler.createOptions({ strict: true });
|
||||||
|
console.log(options); // Shows merged options
|
||||||
|
```
|
||||||
|
|
||||||
|
### Supporting Classes
|
||||||
|
|
||||||
|
#### TsConfig
|
||||||
|
|
||||||
|
TypeScript configuration management.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { TsConfig } from '@git.zone/tsbuild';
|
||||||
|
|
||||||
|
const config = new TsConfig(process.cwd());
|
||||||
|
const options = config.merge({ target: 'ES2022' }, argvArg);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### TsPublishConfig
|
||||||
|
|
||||||
|
Reads `tspublish.json` for module configuration.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { TsPublishConfig } from '@git.zone/tsbuild';
|
||||||
|
|
||||||
|
const pubConfig = new TsPublishConfig('./ts_core');
|
||||||
|
console.log(pubConfig.shouldUnpack); // true/false
|
||||||
|
console.log(pubConfig.order); // number or undefined
|
||||||
|
```
|
||||||
|
|
||||||
|
#### TsUnpacker
|
||||||
|
|
||||||
|
Flattens nested TypeScript output directories.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { TsUnpacker } from '@git.zone/tsbuild';
|
||||||
|
|
||||||
|
const unpacker = new TsUnpacker('./dist_ts', './ts');
|
||||||
|
await unpacker.unpack();
|
||||||
|
```
|
||||||
|
|
||||||
|
#### FsHelpers
|
||||||
|
|
||||||
|
Static filesystem utilities.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { FsHelpers } from '@git.zone/tsbuild';
|
||||||
|
|
||||||
|
const files = await FsHelpers.listFilesWithGlob('./', 'ts/**/*.ts');
|
||||||
|
const exists = await FsHelpers.fileExists('./tsconfig.json');
|
||||||
|
const dirExists = await FsHelpers.directoryExists('./ts');
|
||||||
|
```
|
||||||
|
|
||||||
|
#### TsBuildCli
|
||||||
|
|
||||||
|
CLI command handler. Used internally by the CLI.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { TsBuildCli, runCli } from '@git.zone/tsbuild';
|
||||||
|
|
||||||
|
// Run the CLI
|
||||||
|
runCli();
|
||||||
|
|
||||||
|
// Or with custom working directory
|
||||||
|
const cli = new TsBuildCli('/path/to/project');
|
||||||
|
cli.run();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### tsconfig.json Support
|
||||||
|
|
||||||
|
tsbuild fully supports all compiler options from `tsconfig.json`. Your project configuration is respected and intelligently merged.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "NodeNext",
|
||||||
|
"moduleResolution": "NodeNext",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"verbatimModuleSyntax": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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)
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|
||||||
|
### Default Compiler Options
|
||||||
|
|
||||||
|
When no tsconfig.json exists:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
declaration: true, // Generate .d.ts files
|
||||||
|
emitDecoratorMetadata: true, // Support DI frameworks
|
||||||
|
experimentalDecorators: true, // Enable decorators
|
||||||
|
inlineSourceMap: true, // Debug-friendly
|
||||||
|
noEmitOnError: true, // Fail-fast on errors
|
||||||
|
outDir: 'dist_ts/', // Output directory
|
||||||
|
module: 'NodeNext', // Modern Node.js modules
|
||||||
|
target: 'ESNext', // Latest JavaScript
|
||||||
|
moduleResolution: 'NodeNext',
|
||||||
|
noImplicitAny: false, // Flexible for quick development
|
||||||
|
esModuleInterop: true, // CJS/ESM interop
|
||||||
|
verbatimModuleSyntax: true // Explicit imports/exports
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Path Transformation
|
||||||
|
|
||||||
|
tsbuild automatically transforms path mappings:
|
||||||
|
|
||||||
|
**tsconfig.json:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"paths": {
|
||||||
|
"@models/*": ["./ts_models/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Automatic transformation:**
|
||||||
|
```
|
||||||
|
./ts_models/* -> ./dist_ts_models/*
|
||||||
|
```
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
### Clean Builds
|
||||||
|
|
||||||
|
Output directories are automatically cleared before compilation:
|
||||||
|
|
||||||
|
```
|
||||||
|
Clearing output directory: ./dist_ts
|
||||||
|
Compiling 14 files from ./ts/**/*.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
This ensures no stale files remain from previous builds.
|
||||||
|
|
||||||
|
### 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
|
||||||
|
```
|
||||||
|
|
||||||
|
tsbuild automatically flattens this to:
|
||||||
|
|
||||||
|
```
|
||||||
|
dist_ts_core/
|
||||||
|
index.js <- flat
|
||||||
|
```
|
||||||
|
|
||||||
|
Configure via `tspublish.json` in source folder:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"unpack": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Set `"unpack": false` to disable.
|
||||||
|
|
||||||
|
### Decorator Support
|
||||||
|
|
||||||
|
First-class decorator support out of the box:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@Injectable()
|
||||||
|
class UserService {
|
||||||
|
constructor(
|
||||||
|
@Inject('CONFIG') private config: Config,
|
||||||
|
private logger: Logger
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Works with NestJS, TypeORM, Inversify, Angular, and other DI frameworks.
|
||||||
|
|
||||||
|
## CI/CD Integration
|
||||||
|
|
||||||
|
### GitHub Actions
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: Build
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
|
||||||
|
- run: npm install
|
||||||
|
- run: npx tsbuild
|
||||||
|
```
|
||||||
|
|
||||||
|
### JSON Output
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx tsbuild --json --quiet
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"totals": {
|
||||||
|
"errors": 0,
|
||||||
|
"filesWithErrors": 0,
|
||||||
|
"tasks": 1
|
||||||
|
},
|
||||||
|
"errorsByFile": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Package.json Scripts
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsbuild",
|
||||||
|
"build:prod": "tsbuild --disallowimplicitany",
|
||||||
|
"typecheck": "tsbuild check",
|
||||||
|
"pretest": "tsbuild emitcheck 'test/**/*.ts'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
**"Cannot find module" errors in compiled output**
|
||||||
|
|
||||||
|
Make sure path mappings are configured in tsconfig.json. tsbuild automatically transforms them.
|
||||||
|
|
||||||
|
**Decorator errors**
|
||||||
|
|
||||||
|
Ensure your tsconfig.json has:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"emitDecoratorMetadata": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Slow compilation**
|
||||||
|
|
||||||
|
Use `--skiplibcheck` to skip declaration file checking:
|
||||||
|
```bash
|
||||||
|
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
|
## License and Legal Information
|
||||||
|
|
||||||
@@ -234,7 +626,7 @@ This project is owned and maintained by Task Venture Capital GmbH. The names and
|
|||||||
### Company Information
|
### Company Information
|
||||||
|
|
||||||
Task Venture Capital GmbH
|
Task Venture Capital GmbH
|
||||||
Registered at District court Bremen HRB 35230 HB, Germany
|
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 if you require further information, please contact us via email at hello@task.vc.
|
||||||
|
|
||||||
|
|||||||
1
test/assets/output/tocompile.d.ts
vendored
1
test/assets/output/tocompile.d.ts
vendored
@@ -1 +0,0 @@
|
|||||||
export {};
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
console.log('test');
|
|
||||||
console.log('test2');
|
|
||||||
import * as early from '@push.rocks/early';
|
|
||||||
early.start();
|
|
||||||
early.stop();
|
|
||||||
import { anExportedString } from './tocompile2.js';
|
|
||||||
console.log(anExportedString);
|
|
||||||
class test2 {
|
|
||||||
constructor() {
|
|
||||||
this.test = [];
|
|
||||||
console.log('hi');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const run = async () => {
|
|
||||||
return 'hi';
|
|
||||||
};
|
|
||||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidG9jb21waWxlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdG9jb21waWxlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDLENBQUM7QUFDcEIsT0FBTyxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsQ0FBQztBQUVyQixPQUFPLEtBQUssS0FBSyxNQUFNLG1CQUFtQixDQUFDO0FBRTNDLEtBQUssQ0FBQyxLQUFLLEVBQUUsQ0FBQztBQUNkLEtBQUssQ0FBQyxJQUFJLEVBQUUsQ0FBQztBQUViLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBQ25ELE9BQU8sQ0FBQyxHQUFHLENBQUMsZ0JBQWdCLENBQUMsQ0FBQztBQUU5QixNQUFNLEtBQUs7SUFFVDtRQURBLFNBQUksR0FBYSxFQUFFLENBQUM7UUFFbEIsT0FBTyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUNwQixDQUFDO0NBQ0Y7QUFFRCxNQUFNLEdBQUcsR0FBRyxLQUFLLElBQXFCLEVBQUU7SUFDdEMsT0FBTyxJQUFJLENBQUM7QUFDZCxDQUFDLENBQUMifQ==
|
|
||||||
1
test/assets/output/tocompile2.d.ts
vendored
1
test/assets/output/tocompile2.d.ts
vendored
@@ -1 +0,0 @@
|
|||||||
export declare const anExportedString = "exported string";
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
console.log('hello');
|
|
||||||
export const anExportedString = 'exported string';
|
|
||||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidG9jb21waWxlMi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RvY29tcGlsZTIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsQ0FBQztBQUNyQixNQUFNLENBQUMsTUFBTSxnQkFBZ0IsR0FBRyxpQkFBaUIsQ0FBQyJ9
|
|
||||||
18
test/test.ts
18
test/test.ts
@@ -1,19 +1,21 @@
|
|||||||
import { tap, expect, expectAsync } from '@push.rocks/tapbundle';
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||||
|
|
||||||
import * as tsbuild from '../ts/index.js';
|
import { TsCompiler } from '../ts/index.js';
|
||||||
|
|
||||||
let assetfiles: string[] = ['./test/assets/tocompile.ts', './test/assets/tocompile2.ts'];
|
const assetfiles: string[] = ['./test/assets/tocompile.ts', './test/assets/tocompile2.ts'];
|
||||||
|
|
||||||
let assetfiles2 = {
|
const assetfiles2 = {
|
||||||
'./test/assets/**/!(*.d.ts|*.js|output)': './test/assets/output',
|
'./test/assets/**/!(*.d.ts|*.js|output)': './test/assets/output',
|
||||||
};
|
};
|
||||||
|
|
||||||
tap.test('should convert files from an array with single files to output', async (tools) => {
|
tap.test('should compile files from an array', async () => {
|
||||||
tsbuild.compileFileArray(assetfiles, { outDir: './test/assets/output' });
|
const compiler = new TsCompiler();
|
||||||
|
await compiler.compileFilesOrThrow(assetfiles, { outDir: './test/assets/output' });
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('should convert files from an array with single files to output', async (tools) => {
|
tap.test('should compile files from glob pattern object', async () => {
|
||||||
tsbuild.compileGlobStringObject(assetfiles2);
|
const compiler = new TsCompiler();
|
||||||
|
await compiler.compileGlob(assetfiles2);
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.start();
|
tap.start();
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@git.zone/tsbuild',
|
name: '@git.zone/tsbuild',
|
||||||
version: '2.4.1',
|
version: '4.0.2',
|
||||||
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.'
|
||||||
}
|
}
|
||||||
|
|||||||
13
ts/index.ts
13
ts/index.ts
@@ -1,5 +1,14 @@
|
|||||||
import * as early from '@push.rocks/early';
|
import * as early from '@push.rocks/early';
|
||||||
early.start('tsbuild');
|
early.start('tsbuild');
|
||||||
export * from './tsbuild.exports.js';
|
|
||||||
export * from './tsbuild.cli.js';
|
// Export from new modular structure
|
||||||
|
export * from './mod_fs/index.js';
|
||||||
|
export * from './mod_config/index.js';
|
||||||
|
export * from './mod_unpack/index.js';
|
||||||
|
export * from './mod_compiler/index.js';
|
||||||
|
export * from './mod_cli/index.js';
|
||||||
|
|
||||||
|
// Re-export TypeScript types for convenience
|
||||||
|
export type { CompilerOptions, ScriptTarget, ModuleKind } from 'typescript';
|
||||||
|
|
||||||
early.stop();
|
early.stop();
|
||||||
|
|||||||
320
ts/mod_cli/classes.tsbuildcli.ts
Normal file
320
ts/mod_cli/classes.tsbuildcli.ts
Normal file
@@ -0,0 +1,320 @@
|
|||||||
|
import * as path from 'path';
|
||||||
|
import * as smartcli from '@push.rocks/smartcli';
|
||||||
|
import * as smartpath from '@push.rocks/smartpath';
|
||||||
|
import * as tspublish from '@git.zone/tspublish';
|
||||||
|
|
||||||
|
import { TsCompiler } from '../mod_compiler/index.js';
|
||||||
|
import { FsHelpers } from '../mod_fs/index.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TsBuildCli handles all CLI commands for tsbuild.
|
||||||
|
* Provides commands for compiling, type checking, and emit validation.
|
||||||
|
*/
|
||||||
|
export class TsBuildCli {
|
||||||
|
private cli: smartcli.Smartcli;
|
||||||
|
private cwd: string;
|
||||||
|
|
||||||
|
constructor(cwd: string = process.cwd()) {
|
||||||
|
this.cwd = cwd;
|
||||||
|
this.cli = new smartcli.Smartcli();
|
||||||
|
this.registerCommands();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register all CLI commands
|
||||||
|
*/
|
||||||
|
private registerCommands(): void {
|
||||||
|
this.registerStandardCommand();
|
||||||
|
this.registerCustomCommand();
|
||||||
|
this.registerEmitCheckCommand();
|
||||||
|
this.registerTsFoldersCommand();
|
||||||
|
this.registerCheckCommand();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard command: compiles ts folder to dist_ts
|
||||||
|
*/
|
||||||
|
private registerStandardCommand(): void {
|
||||||
|
this.cli.standardCommand().subscribe(async (argvArg) => {
|
||||||
|
const compiler = new TsCompiler(this.cwd, argvArg);
|
||||||
|
await compiler.compileGlob({
|
||||||
|
'./ts/**/*.ts': './dist_ts',
|
||||||
|
});
|
||||||
|
const summary = (argvArg as any)?.__tsbuildFinalErrorSummary;
|
||||||
|
if (summary && summary.totalErrors > 0) {
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom command: compiles specified directories to dist_ prefixed directories
|
||||||
|
*/
|
||||||
|
private registerCustomCommand(): void {
|
||||||
|
this.cli.addCommand('custom').subscribe(async (argvArg) => {
|
||||||
|
const listedDirectories = argvArg._;
|
||||||
|
listedDirectories.shift(); // removes the first element that is "custom"
|
||||||
|
|
||||||
|
const compilationCommandObject: Record<string, string> = {};
|
||||||
|
for (const directory of listedDirectories) {
|
||||||
|
compilationCommandObject[`./${directory}/**/*.ts`] = `./dist_${directory}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const compiler = new TsCompiler(this.cwd, argvArg);
|
||||||
|
await compiler.compileGlob(compilationCommandObject);
|
||||||
|
|
||||||
|
const summary = (argvArg as any)?.__tsbuildFinalErrorSummary;
|
||||||
|
if (summary && summary.totalErrors > 0) {
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit check command: validates files can be emitted without producing output
|
||||||
|
*/
|
||||||
|
private registerEmitCheckCommand(): void {
|
||||||
|
this.cli.addCommand('emitcheck').subscribe(async (argvArg) => {
|
||||||
|
const patterns = argvArg._.slice(1);
|
||||||
|
|
||||||
|
if (patterns.length === 0) {
|
||||||
|
console.error('\n❌ Error: Please provide at least one TypeScript file path or glob pattern');
|
||||||
|
console.error(' Usage: tsbuild emitcheck <file_or_glob_pattern> [additional_patterns ...]\n');
|
||||||
|
console.error(' Example: tsbuild emitcheck "src/**/*.ts" "test/**/*.ts"\n');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const allFiles = await this.collectFilesFromPatterns(patterns);
|
||||||
|
|
||||||
|
if (allFiles.length === 0) {
|
||||||
|
console.error('\n❌ Error: No TypeScript files found to check');
|
||||||
|
console.error(' Please verify your file paths or glob patterns.\n');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n🔎 Found ${allFiles.length} TypeScript file${allFiles.length !== 1 ? 's' : ''} to check`);
|
||||||
|
|
||||||
|
const compiler = new TsCompiler(this.cwd, argvArg);
|
||||||
|
const success = await compiler.checkEmit(allFiles);
|
||||||
|
|
||||||
|
process.exit(success ? 0 : 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TsFolders command: compiles all ts_* directories in order
|
||||||
|
*/
|
||||||
|
private registerTsFoldersCommand(): void {
|
||||||
|
this.cli.addCommand('tsfolders').subscribe(async (argvArg) => {
|
||||||
|
// List folders matching /^ts/ regex
|
||||||
|
const allEntries = await FsHelpers.listDirectory(this.cwd);
|
||||||
|
const tsFolders = allEntries
|
||||||
|
.filter((e) => e.isDirectory && /^ts/.test(e.name))
|
||||||
|
.map((e) => e.name);
|
||||||
|
|
||||||
|
// Get tspublish.json based ranking
|
||||||
|
const tsPublishInstance = new tspublish.TsPublish();
|
||||||
|
const tsPublishModules = await tsPublishInstance.getModuleSubDirs(this.cwd);
|
||||||
|
|
||||||
|
// Create an array with folder names and their ranks
|
||||||
|
const foldersWithOrder: Array<{ folder: string; rank: number }> = [];
|
||||||
|
|
||||||
|
for (const folder of tsFolders) {
|
||||||
|
let rank = Infinity;
|
||||||
|
if (tsPublishModules[folder] && tsPublishModules[folder].order !== undefined) {
|
||||||
|
rank = tsPublishModules[folder].order;
|
||||||
|
}
|
||||||
|
foldersWithOrder.push({ folder, rank });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort the folders based on rank
|
||||||
|
foldersWithOrder.sort((a, b) => a.rank - b.rank);
|
||||||
|
|
||||||
|
// Construct the sorted list of folders
|
||||||
|
const sortedTsFolders: string[] = [];
|
||||||
|
|
||||||
|
for (const item of foldersWithOrder) {
|
||||||
|
sortedTsFolders.push(item.folder);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure ts_interfaces is first and ts_shared is second if they exist
|
||||||
|
const ensurePosition = (folderName: string, position: number) => {
|
||||||
|
if (tsFolders.indexOf(folderName) > -1 && Object.keys(tsPublishModules).indexOf(folderName) === -1) {
|
||||||
|
const currentIndex = sortedTsFolders.indexOf(folderName);
|
||||||
|
if (currentIndex > -1) {
|
||||||
|
sortedTsFolders.splice(currentIndex, 1);
|
||||||
|
}
|
||||||
|
sortedTsFolders.splice(position, 0, folderName);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ensurePosition('ts_interfaces', 0);
|
||||||
|
ensurePosition('ts_shared', 1);
|
||||||
|
|
||||||
|
// Display compilation plan
|
||||||
|
const folderCount = sortedTsFolders.length;
|
||||||
|
console.log(`\n📂 TypeScript Folder Compilation Plan (${folderCount} folder${folderCount !== 1 ? 's' : ''})`);
|
||||||
|
console.log('┌' + '─'.repeat(60) + '┐');
|
||||||
|
console.log('│ 🔄 Compilation Order │');
|
||||||
|
console.log('├' + '─'.repeat(60) + '┤');
|
||||||
|
|
||||||
|
sortedTsFolders.forEach((folder, index) => {
|
||||||
|
const prefix = index === folderCount - 1 ? '└─' : '├─';
|
||||||
|
const position = `${index + 1}/${folderCount}`;
|
||||||
|
console.log(`│ ${prefix} ${position.padStart(5)} ${folder.padEnd(46)} │`);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('└' + '─'.repeat(60) + '┘\n');
|
||||||
|
|
||||||
|
// Build compilation object
|
||||||
|
const compilationCommandObject: Record<string, string> = {};
|
||||||
|
for (const tsFolder of sortedTsFolders) {
|
||||||
|
compilationCommandObject[`./${tsFolder}/**/*.ts`] = `./dist_${tsFolder}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const compiler = new TsCompiler(this.cwd, argvArg);
|
||||||
|
await compiler.compileGlob(compilationCommandObject);
|
||||||
|
|
||||||
|
const summary = (argvArg as any)?.__tsbuildFinalErrorSummary;
|
||||||
|
if (summary && summary.totalErrors > 0) {
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check command: type checks files without emitting
|
||||||
|
*/
|
||||||
|
private registerCheckCommand(): void {
|
||||||
|
this.cli.addCommand('check').subscribe(async (argvArg) => {
|
||||||
|
const patterns = argvArg._.slice(1);
|
||||||
|
|
||||||
|
// If no patterns provided, default to checking ts/**/* and then test/**/*
|
||||||
|
if (patterns.length === 0) {
|
||||||
|
await this.runDefaultTypeChecks(argvArg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const allFiles = await this.collectFilesFromPatterns(patterns);
|
||||||
|
|
||||||
|
if (allFiles.length === 0) {
|
||||||
|
console.error('\n❌ Error: No TypeScript files found to check');
|
||||||
|
console.error(' Please verify your file paths or glob patterns.\n');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n🔎 Found ${allFiles.length} TypeScript file${allFiles.length !== 1 ? 's' : ''} to check`);
|
||||||
|
|
||||||
|
const compiler = new TsCompiler(this.cwd, argvArg);
|
||||||
|
const success = await compiler.checkTypes(allFiles);
|
||||||
|
|
||||||
|
process.exit(success ? 0 : 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run default type checks for ts/ and test/ directories
|
||||||
|
*/
|
||||||
|
private async runDefaultTypeChecks(argvArg: any): Promise<void> {
|
||||||
|
console.log('\n🔬 Running default type checking sequence...\n');
|
||||||
|
|
||||||
|
// First check ts/**/* without skiplibcheck
|
||||||
|
console.log('📂 Checking ts/**/* files...');
|
||||||
|
const tsTsFiles = await FsHelpers.listFilesWithGlob(this.cwd, 'ts/**/*.ts');
|
||||||
|
|
||||||
|
if (tsTsFiles.length > 0) {
|
||||||
|
console.log(` Found ${tsTsFiles.length} TypeScript files in ts/`);
|
||||||
|
const tsAbsoluteFiles = smartpath.transform.toAbsolute(tsTsFiles, this.cwd) as string[];
|
||||||
|
|
||||||
|
const tsCompiler = new TsCompiler(this.cwd, argvArg);
|
||||||
|
const tsSuccess = await tsCompiler.checkTypes(tsAbsoluteFiles);
|
||||||
|
|
||||||
|
if (!tsSuccess) {
|
||||||
|
console.error('❌ Type checking failed for ts/**/*');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
console.log('✅ Type checking passed for ts/**/*\n');
|
||||||
|
} else {
|
||||||
|
console.log(' No TypeScript files found in ts/\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then check test/**/* with skiplibcheck
|
||||||
|
console.log('📂 Checking test/**/* files with --skiplibcheck...');
|
||||||
|
const testTsFiles = await FsHelpers.listFilesWithGlob(this.cwd, 'test/**/*.ts');
|
||||||
|
|
||||||
|
if (testTsFiles.length > 0) {
|
||||||
|
console.log(` Found ${testTsFiles.length} TypeScript files in test/`);
|
||||||
|
const testAbsoluteFiles = smartpath.transform.toAbsolute(testTsFiles, this.cwd) as string[];
|
||||||
|
|
||||||
|
const testArgvArg = { ...argvArg, skiplibcheck: true };
|
||||||
|
const testCompiler = new TsCompiler(this.cwd, testArgvArg);
|
||||||
|
const testSuccess = await testCompiler.checkTypes(testAbsoluteFiles);
|
||||||
|
|
||||||
|
if (!testSuccess) {
|
||||||
|
console.error('❌ Type checking failed for test/**/*');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
console.log('✅ Type checking passed for test/**/*\n');
|
||||||
|
} else {
|
||||||
|
console.log(' No TypeScript files found in test/\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('✅ All default type checks passed!\n');
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collect files from patterns (glob or direct paths)
|
||||||
|
*/
|
||||||
|
private async collectFilesFromPatterns(patterns: string[]): Promise<string[]> {
|
||||||
|
let allFiles: string[] = [];
|
||||||
|
|
||||||
|
for (const pattern of patterns) {
|
||||||
|
if (pattern.includes('*') || pattern.includes('{') || pattern.includes('?')) {
|
||||||
|
// Handle as glob pattern
|
||||||
|
console.log(`Processing glob pattern: ${pattern}`);
|
||||||
|
try {
|
||||||
|
const stringMatchedFiles = await FsHelpers.listFilesWithGlob(this.cwd, pattern);
|
||||||
|
|
||||||
|
if (stringMatchedFiles.length === 0) {
|
||||||
|
console.warn(`⚠️ Warning: No files matched the pattern '${pattern}'`);
|
||||||
|
} else {
|
||||||
|
console.log(`📂 Found ${stringMatchedFiles.length} files matching pattern '${pattern}'`);
|
||||||
|
const absoluteMatchedFiles = smartpath.transform.toAbsolute(stringMatchedFiles, this.cwd) as string[];
|
||||||
|
allFiles = allFiles.concat(absoluteMatchedFiles);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`❌ Error processing glob pattern '${pattern}': ${err}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Handle as direct file path
|
||||||
|
const filePath = path.isAbsolute(pattern) ? pattern : path.join(this.cwd, pattern);
|
||||||
|
const fileExists = await FsHelpers.fileExists(filePath);
|
||||||
|
if (fileExists) {
|
||||||
|
allFiles.push(filePath);
|
||||||
|
} else {
|
||||||
|
console.error(`❌ Error: File not found: ${filePath}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter to only TypeScript files
|
||||||
|
return allFiles.filter((file) => file.endsWith('.ts') || file.endsWith('.tsx'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start parsing CLI arguments
|
||||||
|
*/
|
||||||
|
public run(): void {
|
||||||
|
this.cli.startParse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the CLI
|
||||||
|
*/
|
||||||
|
export const runCli = async (): Promise<void> => {
|
||||||
|
const cli = new TsBuildCli();
|
||||||
|
cli.run();
|
||||||
|
};
|
||||||
1
ts/mod_cli/index.ts
Normal file
1
ts/mod_cli/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './classes.tsbuildcli.js';
|
||||||
544
ts/mod_compiler/classes.tscompiler.ts
Normal file
544
ts/mod_compiler/classes.tscompiler.ts
Normal file
@@ -0,0 +1,544 @@
|
|||||||
|
import type { CompilerOptions, Diagnostic, Program } from 'typescript';
|
||||||
|
import typescript from 'typescript';
|
||||||
|
import * as smartdelay from '@push.rocks/smartdelay';
|
||||||
|
import * as smartpromise from '@push.rocks/smartpromise';
|
||||||
|
import * as smartpath from '@push.rocks/smartpath';
|
||||||
|
|
||||||
|
import { TsConfig } from '../mod_config/index.js';
|
||||||
|
import { FsHelpers } from '../mod_fs/index.js';
|
||||||
|
import { performUnpack } from '../mod_unpack/index.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for error summary data
|
||||||
|
*/
|
||||||
|
export interface IErrorSummary {
|
||||||
|
errorsByFile: Record<string, Diagnostic[]>;
|
||||||
|
generalErrors: Diagnostic[];
|
||||||
|
totalErrors: number;
|
||||||
|
totalFiles: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for task information
|
||||||
|
*/
|
||||||
|
export interface ITaskInfo {
|
||||||
|
taskNumber: number;
|
||||||
|
totalTasks: number;
|
||||||
|
sourcePattern: string;
|
||||||
|
destDir: string;
|
||||||
|
fileCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for compilation result
|
||||||
|
*/
|
||||||
|
export interface ICompileResult {
|
||||||
|
emittedFiles: string[];
|
||||||
|
errorSummary: IErrorSummary;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TsCompiler handles TypeScript compilation with error tracking,
|
||||||
|
* configuration management, and output unpacking.
|
||||||
|
*/
|
||||||
|
export class TsCompiler {
|
||||||
|
private config: TsConfig;
|
||||||
|
private cwd: string;
|
||||||
|
private argvArg?: any;
|
||||||
|
|
||||||
|
constructor(cwd: string = process.cwd(), argvArg?: any) {
|
||||||
|
this.cwd = cwd;
|
||||||
|
this.config = new TsConfig(cwd);
|
||||||
|
this.argvArg = argvArg;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current working directory
|
||||||
|
*/
|
||||||
|
public getCwd(): string {
|
||||||
|
return this.cwd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the TsConfig instance
|
||||||
|
*/
|
||||||
|
public getConfig(): TsConfig {
|
||||||
|
return this.config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create compiler options by merging defaults, tsconfig.json, and custom options
|
||||||
|
*/
|
||||||
|
public createOptions(customOptions: CompilerOptions = {}): CompilerOptions {
|
||||||
|
return this.config.merge(customOptions, this.argvArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a TypeScript program from file names and options
|
||||||
|
*/
|
||||||
|
private createProgram(fileNames: string[], options: CompilerOptions): Program {
|
||||||
|
return typescript.createProgram(fileNames, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process TypeScript diagnostics and return error summary
|
||||||
|
*/
|
||||||
|
private processDiagnostics(diagnostics: readonly Diagnostic[]): IErrorSummary {
|
||||||
|
const errorsByFile: Record<string, Diagnostic[]> = {};
|
||||||
|
const generalErrors: Diagnostic[] = [];
|
||||||
|
|
||||||
|
diagnostics.forEach((diagnostic) => {
|
||||||
|
if (diagnostic.file) {
|
||||||
|
const fileName = diagnostic.file.fileName;
|
||||||
|
if (!errorsByFile[fileName]) {
|
||||||
|
errorsByFile[fileName] = [];
|
||||||
|
}
|
||||||
|
errorsByFile[fileName].push(diagnostic);
|
||||||
|
} else {
|
||||||
|
generalErrors.push(diagnostic);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
errorsByFile,
|
||||||
|
generalErrors,
|
||||||
|
totalErrors: diagnostics.length,
|
||||||
|
totalFiles: Object.keys(errorsByFile).length,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display error summary to console
|
||||||
|
*/
|
||||||
|
private displayErrorSummary(errorSummary: IErrorSummary): void {
|
||||||
|
if (errorSummary.totalErrors === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { errorsByFile, generalErrors, totalErrors, totalFiles } = errorSummary;
|
||||||
|
|
||||||
|
// Print error summary header
|
||||||
|
console.log('\n' + '='.repeat(80));
|
||||||
|
console.log(
|
||||||
|
`❌ Found ${totalErrors} error${totalErrors !== 1 ? 's' : ''} in ${totalFiles} file${totalFiles !== 1 ? 's' : ''}:`
|
||||||
|
);
|
||||||
|
console.log('='.repeat(80));
|
||||||
|
|
||||||
|
// Color codes for error formatting
|
||||||
|
const colors = {
|
||||||
|
reset: '\x1b[0m',
|
||||||
|
red: '\x1b[31m',
|
||||||
|
yellow: '\x1b[33m',
|
||||||
|
cyan: '\x1b[36m',
|
||||||
|
white: '\x1b[37m',
|
||||||
|
brightRed: '\x1b[91m',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Print file-specific errors
|
||||||
|
Object.entries(errorsByFile).forEach(([fileName, fileErrors]) => {
|
||||||
|
// Show relative path if possible for cleaner output
|
||||||
|
const displayPath = fileName.replace(process.cwd(), '').replace(/^\//, '');
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`\n${colors.cyan}File: ${displayPath} ${colors.yellow}(${fileErrors.length} error${fileErrors.length !== 1 ? 's' : ''})${colors.reset}`
|
||||||
|
);
|
||||||
|
console.log('-'.repeat(80));
|
||||||
|
|
||||||
|
fileErrors.forEach((diagnostic) => {
|
||||||
|
if (diagnostic.file && diagnostic.start !== undefined) {
|
||||||
|
const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
|
||||||
|
const message = typescript.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
|
||||||
|
const errorCode = diagnostic.code ? `TS${diagnostic.code}` : 'Error';
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`${colors.white}Line ${line + 1}, Col ${character + 1}${colors.reset}: ${colors.brightRed}${errorCode}${colors.reset} - ${message}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Try to show the code snippet if possible
|
||||||
|
try {
|
||||||
|
const lineContent = diagnostic.file.text.split('\n')[line];
|
||||||
|
if (lineContent) {
|
||||||
|
console.log(` ${lineContent.trimEnd()}`);
|
||||||
|
const indicator = ' '.repeat(character) + `${colors.red}^${colors.reset}`;
|
||||||
|
console.log(` ${indicator}`);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Failed to get source text, skip showing the code snippet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Print general errors
|
||||||
|
if (generalErrors.length > 0) {
|
||||||
|
console.log(`\n${colors.yellow}General Errors:${colors.reset}`);
|
||||||
|
console.log('-'.repeat(80));
|
||||||
|
|
||||||
|
generalErrors.forEach((diagnostic) => {
|
||||||
|
const message = typescript.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
|
||||||
|
const errorCode = diagnostic.code ? `TS${diagnostic.code}` : 'Error';
|
||||||
|
console.log(`${colors.brightRed}${errorCode}${colors.reset}: ${message}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\n' + '='.repeat(80) + '\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle skipLibCheck warning display
|
||||||
|
*/
|
||||||
|
private async handleSkipLibCheckWarning(): Promise<void> {
|
||||||
|
if (this.argvArg?.confirmskiplibcheck) {
|
||||||
|
console.log('\n⚠️ WARNING ⚠️');
|
||||||
|
console.log('You are skipping libcheck... Is that really wanted?');
|
||||||
|
console.log('Continuing in 5 seconds...\n');
|
||||||
|
await smartdelay.delayFor(5000);
|
||||||
|
} else if (!this.argvArg?.quiet && !this.argvArg?.json) {
|
||||||
|
console.log('⚠️ skipLibCheck enabled; use --confirmskiplibcheck to pause with warning.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compile files with error tracking (returns result instead of throwing)
|
||||||
|
*/
|
||||||
|
public async compileFiles(
|
||||||
|
fileNames: string[],
|
||||||
|
customOptions: CompilerOptions = {},
|
||||||
|
taskInfo?: ITaskInfo
|
||||||
|
): Promise<ICompileResult> {
|
||||||
|
const options = this.createOptions(customOptions);
|
||||||
|
|
||||||
|
if (options.skipLibCheck) {
|
||||||
|
await this.handleSkipLibCheckWarning();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enhanced logging with task info
|
||||||
|
const startTime = Date.now();
|
||||||
|
if (taskInfo) {
|
||||||
|
const { taskNumber, totalTasks, sourcePattern, fileCount } = taskInfo;
|
||||||
|
const relativeDestDir = taskInfo.destDir.replace(process.cwd(), '').replace(/^\//, '');
|
||||||
|
console.log(
|
||||||
|
`\n🔨 [${taskNumber}/${totalTasks}] Compiling ${fileCount} file${fileCount !== 1 ? 's' : ''} from ${sourcePattern}`
|
||||||
|
);
|
||||||
|
console.log(` 📁 Output: ${relativeDestDir}`);
|
||||||
|
} else {
|
||||||
|
console.log(`🔨 Compiling ${fileNames.length} files...`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const done = smartpromise.defer<ICompileResult>();
|
||||||
|
const program = this.createProgram(fileNames, options);
|
||||||
|
|
||||||
|
// Check for pre-emit diagnostics first
|
||||||
|
const preEmitDiagnostics = typescript.getPreEmitDiagnostics(program);
|
||||||
|
const preEmitErrorSummary = this.processDiagnostics(preEmitDiagnostics);
|
||||||
|
|
||||||
|
// Only continue to emit phase if no pre-emit errors
|
||||||
|
if (preEmitErrorSummary.totalErrors > 0) {
|
||||||
|
this.displayErrorSummary(preEmitErrorSummary);
|
||||||
|
console.error('\n❌ TypeScript pre-emit checks failed. Please fix the issues listed above before proceeding.');
|
||||||
|
console.error(' Type errors must be resolved before the compiler can emit output files.\n');
|
||||||
|
done.resolve({ emittedFiles: [], errorSummary: preEmitErrorSummary });
|
||||||
|
return done.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no pre-emit errors, proceed with emit
|
||||||
|
const emitResult = program.emit();
|
||||||
|
const emitErrorSummary = this.processDiagnostics(emitResult.diagnostics);
|
||||||
|
|
||||||
|
// Combine error summaries
|
||||||
|
const combinedErrorSummary: IErrorSummary = {
|
||||||
|
errorsByFile: { ...preEmitErrorSummary.errorsByFile, ...emitErrorSummary.errorsByFile },
|
||||||
|
generalErrors: [...preEmitErrorSummary.generalErrors, ...emitErrorSummary.generalErrors],
|
||||||
|
totalErrors: preEmitErrorSummary.totalErrors + emitErrorSummary.totalErrors,
|
||||||
|
totalFiles: Object.keys({ ...preEmitErrorSummary.errorsByFile, ...emitErrorSummary.errorsByFile }).length,
|
||||||
|
};
|
||||||
|
|
||||||
|
const exitCode = emitResult.emitSkipped ? 1 : 0;
|
||||||
|
if (exitCode === 0) {
|
||||||
|
const endTime = Date.now();
|
||||||
|
const duration = endTime - startTime;
|
||||||
|
|
||||||
|
if (taskInfo) {
|
||||||
|
const { taskNumber, totalTasks } = taskInfo;
|
||||||
|
console.log(`✅ [${taskNumber}/${totalTasks}] Task completed in ${duration}ms`);
|
||||||
|
} else {
|
||||||
|
console.log(`✅ TypeScript emit succeeded! (${duration}ms)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get count of emitted files by type
|
||||||
|
const jsFiles = emitResult.emittedFiles?.filter((f) => f.endsWith('.js')).length || 0;
|
||||||
|
const dtsFiles = emitResult.emittedFiles?.filter((f) => f.endsWith('.d.ts')).length || 0;
|
||||||
|
const mapFiles = emitResult.emittedFiles?.filter((f) => f.endsWith('.map')).length || 0;
|
||||||
|
|
||||||
|
if (emitResult.emittedFiles && emitResult.emittedFiles.length > 0) {
|
||||||
|
console.log(
|
||||||
|
` 📄 Generated ${emitResult.emittedFiles.length} files: ${jsFiles} .js, ${dtsFiles} .d.ts, ${mapFiles} source maps`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
done.resolve({ emittedFiles: emitResult.emittedFiles || [], errorSummary: combinedErrorSummary });
|
||||||
|
} else {
|
||||||
|
this.displayErrorSummary(combinedErrorSummary);
|
||||||
|
console.error('\n❌ TypeScript emit failed. Please investigate the errors listed above!');
|
||||||
|
console.error(' No output files have been generated.\n');
|
||||||
|
done.resolve({ emittedFiles: [], errorSummary: combinedErrorSummary });
|
||||||
|
}
|
||||||
|
|
||||||
|
return done.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compile files (throws on error)
|
||||||
|
*/
|
||||||
|
public async compileFilesOrThrow(fileNames: string[], customOptions: CompilerOptions = {}): Promise<string[]> {
|
||||||
|
const result = await this.compileFiles(fileNames, customOptions);
|
||||||
|
if (result.errorSummary.totalErrors > 0) {
|
||||||
|
throw new Error('TypeScript compilation failed.');
|
||||||
|
}
|
||||||
|
return result.emittedFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compile glob patterns with automatic unpacking
|
||||||
|
*/
|
||||||
|
public async compileGlob(
|
||||||
|
globPatterns: Record<string, string>,
|
||||||
|
customOptions: CompilerOptions = {}
|
||||||
|
): Promise<ICompileResult> {
|
||||||
|
const emittedFiles: string[] = [];
|
||||||
|
const errorSummaries: IErrorSummary[] = [];
|
||||||
|
|
||||||
|
const totalTasks = Object.keys(globPatterns).length;
|
||||||
|
let currentTask = 0;
|
||||||
|
|
||||||
|
const isQuiet = this.argvArg?.quiet === true;
|
||||||
|
const isJson = this.argvArg?.json === true;
|
||||||
|
|
||||||
|
if (!isQuiet && !isJson) {
|
||||||
|
console.log(`\n👷 TypeScript Compilation Tasks (${totalTasks} task${totalTasks !== 1 ? 's' : ''}):`);
|
||||||
|
Object.entries(globPatterns).forEach(([source, dest]) => {
|
||||||
|
console.log(` 📂 ${source} → ${dest}`);
|
||||||
|
});
|
||||||
|
console.log('');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const pattern of Object.keys(globPatterns)) {
|
||||||
|
const destPath = globPatterns[pattern];
|
||||||
|
if (!pattern || !destPath) continue;
|
||||||
|
|
||||||
|
// Get files matching the glob pattern
|
||||||
|
const files = await FsHelpers.listFilesWithGlob(this.cwd, pattern);
|
||||||
|
|
||||||
|
// Transform to absolute paths
|
||||||
|
const absoluteFiles = smartpath.transform.toAbsolute(files, this.cwd) as string[];
|
||||||
|
|
||||||
|
// Get destination directory as absolute path
|
||||||
|
const destDir = smartpath.transform.toAbsolute(destPath, this.cwd) as string;
|
||||||
|
|
||||||
|
// Clear the destination directory before compilation if it exists
|
||||||
|
if (await FsHelpers.directoryExists(destDir)) {
|
||||||
|
if (!isQuiet && !isJson) {
|
||||||
|
console.log(`🧹 Clearing output directory: ${destPath}`);
|
||||||
|
}
|
||||||
|
await FsHelpers.removeDirectory(destDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update compiler options with the output directory
|
||||||
|
const options: CompilerOptions = {
|
||||||
|
...customOptions,
|
||||||
|
outDir: destDir,
|
||||||
|
};
|
||||||
|
|
||||||
|
currentTask++;
|
||||||
|
const taskInfo: ITaskInfo = {
|
||||||
|
taskNumber: currentTask,
|
||||||
|
totalTasks,
|
||||||
|
sourcePattern: pattern,
|
||||||
|
destDir: destPath,
|
||||||
|
fileCount: absoluteFiles.length,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await this.compileFiles(absoluteFiles, options, taskInfo);
|
||||||
|
emittedFiles.push(...result.emittedFiles);
|
||||||
|
errorSummaries.push(result.errorSummary);
|
||||||
|
|
||||||
|
// Perform unpack if compilation succeeded
|
||||||
|
if (result.errorSummary.totalErrors === 0) {
|
||||||
|
await performUnpack(pattern, destDir, this.cwd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge all error summaries
|
||||||
|
const finalErrorSummary = this.mergeErrorSummaries(errorSummaries);
|
||||||
|
|
||||||
|
// Output summary based on mode
|
||||||
|
if (isJson) {
|
||||||
|
const result = {
|
||||||
|
success: finalErrorSummary.totalErrors === 0,
|
||||||
|
totals: {
|
||||||
|
errors: finalErrorSummary.totalErrors,
|
||||||
|
filesWithErrors: finalErrorSummary.totalFiles,
|
||||||
|
tasks: totalTasks,
|
||||||
|
},
|
||||||
|
errorsByFile: Object.fromEntries(
|
||||||
|
Object.entries(finalErrorSummary.errorsByFile).map(([file, diags]) => [
|
||||||
|
file,
|
||||||
|
diags.map((d) => ({
|
||||||
|
code: d.code,
|
||||||
|
message: typescript.flattenDiagnosticMessageText(d.messageText as any, '\n'),
|
||||||
|
})),
|
||||||
|
])
|
||||||
|
),
|
||||||
|
};
|
||||||
|
console.log(JSON.stringify(result));
|
||||||
|
} else if (!isQuiet) {
|
||||||
|
this.displayFinalSummary(finalErrorSummary);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach summary to argvArg for CLI exit behavior
|
||||||
|
if (this.argvArg && typeof this.argvArg === 'object') {
|
||||||
|
(this.argvArg as any).__tsbuildFinalErrorSummary = finalErrorSummary;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
emittedFiles,
|
||||||
|
errorSummary: finalErrorSummary,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if files can be emitted without actually emitting
|
||||||
|
*/
|
||||||
|
public async checkEmit(fileNames: string[], customOptions: CompilerOptions = {}): Promise<boolean> {
|
||||||
|
const options = { ...this.createOptions(customOptions), noEmit: true };
|
||||||
|
const fileCount = fileNames.length;
|
||||||
|
|
||||||
|
console.log(`\n🔍 Checking if ${fileCount} file${fileCount !== 1 ? 's' : ''} can be emitted...`);
|
||||||
|
|
||||||
|
const program = this.createProgram(fileNames, options);
|
||||||
|
|
||||||
|
const preEmitDiagnostics = typescript.getPreEmitDiagnostics(program);
|
||||||
|
const preEmitErrorSummary = this.processDiagnostics(preEmitDiagnostics);
|
||||||
|
|
||||||
|
const emitResult = program.emit(undefined, undefined, undefined, true);
|
||||||
|
const emitErrorSummary = this.processDiagnostics(emitResult.diagnostics);
|
||||||
|
|
||||||
|
const combinedErrorSummary: IErrorSummary = {
|
||||||
|
errorsByFile: { ...preEmitErrorSummary.errorsByFile, ...emitErrorSummary.errorsByFile },
|
||||||
|
generalErrors: [...preEmitErrorSummary.generalErrors, ...emitErrorSummary.generalErrors],
|
||||||
|
totalErrors: preEmitErrorSummary.totalErrors + emitErrorSummary.totalErrors,
|
||||||
|
totalFiles: Object.keys({ ...preEmitErrorSummary.errorsByFile, ...emitErrorSummary.errorsByFile }).length,
|
||||||
|
};
|
||||||
|
|
||||||
|
const success = combinedErrorSummary.totalErrors === 0 && !emitResult.emitSkipped;
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
console.log('\n✅ TypeScript emit check passed! All files can be emitted successfully.');
|
||||||
|
console.log(` ${fileCount} file${fileCount !== 1 ? 's' : ''} ${fileCount !== 1 ? 'are' : 'is'} ready to be compiled.\n`);
|
||||||
|
} else {
|
||||||
|
this.displayErrorSummary(combinedErrorSummary);
|
||||||
|
console.error('\n❌ TypeScript emit check failed. Please fix the issues listed above.');
|
||||||
|
console.error(' The compilation cannot proceed until these errors are resolved.\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check TypeScript files for type errors without emission
|
||||||
|
*/
|
||||||
|
public async checkTypes(fileNames: string[], customOptions: CompilerOptions = {}): Promise<boolean> {
|
||||||
|
const options = { ...this.createOptions(customOptions), noEmit: true };
|
||||||
|
const fileCount = fileNames.length;
|
||||||
|
|
||||||
|
console.log(`\n🔍 Type checking ${fileCount} TypeScript file${fileCount !== 1 ? 's' : ''}...`);
|
||||||
|
|
||||||
|
const program = this.createProgram(fileNames, options);
|
||||||
|
const diagnostics = typescript.getPreEmitDiagnostics(program);
|
||||||
|
const errorSummary = this.processDiagnostics(diagnostics);
|
||||||
|
|
||||||
|
const success = errorSummary.totalErrors === 0;
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
console.log('\n✅ TypeScript type check passed! No type errors found.');
|
||||||
|
console.log(` All ${fileCount} file${fileCount !== 1 ? 's' : ''} passed type checking successfully.\n`);
|
||||||
|
} else {
|
||||||
|
this.displayErrorSummary(errorSummary);
|
||||||
|
console.error('\n❌ TypeScript type check failed. Please fix the type errors listed above.');
|
||||||
|
console.error(' The type checker found issues that need to be resolved.\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge multiple error summaries into one
|
||||||
|
*/
|
||||||
|
private mergeErrorSummaries(summaries: IErrorSummary[]): IErrorSummary {
|
||||||
|
const mergedErrorsByFile: Record<string, Diagnostic[]> = {};
|
||||||
|
const mergedGeneralErrors: Diagnostic[] = [];
|
||||||
|
let totalErrors = 0;
|
||||||
|
|
||||||
|
summaries.forEach((summary) => {
|
||||||
|
Object.entries(summary.errorsByFile).forEach(([fileName, errors]) => {
|
||||||
|
if (!mergedErrorsByFile[fileName]) {
|
||||||
|
mergedErrorsByFile[fileName] = [];
|
||||||
|
}
|
||||||
|
mergedErrorsByFile[fileName] = mergedErrorsByFile[fileName].concat(errors);
|
||||||
|
});
|
||||||
|
|
||||||
|
mergedGeneralErrors.push(...summary.generalErrors);
|
||||||
|
totalErrors += summary.totalErrors;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
errorsByFile: mergedErrorsByFile,
|
||||||
|
generalErrors: mergedGeneralErrors,
|
||||||
|
totalErrors,
|
||||||
|
totalFiles: Object.keys(mergedErrorsByFile).length,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display final compilation summary
|
||||||
|
*/
|
||||||
|
private displayFinalSummary(errorSummary: IErrorSummary): void {
|
||||||
|
if (errorSummary.totalErrors === 0) {
|
||||||
|
console.log('\n📊 \x1b[32mCompilation Summary: All tasks completed successfully! ✅\x1b[0m\n');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const colors = {
|
||||||
|
reset: '\x1b[0m',
|
||||||
|
red: '\x1b[31m',
|
||||||
|
yellow: '\x1b[33m',
|
||||||
|
cyan: '\x1b[36m',
|
||||||
|
brightRed: '\x1b[91m',
|
||||||
|
brightYellow: '\x1b[93m',
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('\n' + '='.repeat(80));
|
||||||
|
console.log(`📊 ${colors.brightYellow}Final Compilation Summary${colors.reset}`);
|
||||||
|
console.log('='.repeat(80));
|
||||||
|
|
||||||
|
if (errorSummary.totalFiles > 0) {
|
||||||
|
console.log(`${colors.brightRed}❌ Files with errors (${errorSummary.totalFiles}):${colors.reset}`);
|
||||||
|
|
||||||
|
Object.entries(errorSummary.errorsByFile).forEach(([fileName, errors]) => {
|
||||||
|
const displayPath = fileName.replace(process.cwd(), '').replace(/^\//, '');
|
||||||
|
console.log(
|
||||||
|
` ${colors.red}•${colors.reset} ${colors.cyan}${displayPath}${colors.reset} ${colors.yellow}(${errors.length} error${errors.length !== 1 ? 's' : ''})${colors.reset}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorSummary.generalErrors.length > 0) {
|
||||||
|
console.log(`${colors.brightRed}❌ General errors: ${errorSummary.generalErrors.length}${colors.reset}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`\n${colors.brightRed}Total: ${errorSummary.totalErrors} error${errorSummary.totalErrors !== 1 ? 's' : ''} across ${errorSummary.totalFiles} file${errorSummary.totalFiles !== 1 ? 's' : ''}${colors.reset}`
|
||||||
|
);
|
||||||
|
console.log('='.repeat(80) + '\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
1
ts/mod_compiler/index.ts
Normal file
1
ts/mod_compiler/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './classes.tscompiler.js';
|
||||||
180
ts/mod_config/classes.tsconfig.ts
Normal file
180
ts/mod_config/classes.tsconfig.ts
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
import type { CompilerOptions } from 'typescript';
|
||||||
|
import typescript from 'typescript';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default compiler options for TypeScript compilation
|
||||||
|
*/
|
||||||
|
export const compilerOptionsDefault: CompilerOptions = {
|
||||||
|
declaration: true,
|
||||||
|
inlineSourceMap: true,
|
||||||
|
noEmitOnError: true,
|
||||||
|
outDir: 'dist_ts/',
|
||||||
|
module: typescript.ModuleKind.NodeNext,
|
||||||
|
target: typescript.ScriptTarget.ESNext,
|
||||||
|
moduleResolution: typescript.ModuleResolutionKind.NodeNext,
|
||||||
|
lib: ['lib.dom.d.ts', 'lib.esnext.d.ts'],
|
||||||
|
noImplicitAny: false,
|
||||||
|
esModuleInterop: true,
|
||||||
|
verbatimModuleSyntax: true,
|
||||||
|
baseUrl: './',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TsConfig handles loading and merging TypeScript compiler configurations.
|
||||||
|
* It supports reading tsconfig.json and merging with defaults and custom options.
|
||||||
|
*/
|
||||||
|
export class TsConfig {
|
||||||
|
private cwd: string;
|
||||||
|
private cachedTsConfig: CompilerOptions | null = null;
|
||||||
|
|
||||||
|
constructor(cwd: string = process.cwd()) {
|
||||||
|
this.cwd = cwd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current working directory
|
||||||
|
*/
|
||||||
|
public getCwd(): string {
|
||||||
|
return this.cwd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load and parse tsconfig.json from the current directory
|
||||||
|
*/
|
||||||
|
public load(): CompilerOptions {
|
||||||
|
if (this.cachedTsConfig !== null) {
|
||||||
|
return this.cachedTsConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
let tsconfig: any;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const tsconfigPath = path.join(this.cwd, 'tsconfig.json');
|
||||||
|
const tsconfigContent = fs.readFileSync(tsconfigPath, 'utf8');
|
||||||
|
tsconfig = JSON.parse(tsconfigContent);
|
||||||
|
} catch {
|
||||||
|
this.cachedTsConfig = {};
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tsconfig || !tsconfig.compilerOptions) {
|
||||||
|
this.cachedTsConfig = {};
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const returnObject: CompilerOptions = { ...tsconfig.compilerOptions };
|
||||||
|
|
||||||
|
// Convert target string to enum
|
||||||
|
if (tsconfig.compilerOptions.target && typeof tsconfig.compilerOptions.target === 'string') {
|
||||||
|
const targetKey = tsconfig.compilerOptions.target.toUpperCase();
|
||||||
|
if (targetKey in typescript.ScriptTarget) {
|
||||||
|
returnObject.target = typescript.ScriptTarget[targetKey as keyof typeof typescript.ScriptTarget];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert module string to enum
|
||||||
|
if (tsconfig.compilerOptions.module && typeof tsconfig.compilerOptions.module === 'string') {
|
||||||
|
const moduleKey = tsconfig.compilerOptions.module.toUpperCase();
|
||||||
|
if (moduleKey in typescript.ModuleKind) {
|
||||||
|
returnObject.module = typescript.ModuleKind[moduleKey as keyof typeof typescript.ModuleKind];
|
||||||
|
} else if (moduleKey === 'NODENEXT') {
|
||||||
|
returnObject.module = typescript.ModuleKind.NodeNext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert moduleResolution string to enum
|
||||||
|
if (tsconfig.compilerOptions.moduleResolution && typeof tsconfig.compilerOptions.moduleResolution === 'string') {
|
||||||
|
const moduleResolutionKey = tsconfig.compilerOptions.moduleResolution.toUpperCase();
|
||||||
|
if (moduleResolutionKey in typescript.ModuleResolutionKind) {
|
||||||
|
returnObject.moduleResolution = typescript.ModuleResolutionKind[
|
||||||
|
moduleResolutionKey as keyof typeof typescript.ModuleResolutionKind
|
||||||
|
];
|
||||||
|
} else if (moduleResolutionKey === 'NODENEXT') {
|
||||||
|
returnObject.moduleResolution = typescript.ModuleResolutionKind.NodeNext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply path transformations (ts_ → dist_ts_)
|
||||||
|
if (tsconfig.compilerOptions.paths) {
|
||||||
|
returnObject.paths = { ...tsconfig.compilerOptions.paths };
|
||||||
|
for (const pathKey of Object.keys(returnObject.paths)) {
|
||||||
|
if (Array.isArray(returnObject.paths[pathKey]) && returnObject.paths[pathKey].length > 0) {
|
||||||
|
returnObject.paths[pathKey][0] = returnObject.paths[pathKey][0].replace('./ts_', './dist_ts_');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.cachedTsConfig = returnObject;
|
||||||
|
return returnObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get default compiler options
|
||||||
|
*/
|
||||||
|
public getDefaultOptions(): CompilerOptions {
|
||||||
|
return { ...compilerOptionsDefault };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get critical/protected default options that shouldn't be overridden by tsconfig.json
|
||||||
|
*/
|
||||||
|
public getProtectedDefaults(): CompilerOptions {
|
||||||
|
return {
|
||||||
|
outDir: 'dist_ts/',
|
||||||
|
noEmitOnError: true,
|
||||||
|
declaration: true,
|
||||||
|
inlineSourceMap: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process command line arguments and return applicable compiler options
|
||||||
|
*/
|
||||||
|
public getCommandLineOptions(argvArg?: any): CompilerOptions {
|
||||||
|
if (!argvArg) return {};
|
||||||
|
|
||||||
|
const options: CompilerOptions = {};
|
||||||
|
|
||||||
|
if (argvArg.skiplibcheck) {
|
||||||
|
options.skipLibCheck = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argvArg.disallowimplicitany) {
|
||||||
|
options.noImplicitAny = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argvArg.commonjs) {
|
||||||
|
options.module = typescript.ModuleKind.CommonJS;
|
||||||
|
options.moduleResolution = typescript.ModuleResolutionKind.NodeJs;
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge compiler options with proper priority order:
|
||||||
|
* 1. Default options
|
||||||
|
* 2. tsconfig.json options
|
||||||
|
* 3. Protected defaults (cannot be overridden by tsconfig)
|
||||||
|
* 4. Custom options (programmatic)
|
||||||
|
* 5. CLI options (highest priority)
|
||||||
|
*/
|
||||||
|
public merge(customOptions: CompilerOptions = {}, argvArg?: any): CompilerOptions {
|
||||||
|
return {
|
||||||
|
...this.getDefaultOptions(),
|
||||||
|
...this.load(),
|
||||||
|
...this.getProtectedDefaults(),
|
||||||
|
...customOptions,
|
||||||
|
...this.getCommandLineOptions(argvArg),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the cached tsconfig (useful for reloading)
|
||||||
|
*/
|
||||||
|
public clearCache(): void {
|
||||||
|
this.cachedTsConfig = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
116
ts/mod_config/classes.tspublishconfig.ts
Normal file
116
ts/mod_config/classes.tspublishconfig.ts
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for tspublish.json configuration
|
||||||
|
*/
|
||||||
|
export interface ITsPublishJson {
|
||||||
|
order?: number;
|
||||||
|
unpack?: boolean;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TsPublishConfig handles loading and parsing tspublish.json files.
|
||||||
|
* These configuration files control module-specific settings like
|
||||||
|
* compilation order and output unpacking behavior.
|
||||||
|
*/
|
||||||
|
export class TsPublishConfig {
|
||||||
|
private folderPath: string;
|
||||||
|
private cachedConfig: ITsPublishJson | null | undefined = undefined;
|
||||||
|
|
||||||
|
constructor(folderPath: string) {
|
||||||
|
this.folderPath = folderPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the folder path this config is for
|
||||||
|
*/
|
||||||
|
public getFolderPath(): string {
|
||||||
|
return this.folderPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load and parse tspublish.json from the folder
|
||||||
|
* Returns null if file doesn't exist or is invalid
|
||||||
|
*/
|
||||||
|
public async load(): Promise<ITsPublishJson | null> {
|
||||||
|
if (this.cachedConfig !== undefined) {
|
||||||
|
return this.cachedConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const configPath = path.join(this.folderPath, 'tspublish.json');
|
||||||
|
const content = await fs.promises.readFile(configPath, 'utf8');
|
||||||
|
this.cachedConfig = JSON.parse(content);
|
||||||
|
return this.cachedConfig;
|
||||||
|
} catch {
|
||||||
|
this.cachedConfig = null;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronously load and parse tspublish.json from the folder
|
||||||
|
* Returns null if file doesn't exist or is invalid
|
||||||
|
*/
|
||||||
|
public loadSync(): ITsPublishJson | null {
|
||||||
|
if (this.cachedConfig !== undefined) {
|
||||||
|
return this.cachedConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const configPath = path.join(this.folderPath, 'tspublish.json');
|
||||||
|
const content = fs.readFileSync(configPath, 'utf8');
|
||||||
|
this.cachedConfig = JSON.parse(content);
|
||||||
|
return this.cachedConfig;
|
||||||
|
} catch {
|
||||||
|
this.cachedConfig = null;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if output should be unpacked (flattened)
|
||||||
|
* Default is true if not specified
|
||||||
|
*/
|
||||||
|
public get shouldUnpack(): boolean {
|
||||||
|
const config = this.loadSync();
|
||||||
|
if (!config || config.unpack === undefined) {
|
||||||
|
return true; // Default to true
|
||||||
|
}
|
||||||
|
return config.unpack === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the compilation order for tsfolders command
|
||||||
|
* Returns Infinity if not specified (sorted last)
|
||||||
|
*/
|
||||||
|
public get order(): number {
|
||||||
|
const config = this.loadSync();
|
||||||
|
if (!config || config.order === undefined) {
|
||||||
|
return Infinity;
|
||||||
|
}
|
||||||
|
return config.order;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if tspublish.json exists in the folder
|
||||||
|
*/
|
||||||
|
public async exists(): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const configPath = path.join(this.folderPath, 'tspublish.json');
|
||||||
|
await fs.promises.access(configPath, fs.constants.F_OK);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the cached config (useful for reloading)
|
||||||
|
*/
|
||||||
|
public clearCache(): void {
|
||||||
|
this.cachedConfig = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
2
ts/mod_config/index.ts
Normal file
2
ts/mod_config/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './classes.tsconfig.js';
|
||||||
|
export * from './classes.tspublishconfig.js';
|
||||||
144
ts/mod_fs/classes.fshelpers.ts
Normal file
144
ts/mod_fs/classes.fshelpers.ts
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as smartfsModule from '@push.rocks/smartfs';
|
||||||
|
|
||||||
|
// Create a smartfs instance with Node.js provider
|
||||||
|
const smartfs = new smartfsModule.SmartFs(new smartfsModule.SmartFsProviderNode());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FsHelpers provides filesystem utility methods for tsbuild.
|
||||||
|
* All methods are static for convenience.
|
||||||
|
*/
|
||||||
|
export class FsHelpers {
|
||||||
|
/**
|
||||||
|
* The smartfs instance for filesystem operations
|
||||||
|
*/
|
||||||
|
public static readonly smartfs = smartfs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List files matching a glob pattern like './ts/**\/*.ts'
|
||||||
|
* Parses the pattern to extract base directory and filter pattern
|
||||||
|
*/
|
||||||
|
public static async listFilesWithGlob(basePath: string, globPattern: string): Promise<string[]> {
|
||||||
|
// Remove leading ./ if present
|
||||||
|
const pattern = globPattern.replace(/^\.\//, '');
|
||||||
|
|
||||||
|
// Find the first directory part before any glob characters
|
||||||
|
const globChars = ['*', '?', '{', '['];
|
||||||
|
let baseDir = basePath;
|
||||||
|
|
||||||
|
// Find where the glob pattern starts
|
||||||
|
const parts = pattern.split('/');
|
||||||
|
const staticParts: string[] = [];
|
||||||
|
const filterParts: string[] = [];
|
||||||
|
let foundGlob = false;
|
||||||
|
|
||||||
|
for (const part of parts) {
|
||||||
|
if (!foundGlob && !globChars.some(c => part.includes(c))) {
|
||||||
|
staticParts.push(part);
|
||||||
|
} else {
|
||||||
|
foundGlob = true;
|
||||||
|
filterParts.push(part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the base directory
|
||||||
|
if (staticParts.length > 0) {
|
||||||
|
baseDir = path.join(basePath, ...staticParts);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the filter pattern (just the filename part, ignoring ** for directories)
|
||||||
|
// The recursive() handles the ** part
|
||||||
|
const fileFilter = filterParts[filterParts.length - 1] || '*';
|
||||||
|
|
||||||
|
// Check if we need recursive search
|
||||||
|
const needsRecursive = filterParts.some(p => p === '**' || p.includes('**'));
|
||||||
|
|
||||||
|
let dirBuilder = smartfs.directory(baseDir);
|
||||||
|
if (needsRecursive) {
|
||||||
|
dirBuilder = dirBuilder.recursive();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const entries = await dirBuilder.filter(fileFilter).list();
|
||||||
|
return entries.filter(e => e.isFile).map(e => e.path);
|
||||||
|
} catch {
|
||||||
|
// Directory doesn't exist or other error
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract source folder name from a glob pattern
|
||||||
|
* './ts_core/**\/*.ts' → 'ts_core'
|
||||||
|
* 'ts_foo/**\/*.ts' → 'ts_foo'
|
||||||
|
*/
|
||||||
|
public static extractSourceFolder(pattern: string): string | null {
|
||||||
|
const match = pattern.match(/^\.?\/?([^\/\*]+)/);
|
||||||
|
return match ? match[1] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current working directory
|
||||||
|
*/
|
||||||
|
public static getCwd(): string {
|
||||||
|
return process.cwd();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the package directory (where package.json is located)
|
||||||
|
*/
|
||||||
|
public static getPackageDir(): string {
|
||||||
|
return path.resolve(__dirname, '../../');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a file exists
|
||||||
|
*/
|
||||||
|
public static async fileExists(filePath: string): Promise<boolean> {
|
||||||
|
return smartfs.file(filePath).exists();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a directory exists
|
||||||
|
*/
|
||||||
|
public static async directoryExists(dirPath: string): Promise<boolean> {
|
||||||
|
return smartfs.directory(dirPath).exists();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a JSON file and parse it
|
||||||
|
*/
|
||||||
|
public static readJsonSync<T = any>(filePath: string): T {
|
||||||
|
const content = fs.readFileSync(filePath, 'utf8');
|
||||||
|
return JSON.parse(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List directory contents
|
||||||
|
*/
|
||||||
|
public static async listDirectory(dirPath: string) {
|
||||||
|
return smartfs.directory(dirPath).list();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a directory recursively
|
||||||
|
*/
|
||||||
|
public static async removeDirectory(dirPath: string): Promise<void> {
|
||||||
|
await fs.promises.rm(dirPath, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move/rename a file or directory
|
||||||
|
*/
|
||||||
|
public static async move(src: string, dest: string): Promise<void> {
|
||||||
|
await fs.promises.rename(src, dest);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove an empty directory
|
||||||
|
*/
|
||||||
|
public static async removeEmptyDirectory(dirPath: string): Promise<void> {
|
||||||
|
await fs.promises.rmdir(dirPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
1
ts/mod_fs/index.ts
Normal file
1
ts/mod_fs/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './classes.fshelpers.js';
|
||||||
153
ts/mod_unpack/classes.tsunpacker.ts
Normal file
153
ts/mod_unpack/classes.tsunpacker.ts
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
import { TsPublishConfig } from '../mod_config/index.js';
|
||||||
|
import { FsHelpers } from '../mod_fs/index.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TsUnpacker handles flattening of nested TypeScript output directories.
|
||||||
|
*
|
||||||
|
* When TypeScript compiles files that import from sibling directories,
|
||||||
|
* it creates a nested structure like:
|
||||||
|
* dist_ts_core/ts_core/index.js
|
||||||
|
* dist_ts_core/ts_shared/helper.js
|
||||||
|
*
|
||||||
|
* This class flattens it to:
|
||||||
|
* dist_ts_core/index.js
|
||||||
|
*/
|
||||||
|
export class TsUnpacker {
|
||||||
|
private sourceFolderName: string;
|
||||||
|
private destDir: string;
|
||||||
|
private cwd: string;
|
||||||
|
private config: TsPublishConfig;
|
||||||
|
|
||||||
|
constructor(sourceFolderName: string, destDir: string, cwd: string = process.cwd()) {
|
||||||
|
this.sourceFolderName = sourceFolderName;
|
||||||
|
this.destDir = destDir;
|
||||||
|
this.cwd = cwd;
|
||||||
|
this.config = new TsPublishConfig(path.join(cwd, sourceFolderName));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an unpacker from a glob pattern
|
||||||
|
* './ts_core/**\/*.ts' → sourceFolderName = 'ts_core'
|
||||||
|
*/
|
||||||
|
public static fromGlobPattern(
|
||||||
|
sourcePattern: string,
|
||||||
|
destDir: string,
|
||||||
|
cwd: string = process.cwd()
|
||||||
|
): TsUnpacker | null {
|
||||||
|
const sourceFolderName = FsHelpers.extractSourceFolder(sourcePattern);
|
||||||
|
if (!sourceFolderName) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new TsUnpacker(sourceFolderName, destDir, cwd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the source folder name
|
||||||
|
*/
|
||||||
|
public getSourceFolderName(): string {
|
||||||
|
return this.sourceFolderName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the destination directory
|
||||||
|
*/
|
||||||
|
public getDestDir(): string {
|
||||||
|
return this.destDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if unpacking should be performed based on tspublish.json config
|
||||||
|
* Default is true if not specified
|
||||||
|
*/
|
||||||
|
public async shouldUnpack(): Promise<boolean> {
|
||||||
|
return this.config.shouldUnpack;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if nested structure exists in the destination directory
|
||||||
|
*/
|
||||||
|
public async detectNesting(): Promise<boolean> {
|
||||||
|
const nestedPath = path.join(this.destDir, this.sourceFolderName);
|
||||||
|
return FsHelpers.directoryExists(nestedPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the path to the nested directory
|
||||||
|
*/
|
||||||
|
public getNestedPath(): string {
|
||||||
|
return path.join(this.destDir, this.sourceFolderName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform the unpack operation - flatten nested output directories
|
||||||
|
* Returns true if unpacking was performed, false if skipped
|
||||||
|
*/
|
||||||
|
public async unpack(): Promise<boolean> {
|
||||||
|
// Check if we should unpack based on config
|
||||||
|
if (!(await this.shouldUnpack())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if nested structure exists
|
||||||
|
if (!(await this.detectNesting())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nestedPath = this.getNestedPath();
|
||||||
|
|
||||||
|
// Delete sibling folders (not the source folder)
|
||||||
|
await this.removeSiblingDirectories();
|
||||||
|
|
||||||
|
// Move contents from nested folder up
|
||||||
|
await this.moveNestedContentsUp();
|
||||||
|
|
||||||
|
// Remove empty nested folder
|
||||||
|
await FsHelpers.removeEmptyDirectory(nestedPath);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove sibling directories in the destination folder
|
||||||
|
* (directories other than the source folder being unpacked)
|
||||||
|
*/
|
||||||
|
private async removeSiblingDirectories(): Promise<void> {
|
||||||
|
const entries = await FsHelpers.listDirectory(this.destDir);
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (entry.isDirectory && entry.name !== this.sourceFolderName) {
|
||||||
|
await FsHelpers.removeDirectory(path.join(this.destDir, entry.name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move contents from the nested folder up to the destination directory
|
||||||
|
*/
|
||||||
|
private async moveNestedContentsUp(): Promise<void> {
|
||||||
|
const nestedPath = this.getNestedPath();
|
||||||
|
const entries = await FsHelpers.listDirectory(nestedPath);
|
||||||
|
for (const entry of entries) {
|
||||||
|
const src = path.join(nestedPath, entry.name);
|
||||||
|
const dest = path.join(this.destDir, entry.name);
|
||||||
|
await FsHelpers.move(src, dest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience function to perform unpack operation
|
||||||
|
* Can be used directly without instantiating the class
|
||||||
|
*/
|
||||||
|
export async function performUnpack(
|
||||||
|
sourcePattern: string,
|
||||||
|
destDir: string,
|
||||||
|
cwd: string = process.cwd()
|
||||||
|
): Promise<boolean> {
|
||||||
|
const unpacker = TsUnpacker.fromGlobPattern(sourcePattern, destDir, cwd);
|
||||||
|
if (!unpacker) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return unpacker.unpack();
|
||||||
|
}
|
||||||
1
ts/mod_unpack/index.ts
Normal file
1
ts/mod_unpack/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './classes.tsunpacker.js';
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
import * as plugins from './plugins.js';
|
|
||||||
|
|
||||||
export const cwd = process.cwd();
|
|
||||||
export const packageDir = plugins.path.join(plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url), '../');
|
|
||||||
@@ -1,16 +1,13 @@
|
|||||||
// node native
|
// node native
|
||||||
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
export {
|
export { fs, path };
|
||||||
path
|
|
||||||
}
|
|
||||||
|
|
||||||
// @git.zone scope
|
// @git.zone scope
|
||||||
import * as tspublish from '@git.zone/tspublish';
|
import * as tspublish from '@git.zone/tspublish';
|
||||||
|
|
||||||
export {
|
export { tspublish };
|
||||||
tspublish
|
|
||||||
}
|
|
||||||
|
|
||||||
// @push.rocks scope
|
// @push.rocks scope
|
||||||
import * as smartcli from '@push.rocks/smartcli';
|
import * as smartcli from '@push.rocks/smartcli';
|
||||||
@@ -24,6 +21,4 @@ export { smartcli, smartdelay, smartfile, smartpath, smartpromise };
|
|||||||
// third party scope
|
// third party scope
|
||||||
import typescript from 'typescript';
|
import typescript from 'typescript';
|
||||||
|
|
||||||
export {
|
export { typescript };
|
||||||
typescript
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,450 +0,0 @@
|
|||||||
// import all the stuff we need
|
|
||||||
import * as plugins from './plugins.js';
|
|
||||||
import * as paths from './paths.js';
|
|
||||||
import type { CompilerOptions, ScriptTarget, ModuleKind } from './tsbuild.exports.js';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default compiler options for TypeScript compilation
|
|
||||||
*/
|
|
||||||
export const compilerOptionsDefault: CompilerOptions = {
|
|
||||||
declaration: true,
|
|
||||||
emitDecoratorMetadata: true,
|
|
||||||
experimentalDecorators: true,
|
|
||||||
inlineSourceMap: true,
|
|
||||||
noEmitOnError: true,
|
|
||||||
outDir: 'dist_ts/',
|
|
||||||
module: plugins.typescript.ModuleKind.NodeNext,
|
|
||||||
target: plugins.typescript.ScriptTarget.ESNext,
|
|
||||||
moduleResolution: plugins.typescript.ModuleResolutionKind.NodeNext,
|
|
||||||
lib: ['lib.dom.d.ts', 'lib.es2022.d.ts'],
|
|
||||||
noImplicitAny: false, // Allow implicit any by default
|
|
||||||
esModuleInterop: true,
|
|
||||||
useDefineForClassFields: false,
|
|
||||||
verbatimModuleSyntax: true,
|
|
||||||
baseUrl: './',
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TsBuild class for handling TypeScript compilation
|
|
||||||
*/
|
|
||||||
export class TsBuild {
|
|
||||||
private fileNames: string[] = [];
|
|
||||||
private options: plugins.typescript.CompilerOptions;
|
|
||||||
private argvArg?: any;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new TsBuild instance
|
|
||||||
*/
|
|
||||||
constructor(
|
|
||||||
fileNames: string[] = [],
|
|
||||||
customOptions: CompilerOptions = {},
|
|
||||||
argvArg?: any
|
|
||||||
) {
|
|
||||||
this.fileNames = fileNames;
|
|
||||||
this.argvArg = argvArg;
|
|
||||||
this.options = this.mergeCompilerOptions(customOptions, argvArg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper function to read and process tsconfig.json
|
|
||||||
*/
|
|
||||||
private getTsConfigOptions(): CompilerOptions {
|
|
||||||
const tsconfig = plugins.smartfile.fs.toObjectSync(plugins.path.join(paths.cwd, 'tsconfig.json'));
|
|
||||||
const returnObject: CompilerOptions = {};
|
|
||||||
|
|
||||||
if (!tsconfig || !tsconfig.compilerOptions) {
|
|
||||||
return returnObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process baseUrl
|
|
||||||
if (tsconfig.compilerOptions.baseUrl) {
|
|
||||||
returnObject.baseUrl = tsconfig.compilerOptions.baseUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process paths
|
|
||||||
if (tsconfig.compilerOptions.paths) {
|
|
||||||
returnObject.paths = { ...tsconfig.compilerOptions.paths };
|
|
||||||
for (const path of Object.keys(returnObject.paths)) {
|
|
||||||
if (Array.isArray(returnObject.paths[path]) && returnObject.paths[path].length > 0) {
|
|
||||||
returnObject.paths[path][0] = returnObject.paths[path][0].replace('./ts_', './dist_ts_');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process target
|
|
||||||
if (tsconfig.compilerOptions.target) {
|
|
||||||
if (typeof tsconfig.compilerOptions.target === 'string') {
|
|
||||||
const targetKey = tsconfig.compilerOptions.target.toUpperCase();
|
|
||||||
if (targetKey in plugins.typescript.ScriptTarget) {
|
|
||||||
returnObject.target = plugins.typescript.ScriptTarget[targetKey as keyof typeof plugins.typescript.ScriptTarget];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process module
|
|
||||||
if (tsconfig.compilerOptions.module) {
|
|
||||||
if (typeof tsconfig.compilerOptions.module === 'string') {
|
|
||||||
const moduleKey = tsconfig.compilerOptions.module.toUpperCase();
|
|
||||||
if (moduleKey in plugins.typescript.ModuleKind) {
|
|
||||||
returnObject.module = plugins.typescript.ModuleKind[moduleKey as keyof typeof plugins.typescript.ModuleKind];
|
|
||||||
} else if (moduleKey === 'NODENEXT') {
|
|
||||||
returnObject.module = plugins.typescript.ModuleKind.NodeNext;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process moduleResolution
|
|
||||||
if (tsconfig.compilerOptions.moduleResolution) {
|
|
||||||
if (typeof tsconfig.compilerOptions.moduleResolution === 'string') {
|
|
||||||
const moduleResolutionKey = tsconfig.compilerOptions.moduleResolution.toUpperCase();
|
|
||||||
if (moduleResolutionKey in plugins.typescript.ModuleResolutionKind) {
|
|
||||||
returnObject.moduleResolution = plugins.typescript.ModuleResolutionKind[
|
|
||||||
moduleResolutionKey as keyof typeof plugins.typescript.ModuleResolutionKind
|
|
||||||
];
|
|
||||||
} else if (moduleResolutionKey === 'NODENEXT') {
|
|
||||||
returnObject.moduleResolution = plugins.typescript.ModuleResolutionKind.NodeNext;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy boolean options directly
|
|
||||||
const booleanOptions = [
|
|
||||||
'experimentalDecorators', 'useDefineForClassFields',
|
|
||||||
'esModuleInterop', 'verbatimModuleSyntax'
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const option of booleanOptions) {
|
|
||||||
if (option in tsconfig.compilerOptions) {
|
|
||||||
(returnObject as any)[option] = (tsconfig.compilerOptions as any)[option];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return returnObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Process command line arguments and return applicable compiler options
|
|
||||||
*/
|
|
||||||
private getCommandLineOptions(argvArg?: any): CompilerOptions {
|
|
||||||
if (!argvArg) return {};
|
|
||||||
|
|
||||||
const options: CompilerOptions = {};
|
|
||||||
|
|
||||||
if (argvArg.skiplibcheck) {
|
|
||||||
options.skipLibCheck = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Changed behavior: --disallowimplicitany instead of --allowimplicitany
|
|
||||||
if (argvArg.disallowimplicitany) {
|
|
||||||
options.noImplicitAny = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (argvArg.commonjs) {
|
|
||||||
options.module = plugins.typescript.ModuleKind.CommonJS;
|
|
||||||
options.moduleResolution = plugins.typescript.ModuleResolutionKind.NodeJs;
|
|
||||||
}
|
|
||||||
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Merges compilerOptions with the default compiler options
|
|
||||||
*/
|
|
||||||
public mergeCompilerOptions(
|
|
||||||
customTsOptions: CompilerOptions = {},
|
|
||||||
argvArg?: any
|
|
||||||
): CompilerOptions {
|
|
||||||
// create merged options
|
|
||||||
const mergedOptions: CompilerOptions = {
|
|
||||||
...compilerOptionsDefault,
|
|
||||||
...customTsOptions,
|
|
||||||
...this.getCommandLineOptions(argvArg),
|
|
||||||
...this.getTsConfigOptions(),
|
|
||||||
};
|
|
||||||
|
|
||||||
return mergedOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper function to handle and log TypeScript diagnostics
|
|
||||||
*/
|
|
||||||
private handleDiagnostics(diagnostics: readonly plugins.typescript.Diagnostic[]): boolean {
|
|
||||||
if (diagnostics.length === 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Group errors by file for better readability
|
|
||||||
const errorsByFile: Record<string, plugins.typescript.Diagnostic[]> = {};
|
|
||||||
const generalErrors: plugins.typescript.Diagnostic[] = [];
|
|
||||||
|
|
||||||
// Categorize diagnostics
|
|
||||||
diagnostics.forEach((diagnostic) => {
|
|
||||||
if (diagnostic.file) {
|
|
||||||
const fileName = diagnostic.file.fileName;
|
|
||||||
if (!errorsByFile[fileName]) {
|
|
||||||
errorsByFile[fileName] = [];
|
|
||||||
}
|
|
||||||
errorsByFile[fileName].push(diagnostic);
|
|
||||||
} else {
|
|
||||||
generalErrors.push(diagnostic);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Print error summary header
|
|
||||||
const totalErrorCount = diagnostics.length;
|
|
||||||
const fileCount = Object.keys(errorsByFile).length;
|
|
||||||
|
|
||||||
console.log('\n' + '='.repeat(80));
|
|
||||||
console.log(`❌ Found ${totalErrorCount} error${totalErrorCount !== 1 ? 's' : ''} in ${fileCount} file${fileCount !== 1 ? 's' : ''}:`);
|
|
||||||
console.log('='.repeat(80));
|
|
||||||
|
|
||||||
// Color codes for error formatting
|
|
||||||
const colors = {
|
|
||||||
reset: '\x1b[0m',
|
|
||||||
red: '\x1b[31m',
|
|
||||||
yellow: '\x1b[33m',
|
|
||||||
cyan: '\x1b[36m',
|
|
||||||
white: '\x1b[37m',
|
|
||||||
brightRed: '\x1b[91m'
|
|
||||||
};
|
|
||||||
|
|
||||||
// Print file-specific errors
|
|
||||||
Object.entries(errorsByFile).forEach(([fileName, fileErrors]) => {
|
|
||||||
// Show relative path if possible for cleaner output
|
|
||||||
const displayPath = fileName.replace(process.cwd(), '').replace(/^\//, '');
|
|
||||||
|
|
||||||
console.log(`\n${colors.cyan}File: ${displayPath} ${colors.yellow}(${fileErrors.length} error${fileErrors.length !== 1 ? 's' : ''})${colors.reset}`);
|
|
||||||
console.log('-'.repeat(80));
|
|
||||||
|
|
||||||
fileErrors.forEach((diagnostic) => {
|
|
||||||
if (diagnostic.file && diagnostic.start !== undefined) {
|
|
||||||
const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
|
|
||||||
const message = plugins.typescript.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
|
|
||||||
const errorCode = diagnostic.code ? `TS${diagnostic.code}` : 'Error';
|
|
||||||
|
|
||||||
console.log(`${colors.white}Line ${line + 1}, Col ${character + 1}${colors.reset}: ${colors.brightRed}${errorCode}${colors.reset} - ${message}`);
|
|
||||||
|
|
||||||
// Try to show the code snippet if possible
|
|
||||||
try {
|
|
||||||
const lineContent = diagnostic.file.text.split('\n')[line];
|
|
||||||
if (lineContent) {
|
|
||||||
// Show the line of code
|
|
||||||
console.log(` ${lineContent.trimRight()}`);
|
|
||||||
|
|
||||||
// Show the error position indicator
|
|
||||||
const indicator = ' '.repeat(character) + `${colors.red}^${colors.reset}`;
|
|
||||||
console.log(` ${indicator}`);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// Failed to get source text, skip showing the code snippet
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Print general errors
|
|
||||||
if (generalErrors.length > 0) {
|
|
||||||
console.log(`\n${colors.yellow}General Errors:${colors.reset}`);
|
|
||||||
console.log('-'.repeat(80));
|
|
||||||
|
|
||||||
generalErrors.forEach((diagnostic) => {
|
|
||||||
const message = plugins.typescript.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
|
|
||||||
const errorCode = diagnostic.code ? `TS${diagnostic.code}` : 'Error';
|
|
||||||
console.log(`${colors.brightRed}${errorCode}${colors.reset}: ${message}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('\n' + '='.repeat(80) + '\n');
|
|
||||||
|
|
||||||
return diagnostics.length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a TypeScript program from file names and options
|
|
||||||
*/
|
|
||||||
private createProgram(
|
|
||||||
options: plugins.typescript.CompilerOptions = this.options
|
|
||||||
): plugins.typescript.Program {
|
|
||||||
return plugins.typescript.createProgram(this.fileNames, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set file names to be compiled
|
|
||||||
*/
|
|
||||||
public setFileNames(fileNames: string[]): void {
|
|
||||||
this.fileNames = fileNames;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set compiler options
|
|
||||||
*/
|
|
||||||
public setOptions(options: CompilerOptions): void {
|
|
||||||
this.options = { ...this.options, ...options };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The main compiler function that compiles the files
|
|
||||||
*/
|
|
||||||
public async compile(): Promise<any[]> {
|
|
||||||
if (this.options.skipLibCheck) {
|
|
||||||
console.log('\n⚠️ WARNING ⚠️');
|
|
||||||
console.log('You are skipping libcheck... Is that really wanted?');
|
|
||||||
console.log('Continuing in 5 seconds...\n');
|
|
||||||
await plugins.smartdelay.delayFor(5000);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`🔨 Compiling ${this.fileNames.length} files...`);
|
|
||||||
const done = plugins.smartpromise.defer<any[]>();
|
|
||||||
const program = this.createProgram();
|
|
||||||
|
|
||||||
// Check for pre-emit diagnostics first
|
|
||||||
const preEmitDiagnostics = plugins.typescript.getPreEmitDiagnostics(program);
|
|
||||||
const hasPreEmitErrors = this.handleDiagnostics(preEmitDiagnostics);
|
|
||||||
|
|
||||||
// Only continue to emit phase if no pre-emit errors
|
|
||||||
if (hasPreEmitErrors) {
|
|
||||||
console.error('\n❌ TypeScript pre-emit checks failed. Please fix the issues listed above before proceeding.');
|
|
||||||
console.error(' Type errors must be resolved before the compiler can emit output files.\n');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no pre-emit errors, proceed with emit
|
|
||||||
const emitResult = program.emit();
|
|
||||||
const hasEmitErrors = this.handleDiagnostics(emitResult.diagnostics);
|
|
||||||
|
|
||||||
const exitCode = emitResult.emitSkipped ? 1 : 0;
|
|
||||||
if (exitCode === 0) {
|
|
||||||
console.log('\n✅ TypeScript emit succeeded!');
|
|
||||||
|
|
||||||
// Get count of emitted files by type
|
|
||||||
const jsFiles = emitResult.emittedFiles?.filter(f => f.endsWith('.js')).length || 0;
|
|
||||||
const dtsFiles = emitResult.emittedFiles?.filter(f => f.endsWith('.d.ts')).length || 0;
|
|
||||||
const mapFiles = emitResult.emittedFiles?.filter(f => f.endsWith('.map')).length || 0;
|
|
||||||
|
|
||||||
// If we have emitted files, show a summary
|
|
||||||
if (emitResult.emittedFiles && emitResult.emittedFiles.length > 0) {
|
|
||||||
console.log(` Generated ${emitResult.emittedFiles.length} files: ${jsFiles} .js, ${dtsFiles} .d.ts, ${mapFiles} source maps`);
|
|
||||||
}
|
|
||||||
|
|
||||||
done.resolve(emitResult.emittedFiles);
|
|
||||||
} else {
|
|
||||||
console.error('\n❌ TypeScript emit failed. Please investigate the errors listed above!');
|
|
||||||
console.error(' No output files have been generated.\n');
|
|
||||||
process.exit(exitCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
return done.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Function to check if files can be emitted without actually emitting them
|
|
||||||
*/
|
|
||||||
public async checkEmit(): Promise<boolean> {
|
|
||||||
const fileCount = this.fileNames.length;
|
|
||||||
console.log(`\n🔍 Checking if ${fileCount} file${fileCount !== 1 ? 's' : ''} can be emitted...`);
|
|
||||||
|
|
||||||
// Create a program with noEmit option
|
|
||||||
const program = this.createProgram({
|
|
||||||
...this.options,
|
|
||||||
noEmit: true
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check for pre-emit diagnostics
|
|
||||||
const preEmitDiagnostics = plugins.typescript.getPreEmitDiagnostics(program);
|
|
||||||
const hasPreEmitErrors = this.handleDiagnostics(preEmitDiagnostics);
|
|
||||||
|
|
||||||
// Run the emit phase but with noEmit: true to check for emit errors without producing files
|
|
||||||
const emitResult = program.emit(undefined, undefined, undefined, true);
|
|
||||||
const hasEmitErrors = this.handleDiagnostics(emitResult.diagnostics);
|
|
||||||
|
|
||||||
const success = !hasPreEmitErrors && !hasEmitErrors && !emitResult.emitSkipped;
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
console.log('\n✅ TypeScript emit check passed! All files can be emitted successfully.');
|
|
||||||
console.log(` ${fileCount} file${fileCount !== 1 ? 's' : ''} ${fileCount !== 1 ? 'are' : 'is'} ready to be compiled.\n`);
|
|
||||||
} else {
|
|
||||||
console.error('\n❌ TypeScript emit check failed. Please fix the issues listed above.');
|
|
||||||
console.error(' The compilation cannot proceed until these errors are resolved.\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Function to check TypeScript files for type errors without emission
|
|
||||||
*/
|
|
||||||
public async checkTypes(): Promise<boolean> {
|
|
||||||
const fileCount = this.fileNames.length;
|
|
||||||
console.log(`\n🔍 Type checking ${fileCount} TypeScript file${fileCount !== 1 ? 's' : ''}...`);
|
|
||||||
|
|
||||||
// Create a program with noEmit option explicitly set
|
|
||||||
const program = this.createProgram({
|
|
||||||
...this.options,
|
|
||||||
noEmit: true
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check for type errors
|
|
||||||
const diagnostics = plugins.typescript.getPreEmitDiagnostics(program);
|
|
||||||
const hasErrors = this.handleDiagnostics(diagnostics);
|
|
||||||
|
|
||||||
// Set success flag
|
|
||||||
const success = !hasErrors;
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
console.log('\n✅ TypeScript type check passed! No type errors found.');
|
|
||||||
console.log(` All ${fileCount} file${fileCount !== 1 ? 's' : ''} passed type checking successfully.\n`);
|
|
||||||
} else {
|
|
||||||
console.error('\n❌ TypeScript type check failed. Please fix the type errors listed above.');
|
|
||||||
console.error(' The type checker found issues that need to be resolved.\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Merges compilerOptions with the default compiler options (backward compatibility)
|
|
||||||
*/
|
|
||||||
export const mergeCompilerOptions = (
|
|
||||||
customTsOptions: CompilerOptions,
|
|
||||||
argvArg?: any
|
|
||||||
): CompilerOptions => {
|
|
||||||
const tsBuild = new TsBuild();
|
|
||||||
return tsBuild.mergeCompilerOptions(customTsOptions, argvArg);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The internal main compiler function that compiles the files (backward compatibility)
|
|
||||||
*/
|
|
||||||
export const compiler = async (
|
|
||||||
fileNames: string[],
|
|
||||||
options: plugins.typescript.CompilerOptions,
|
|
||||||
argvArg?: any
|
|
||||||
): Promise<any[]> => {
|
|
||||||
const tsBuild = new TsBuild(fileNames, options, argvArg);
|
|
||||||
return tsBuild.compile();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Function to check if a TypeScript file can be emitted without actually emitting it (backward compatibility)
|
|
||||||
*/
|
|
||||||
export const emitCheck = async (
|
|
||||||
fileNames: string[],
|
|
||||||
options: plugins.typescript.CompilerOptions = {},
|
|
||||||
argvArg?: any
|
|
||||||
): Promise<boolean> => {
|
|
||||||
const tsBuild = new TsBuild(fileNames, options, argvArg);
|
|
||||||
return tsBuild.checkEmit();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Function to check TypeScript files for type errors without emission (backward compatibility)
|
|
||||||
*/
|
|
||||||
export const checkTypes = async (
|
|
||||||
fileNames: string[],
|
|
||||||
options: plugins.typescript.CompilerOptions = {},
|
|
||||||
argvArg?: any
|
|
||||||
): Promise<boolean> => {
|
|
||||||
const tsBuild = new TsBuild(fileNames, options, argvArg);
|
|
||||||
return tsBuild.checkTypes();
|
|
||||||
};
|
|
||||||
@@ -1,272 +0,0 @@
|
|||||||
import * as plugins from './plugins.js';
|
|
||||||
import * as paths from './paths.js';
|
|
||||||
import * as tsbuild from './tsbuild.exports.js';
|
|
||||||
|
|
||||||
export const runCli = async () => {
|
|
||||||
const tsbuildCli = new plugins.smartcli.Smartcli();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* the standard task compiles anything in ts/ directory to dist directory
|
|
||||||
*/
|
|
||||||
tsbuildCli.standardCommand().subscribe(async (argvArg) => {
|
|
||||||
tsbuild.compileGlobStringObject(
|
|
||||||
{
|
|
||||||
'./ts/**/*.ts': './dist_ts',
|
|
||||||
},
|
|
||||||
{},
|
|
||||||
process.cwd(),
|
|
||||||
argvArg
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* the custom command compiles any customDir to dist_customDir
|
|
||||||
*/
|
|
||||||
tsbuildCli.addCommand('custom').subscribe(async (argvArg) => {
|
|
||||||
const listedDirectories = argvArg._;
|
|
||||||
listedDirectories.shift(); // removes the first element that is "custom"
|
|
||||||
const compilationCommandObject: { [key: string]: string } = {};
|
|
||||||
for (const directory of listedDirectories) {
|
|
||||||
compilationCommandObject[`./${directory}/**/*.ts`] = `./dist_${directory}`;
|
|
||||||
}
|
|
||||||
await tsbuild.compileGlobStringObject(compilationCommandObject, {}, process.cwd(), argvArg);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* the emitcheck command checks if a TypeScript file can be emitted without actually emitting it
|
|
||||||
*/
|
|
||||||
tsbuildCli.addCommand('emitcheck').subscribe(async (argvArg) => {
|
|
||||||
const patterns = argvArg._.slice(1); // Remove the first element which is 'emitcheck'
|
|
||||||
|
|
||||||
if (patterns.length === 0) {
|
|
||||||
console.error('\n❌ Error: Please provide at least one TypeScript file path or glob pattern');
|
|
||||||
console.error(' Usage: tsbuild emitcheck <file_or_glob_pattern> [additional_patterns ...]\n');
|
|
||||||
console.error(' Example: tsbuild emitcheck "src/**/*.ts" "test/**/*.ts"\n');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const cwd = process.cwd();
|
|
||||||
let allFiles: string[] = [];
|
|
||||||
|
|
||||||
// Process each pattern - could be a direct file path or a glob pattern
|
|
||||||
for (const pattern of patterns) {
|
|
||||||
// Check if the pattern looks like a glob pattern
|
|
||||||
if (pattern.includes('*') || pattern.includes('{') || pattern.includes('?')) {
|
|
||||||
// Handle as glob pattern
|
|
||||||
console.log(`Processing glob pattern: ${pattern}`);
|
|
||||||
try {
|
|
||||||
const matchedFiles = await plugins.smartfile.fs.listFileTree(cwd, pattern);
|
|
||||||
|
|
||||||
// Ensure matchedFiles contains only strings
|
|
||||||
const stringMatchedFiles = Array.isArray(matchedFiles)
|
|
||||||
? matchedFiles.filter((item): item is string => typeof item === 'string')
|
|
||||||
: [];
|
|
||||||
|
|
||||||
if (stringMatchedFiles.length === 0) {
|
|
||||||
console.warn(`⚠️ Warning: No files matched the pattern '${pattern}'`);
|
|
||||||
} else {
|
|
||||||
console.log(`📂 Found ${stringMatchedFiles.length} files matching pattern '${pattern}'`);
|
|
||||||
|
|
||||||
// Transform to absolute paths
|
|
||||||
const absoluteMatchedFiles = plugins.smartpath.transform.toAbsolute(
|
|
||||||
stringMatchedFiles,
|
|
||||||
cwd
|
|
||||||
) as string[];
|
|
||||||
|
|
||||||
// Add to the list of all files to check
|
|
||||||
allFiles = allFiles.concat(absoluteMatchedFiles);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`❌ Error processing glob pattern '${pattern}': ${err}`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Handle as direct file path
|
|
||||||
const filePath = plugins.path.isAbsolute(pattern)
|
|
||||||
? pattern
|
|
||||||
: plugins.path.join(cwd, pattern);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await plugins.smartfile.fs.fileExists(filePath);
|
|
||||||
allFiles.push(filePath);
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`❌ Error: File not found: ${filePath}`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter to only TypeScript files
|
|
||||||
allFiles = allFiles.filter(file => file.endsWith('.ts') || file.endsWith('.tsx'));
|
|
||||||
|
|
||||||
if (allFiles.length === 0) {
|
|
||||||
console.error('\n❌ Error: No TypeScript files found to check');
|
|
||||||
console.error(' Please verify your file paths or glob patterns.\n');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`\n🔎 Found ${allFiles.length} TypeScript file${allFiles.length !== 1 ? 's' : ''} to check`);
|
|
||||||
|
|
||||||
// Process compiler options
|
|
||||||
const compilerOptions = tsbuild.mergeCompilerOptions({}, argvArg);
|
|
||||||
|
|
||||||
// Run emit check
|
|
||||||
const success = await tsbuild.emitCheck(allFiles, compilerOptions, argvArg);
|
|
||||||
|
|
||||||
// Exit with appropriate code
|
|
||||||
process.exit(success ? 0 : 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* the custom command compiles any customDir to dist_customDir
|
|
||||||
*/
|
|
||||||
tsbuildCli.addCommand('tsfolders').subscribe(async (argvArg) => {
|
|
||||||
const tsFolders = await plugins.smartfile.fs.listFolders(paths.cwd, /^ts/);
|
|
||||||
|
|
||||||
// Now tsFolders contains all other folders except 'ts_shared' and 'ts_interfaces'
|
|
||||||
|
|
||||||
// We've established a base order. Now let's look at tspublish.json based ranking.
|
|
||||||
const tsPublishInstance = new plugins.tspublish.TsPublish();
|
|
||||||
const tsPublishModules = await tsPublishInstance.getModuleSubDirs(paths.cwd);
|
|
||||||
// tsPublishModules is an object: { [folderName]: tspublishJsonData }
|
|
||||||
|
|
||||||
// Create an array with folder names and their ranks
|
|
||||||
const foldersWithOrder = [];
|
|
||||||
|
|
||||||
for (const folder of tsFolders) {
|
|
||||||
let rank = Infinity; // Default rank if not specified
|
|
||||||
if (tsPublishModules[folder] && tsPublishModules[folder].order !== undefined) {
|
|
||||||
rank = tsPublishModules[folder].order;
|
|
||||||
}
|
|
||||||
foldersWithOrder.push({ folder, rank });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort the folders based on rank
|
|
||||||
foldersWithOrder.sort((a, b) => a.rank - b.rank);
|
|
||||||
|
|
||||||
// Construct the sorted list of folders
|
|
||||||
const sortedTsFolders = [];
|
|
||||||
|
|
||||||
// Add the rest of the folders in sorted order
|
|
||||||
for (const item of foldersWithOrder) {
|
|
||||||
sortedTsFolders.push(item.folder);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Let's make sure 'ts_shared' is always transpiled first
|
|
||||||
const ensurePosition = (folderNameArg: string, ensuredPosition: number) => {
|
|
||||||
if (tsFolders.indexOf(folderNameArg) > -1 && Object.keys(tsPublishModules).indexOf(folderNameArg) === -1) {
|
|
||||||
sortedTsFolders.splice(tsFolders.indexOf(folderNameArg), 1);
|
|
||||||
sortedTsFolders.splice(ensuredPosition, 0, folderNameArg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ensurePosition('ts_interfaces', 0);
|
|
||||||
ensurePosition('ts_shared', 1);
|
|
||||||
|
|
||||||
|
|
||||||
const compilationCommandObject: { [key: string]: string } = {};
|
|
||||||
const folderCount = sortedTsFolders.length;
|
|
||||||
console.log(`\n📂 TypeScript Folder Compilation Plan (${folderCount} folder${folderCount !== 1 ? 's' : ''})`);
|
|
||||||
console.log('┌' + '─'.repeat(60) + '┐');
|
|
||||||
console.log('│ 🔄 Compilation Order │');
|
|
||||||
console.log('├' + '─'.repeat(60) + '┤');
|
|
||||||
|
|
||||||
sortedTsFolders.forEach((folder, index) => {
|
|
||||||
const prefix = index === folderCount - 1 ? '└─' : '├─';
|
|
||||||
const position = `${index + 1}/${folderCount}`;
|
|
||||||
console.log(`│ ${prefix} ${position.padStart(5)} ${folder.padEnd(46)} │`);
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('└' + '─'.repeat(60) + '┘\n');
|
|
||||||
|
|
||||||
for (const tsFolder of sortedTsFolders) {
|
|
||||||
compilationCommandObject[`./${tsFolder}/**/*.ts`] = `./dist_${tsFolder}`;
|
|
||||||
}
|
|
||||||
await tsbuild.compileGlobStringObject(compilationCommandObject, {}, process.cwd(), argvArg);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* the check command checks TypeScript files against a glob pattern without emitting them
|
|
||||||
*/
|
|
||||||
tsbuildCli.addCommand('check').subscribe(async (argvArg) => {
|
|
||||||
const patterns = argvArg._.slice(1); // Remove the first element which is 'check'
|
|
||||||
|
|
||||||
if (patterns.length === 0) {
|
|
||||||
console.error('\n❌ Error: Please provide at least one TypeScript file path or glob pattern');
|
|
||||||
console.error(' Usage: tsbuild check <file_or_glob_pattern> [additional_patterns ...]\n');
|
|
||||||
console.error(' Example: tsbuild check "src/**/*.ts" "test/**/*.ts"\n');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const cwd = process.cwd();
|
|
||||||
let allFiles: string[] = [];
|
|
||||||
|
|
||||||
// Process each pattern - could be a direct file path or a glob pattern
|
|
||||||
for (const pattern of patterns) {
|
|
||||||
// Check if the pattern looks like a glob pattern
|
|
||||||
if (pattern.includes('*') || pattern.includes('{') || pattern.includes('?')) {
|
|
||||||
// Handle as glob pattern
|
|
||||||
console.log(`Processing glob pattern: ${pattern}`);
|
|
||||||
try {
|
|
||||||
const matchedFiles = await plugins.smartfile.fs.listFileTree(cwd, pattern);
|
|
||||||
|
|
||||||
// Ensure matchedFiles contains only strings
|
|
||||||
const stringMatchedFiles = Array.isArray(matchedFiles)
|
|
||||||
? matchedFiles.filter((item): item is string => typeof item === 'string')
|
|
||||||
: [];
|
|
||||||
|
|
||||||
if (stringMatchedFiles.length === 0) {
|
|
||||||
console.warn(`⚠️ Warning: No files matched the pattern '${pattern}'`);
|
|
||||||
} else {
|
|
||||||
console.log(`📂 Found ${stringMatchedFiles.length} files matching pattern '${pattern}'`);
|
|
||||||
|
|
||||||
// Transform to absolute paths
|
|
||||||
const absoluteMatchedFiles = plugins.smartpath.transform.toAbsolute(
|
|
||||||
stringMatchedFiles,
|
|
||||||
cwd
|
|
||||||
) as string[];
|
|
||||||
|
|
||||||
// Add to the list of all files to check
|
|
||||||
allFiles = allFiles.concat(absoluteMatchedFiles);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`❌ Error processing glob pattern '${pattern}': ${err}`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Handle as direct file path
|
|
||||||
const filePath = plugins.path.isAbsolute(pattern)
|
|
||||||
? pattern
|
|
||||||
: plugins.path.join(cwd, pattern);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await plugins.smartfile.fs.fileExists(filePath);
|
|
||||||
allFiles.push(filePath);
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`❌ Error: File not found: ${filePath}`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter to only TypeScript files
|
|
||||||
allFiles = allFiles.filter(file => file.endsWith('.ts') || file.endsWith('.tsx'));
|
|
||||||
|
|
||||||
if (allFiles.length === 0) {
|
|
||||||
console.error('\n❌ Error: No TypeScript files found to check');
|
|
||||||
console.error(' Please verify your file paths or glob patterns.\n');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`\n🔎 Found ${allFiles.length} TypeScript file${allFiles.length !== 1 ? 's' : ''} to check`);
|
|
||||||
|
|
||||||
// Process compiler options
|
|
||||||
const compilerOptions = tsbuild.mergeCompilerOptions({}, argvArg);
|
|
||||||
|
|
||||||
// Run type check without emitting
|
|
||||||
const success = await tsbuild.checkTypes(allFiles, compilerOptions, argvArg);
|
|
||||||
|
|
||||||
// Exit with appropriate code
|
|
||||||
process.exit(success ? 0 : 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
tsbuildCli.startParse();
|
|
||||||
};
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
import * as plugins from './plugins.js';
|
|
||||||
import type { CompilerOptions, ScriptTarget, ModuleKind } from 'typescript';
|
|
||||||
import { compiler, mergeCompilerOptions, emitCheck, checkTypes } from './tsbuild.classes.tsbuild.js';
|
|
||||||
|
|
||||||
export type { CompilerOptions, ScriptTarget, ModuleKind };
|
|
||||||
|
|
||||||
export * from './tsbuild.classes.tsbuild.js';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* compile am array of absolute file paths
|
|
||||||
*/
|
|
||||||
export let compileFileArray = (
|
|
||||||
fileStringArrayArg: string[],
|
|
||||||
compilerOptionsArg: CompilerOptions = {},
|
|
||||||
argvArg?: any
|
|
||||||
): Promise<any[]> => {
|
|
||||||
return compiler(fileStringArrayArg, mergeCompilerOptions(compilerOptionsArg, argvArg), argvArg);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* compile advanced glob configurations
|
|
||||||
* @param globStringArrayArg a array of glob strings
|
|
||||||
* {
|
|
||||||
* './some/origin/folder/**\/*.ts': './some/destination/folder'
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
export let compileGlobStringObject = async (
|
|
||||||
globStringObjectArg: Record<string, string>,
|
|
||||||
tsOptionsArg: CompilerOptions = {},
|
|
||||||
cwdArg: string = process.cwd(),
|
|
||||||
argvArg?: any
|
|
||||||
) => {
|
|
||||||
let compiledFiles: any[] = [];
|
|
||||||
|
|
||||||
// Log the compilation tasks in a nice format
|
|
||||||
console.log('\n👷 TypeScript Compilation Tasks:');
|
|
||||||
Object.entries(globStringObjectArg).forEach(([source, dest]) => {
|
|
||||||
console.log(` 📂 ${source} → ${dest}`);
|
|
||||||
});
|
|
||||||
console.log('');
|
|
||||||
|
|
||||||
for (const keyArg in globStringObjectArg) {
|
|
||||||
// Type safety check for key
|
|
||||||
if (keyArg && typeof keyArg === 'string' && globStringObjectArg[keyArg]) {
|
|
||||||
|
|
||||||
// Get files matching the glob pattern
|
|
||||||
const fileTreeArray = await plugins.smartfile.fs.listFileTree(cwdArg, keyArg);
|
|
||||||
|
|
||||||
// Ensure fileTreeArray contains only strings before transforming
|
|
||||||
const stringFileTreeArray = Array.isArray(fileTreeArray)
|
|
||||||
? fileTreeArray.filter((item): item is string => typeof item === 'string')
|
|
||||||
: [];
|
|
||||||
|
|
||||||
// Transform to absolute paths
|
|
||||||
const absoluteFilePathArray = plugins.smartpath.transform.toAbsolute(
|
|
||||||
stringFileTreeArray,
|
|
||||||
cwdArg
|
|
||||||
) as string[];
|
|
||||||
|
|
||||||
// Get destination directory as absolute path
|
|
||||||
const destDir: string = plugins.smartpath.transform.toAbsolute(
|
|
||||||
globStringObjectArg[keyArg],
|
|
||||||
cwdArg
|
|
||||||
) as string;
|
|
||||||
|
|
||||||
// Update compiler options with the output directory
|
|
||||||
const updatedTsOptions: CompilerOptions = {
|
|
||||||
...tsOptionsArg,
|
|
||||||
outDir: destDir,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Compile the files and correctly concat the results
|
|
||||||
// Fixed: removed duplicating compiledFiles in the concat operation
|
|
||||||
const newlyCompiledFiles = await compileFileArray(absoluteFilePathArray, updatedTsOptions, argvArg);
|
|
||||||
compiledFiles = compiledFiles.concat(newlyCompiledFiles);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return compiledFiles;
|
|
||||||
};
|
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"experimentalDecorators": true,
|
|
||||||
"useDefineForClassFields": false,
|
|
||||||
"target": "ES2022",
|
"target": "ES2022",
|
||||||
"module": "NodeNext",
|
"module": "NodeNext",
|
||||||
"moduleResolution": "NodeNext",
|
"moduleResolution": "NodeNext",
|
||||||
|
|||||||
Reference in New Issue
Block a user