Compare commits

..

No commits in common. "master" and "v2.3.2" have entirely different histories.

9 changed files with 50 additions and 489 deletions

1
.gitignore vendored

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

@ -1,35 +1,5 @@
# Changelog # Changelog
## 2025-05-15 - 2.5.1 - fix(commitinfo)
Update commit information and metadata to synchronize release data
- Regenerated the commitinfo file with current version details
- Maintained existing functionality with no functional code changes
## 2025-05-15 - 2.5.0 - feat(cli)
Enhance type checking in CLI by adding default file pattern handling
- When no TypeScript file or glob pattern is provided, the CLI now performs a default type checking sequence.
- First checks 'ts/**/*' files with standard options, then checks 'test/**/*' files with skiplibcheck enabled.
- Improved logging to indicate file discovery and check results, ensuring clear feedback for users.
## 2025-05-15 - 2.4.1 - fix(cli)
Improve TS folder compilation order display in CLI
- Refactor folder compilation output to use a bordered, tabular format with order numbering
- Enhance readability of TS folder compilation plan in the CLI output
## 2025-05-15 - 2.4.0 - feat(cli)
Add new 'check' command for type checking and update compiler options handling
- Introduced a new 'check' command to verify TypeScript files without emitting output
- Updated CLI error messages and logging for better clarity
- Replaced '--allowimplicitany' flag with '--disallowimplicitany' to reflect new default behavior
- Modified compiler options default settings (noImplicitAny now set to false) for more flexible type handling
- Refined diagnostic output in tsbuild class for improved error reporting
- Updated .gitignore to exclude the .claude file
- Enhanced documentation in readme and implementation plan files
## 2025-03-20 - 2.3.2 - fix(compileGlobStringObject) ## 2025-03-20 - 2.3.2 - fix(compileGlobStringObject)
Fix duplicate file outputs in glob pattern processing Fix duplicate file outputs in glob pattern processing

@ -1,6 +1,6 @@
{ {
"name": "@git.zone/tsbuild", "name": "@git.zone/tsbuild",
"version": "2.5.1", "version": "2.3.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",
@ -11,7 +11,7 @@
}, },
"scripts": { "scripts": {
"test": "tsrun test/test.ts", "test": "tsrun test/test.ts",
"build": "node cli.ts.js --web", "build": "node cli.ts.js --web --allowimplicitany",
"buildDocs": "tsdoc" "buildDocs": "tsdoc"
}, },
"repository": { "repository": {
@ -65,6 +65,5 @@
], ],
"browserslist": [ "browserslist": [
"last 1 chrome versions" "last 1 chrome versions"
], ]
"packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39"
} }

@ -146,42 +146,17 @@ Example usage with glob patterns:
npx tsbuild emitcheck "src/**/*.ts" "test/**/*.ts" npx tsbuild emitcheck "src/**/*.ts" "test/**/*.ts"
``` ```
### Check Command
Performs type checking on TypeScript files specified by glob patterns without emitting them:
```bash
npx tsbuild check <glob_pattern> [additional_patterns ...]
```
This command:
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:
```bash
npx tsbuild check ts/**/*
```
Example usage with multiple patterns:
```bash
npx tsbuild check "src/**/*.ts" "test/**/*.ts"
```
## Compiler Options ## Compiler Options
Additional flags can be passed to any command to modify the compilation behavior: Additional flags can be passed to any command to modify the compilation behavior:
- `--skiplibcheck`: Skip type checking of declaration files (shows warning) - `--skiplibcheck`: Skip type checking of declaration files (shows warning)
- `--disallowimplicitany`: Disallow variables to be implicitly typed as `any` (implicit any is allowed by default) - `--allowimplicitany`: Allow variables to be implicitly typed as `any`
- `--commonjs`: Use CommonJS module format instead of ESNext - `--commonjs`: Use CommonJS module format instead of ESNext
Example: Example:
```bash ```bash
npx tsbuild --skiplibcheck --disallowimplicitany npx tsbuild --skiplibcheck --allowimplicitany
``` ```
## Default Compiler Options ## Default Compiler Options
@ -200,7 +175,7 @@ By default, `@git.zone/tsbuild` uses the following compiler options:
target: ScriptTarget.ESNext, target: ScriptTarget.ESNext,
moduleResolution: ModuleResolutionKind.NodeNext, moduleResolution: ModuleResolutionKind.NodeNext,
lib: ['lib.dom.d.ts', 'lib.es2022.d.ts'], lib: ['lib.dom.d.ts', 'lib.es2022.d.ts'],
noImplicitAny: false, // Now allowing implicit any by default noImplicitAny: true,
esModuleInterop: true, esModuleInterop: true,
useDefineForClassFields: false, useDefineForClassFields: false,
verbatimModuleSyntax: true, verbatimModuleSyntax: true,

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

@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@git.zone/tsbuild', name: '@git.zone/tsbuild',
version: '2.5.1', version: '2.3.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.'
} }

@ -17,7 +17,7 @@ export const compilerOptionsDefault: CompilerOptions = {
target: plugins.typescript.ScriptTarget.ESNext, target: plugins.typescript.ScriptTarget.ESNext,
moduleResolution: plugins.typescript.ModuleResolutionKind.NodeNext, moduleResolution: plugins.typescript.ModuleResolutionKind.NodeNext,
lib: ['lib.dom.d.ts', 'lib.es2022.d.ts'], lib: ['lib.dom.d.ts', 'lib.es2022.d.ts'],
noImplicitAny: false, // Allow implicit any by default noImplicitAny: true,
esModuleInterop: true, esModuleInterop: true,
useDefineForClassFields: false, useDefineForClassFields: false,
verbatimModuleSyntax: true, verbatimModuleSyntax: true,
@ -49,6 +49,7 @@ export class TsBuild {
* Helper function to read and process tsconfig.json * Helper function to read and process tsconfig.json
*/ */
private getTsConfigOptions(): CompilerOptions { private getTsConfigOptions(): CompilerOptions {
console.log(`looking at tsconfig.json at ${paths.cwd}`);
const tsconfig = plugins.smartfile.fs.toObjectSync(plugins.path.join(paths.cwd, 'tsconfig.json')); const tsconfig = plugins.smartfile.fs.toObjectSync(plugins.path.join(paths.cwd, 'tsconfig.json'));
const returnObject: CompilerOptions = {}; const returnObject: CompilerOptions = {};
@ -56,68 +57,18 @@ export class TsBuild {
return returnObject; return returnObject;
} }
// Process baseUrl
if (tsconfig.compilerOptions.baseUrl) { if (tsconfig.compilerOptions.baseUrl) {
console.log('baseUrl found in tsconfig.json');
returnObject.baseUrl = tsconfig.compilerOptions.baseUrl; returnObject.baseUrl = tsconfig.compilerOptions.baseUrl;
} }
// Process paths
if (tsconfig.compilerOptions.paths) { if (tsconfig.compilerOptions.paths) {
returnObject.paths = { ...tsconfig.compilerOptions.paths }; console.log('paths found in tsconfig.json');
for (const path of Object.keys(returnObject.paths)) { returnObject.paths = tsconfig.compilerOptions.paths;
if (Array.isArray(returnObject.paths[path]) && returnObject.paths[path].length > 0) { for (const path of Object.keys(tsconfig.compilerOptions.paths)) {
returnObject.paths[path][0] = returnObject.paths[path][0].replace('./ts_', './dist_ts_'); 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; return returnObject;
} }
@ -134,9 +85,8 @@ export class TsBuild {
options.skipLibCheck = true; options.skipLibCheck = true;
} }
// Changed behavior: --disallowimplicitany instead of --allowimplicitany if (argvArg.allowimplicitany) {
if (argvArg.disallowimplicitany) { options.noImplicitAny = false;
options.noImplicitAny = true;
} }
if (argvArg.commonjs) { if (argvArg.commonjs) {
@ -162,6 +112,7 @@ export class TsBuild {
...this.getTsConfigOptions(), ...this.getTsConfigOptions(),
}; };
console.log(mergedOptions);
return mergedOptions; return mergedOptions;
} }
@ -169,94 +120,22 @@ export class TsBuild {
* Helper function to handle and log TypeScript diagnostics * Helper function to handle and log TypeScript diagnostics
*/ */
private handleDiagnostics(diagnostics: readonly plugins.typescript.Diagnostic[]): boolean { private handleDiagnostics(diagnostics: readonly plugins.typescript.Diagnostic[]): boolean {
if (diagnostics.length === 0) { let hasErrors = false;
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) => { diagnostics.forEach((diagnostic) => {
hasErrors = true;
if (diagnostic.file) { if (diagnostic.file) {
const fileName = diagnostic.file.fileName; const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start!);
if (!errorsByFile[fileName]) { const message = plugins.typescript.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
errorsByFile[fileName] = []; console.log(`${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`);
}
errorsByFile[fileName].push(diagnostic);
} else { } else {
generalErrors.push(diagnostic); console.log(
`${plugins.typescript.flattenDiagnosticMessageText(diagnostic.messageText, '\n')}`
);
} }
}); });
// Print error summary header return hasErrors;
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;
} }
/** /**
@ -287,13 +166,14 @@ export class TsBuild {
*/ */
public async compile(): Promise<any[]> { public async compile(): Promise<any[]> {
if (this.options.skipLibCheck) { if (this.options.skipLibCheck) {
console.log('\n⚠ WARNING ⚠️'); console.log('? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?');
console.log('You are skipping libcheck... Is that really wanted?'); console.log('You are skipping libcheck... Is that really wanted?');
console.log('Continuing in 5 seconds...\n'); console.log('continuing in 5 seconds...');
console.log('? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?');
await plugins.smartdelay.delayFor(5000); await plugins.smartdelay.delayFor(5000);
} }
console.log(`🔨 Compiling ${this.fileNames.length} files...`); console.log(`Compiling ${this.fileNames.length} files...`);
const done = plugins.smartpromise.defer<any[]>(); const done = plugins.smartpromise.defer<any[]>();
const program = this.createProgram(); const program = this.createProgram();
@ -303,8 +183,7 @@ export class TsBuild {
// Only continue to emit phase if no pre-emit errors // Only continue to emit phase if no pre-emit errors
if (hasPreEmitErrors) { if (hasPreEmitErrors) {
console.error('\n❌ TypeScript pre-emit checks failed. Please fix the issues listed above before proceeding.'); console.error('TypeScript pre-emit checks failed. Please fix the issues above.');
console.error(' Type errors must be resolved before the compiler can emit output files.\n');
process.exit(1); process.exit(1);
} }
@ -314,22 +193,10 @@ export class TsBuild {
const exitCode = emitResult.emitSkipped ? 1 : 0; const exitCode = emitResult.emitSkipped ? 1 : 0;
if (exitCode === 0) { if (exitCode === 0) {
console.log('\n✅ TypeScript emit succeeded!'); console.log('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); done.resolve(emitResult.emittedFiles);
} else { } else {
console.error('\n❌ TypeScript emit failed. Please investigate the errors listed above!'); console.error('TypeScript emit failed. Please investigate!');
console.error(' No output files have been generated.\n');
process.exit(exitCode); process.exit(exitCode);
} }
@ -340,8 +207,7 @@ export class TsBuild {
* Function to check if files can be emitted without actually emitting them * Function to check if files can be emitted without actually emitting them
*/ */
public async checkEmit(): Promise<boolean> { public async checkEmit(): Promise<boolean> {
const fileCount = this.fileNames.length; console.log(`Checking if ${this.fileNames.length} files can be emitted...`);
console.log(`\n🔍 Checking if ${fileCount} file${fileCount !== 1 ? 's' : ''} can be emitted...`);
// Create a program with noEmit option // Create a program with noEmit option
const program = this.createProgram({ const program = this.createProgram({
@ -360,42 +226,9 @@ export class TsBuild {
const success = !hasPreEmitErrors && !hasEmitErrors && !emitResult.emitSkipped; const success = !hasPreEmitErrors && !hasEmitErrors && !emitResult.emitSkipped;
if (success) { if (success) {
console.log('\n✅ TypeScript emit check passed! All files can be emitted successfully.'); console.log('TypeScript emit check passed! File can be emitted successfully.');
console.log(` ${fileCount} file${fileCount !== 1 ? 's' : ''} ${fileCount !== 1 ? 'are' : 'is'} ready to be compiled.\n`);
} else { } else {
console.error('\n❌ TypeScript emit check failed. Please fix the issues listed above.'); console.error('TypeScript emit check failed. Please fix the issues 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; return success;
@ -436,15 +269,3 @@ export const emitCheck = async (
const tsBuild = new TsBuild(fileNames, options, argvArg); const tsBuild = new TsBuild(fileNames, options, argvArg);
return tsBuild.checkEmit(); 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();
};

@ -39,9 +39,7 @@ export const runCli = async () => {
const patterns = argvArg._.slice(1); // Remove the first element which is 'emitcheck' const patterns = argvArg._.slice(1); // Remove the first element which is 'emitcheck'
if (patterns.length === 0) { if (patterns.length === 0) {
console.error('\n❌ Error: Please provide at least one TypeScript file path or glob pattern'); console.error('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); process.exit(1);
} }
@ -63,9 +61,9 @@ export const runCli = async () => {
: []; : [];
if (stringMatchedFiles.length === 0) { if (stringMatchedFiles.length === 0) {
console.warn(`⚠️ Warning: No files matched the pattern '${pattern}'`); console.warn(`Warning: No files matched the pattern '${pattern}'`);
} else { } else {
console.log(`📂 Found ${stringMatchedFiles.length} files matching pattern '${pattern}'`); console.log(`Found ${stringMatchedFiles.length} files matching pattern '${pattern}'`);
// Transform to absolute paths // Transform to absolute paths
const absoluteMatchedFiles = plugins.smartpath.transform.toAbsolute( const absoluteMatchedFiles = plugins.smartpath.transform.toAbsolute(
@ -77,7 +75,7 @@ export const runCli = async () => {
allFiles = allFiles.concat(absoluteMatchedFiles); allFiles = allFiles.concat(absoluteMatchedFiles);
} }
} catch (err) { } catch (err) {
console.error(`Error processing glob pattern '${pattern}': ${err}`); console.error(`Error processing glob pattern '${pattern}': ${err}`);
} }
} else { } else {
// Handle as direct file path // Handle as direct file path
@ -89,7 +87,7 @@ export const runCli = async () => {
await plugins.smartfile.fs.fileExists(filePath); await plugins.smartfile.fs.fileExists(filePath);
allFiles.push(filePath); allFiles.push(filePath);
} catch (err) { } catch (err) {
console.error(`Error: File not found: ${filePath}`); console.error(`Error: File not found: ${filePath}`);
process.exit(1); process.exit(1);
} }
} }
@ -99,12 +97,11 @@ export const runCli = async () => {
allFiles = allFiles.filter(file => file.endsWith('.ts') || file.endsWith('.tsx')); allFiles = allFiles.filter(file => file.endsWith('.ts') || file.endsWith('.tsx'));
if (allFiles.length === 0) { if (allFiles.length === 0) {
console.error('\n❌ Error: No TypeScript files found to check'); console.error('Error: No TypeScript files found to check');
console.error(' Please verify your file paths or glob patterns.\n');
process.exit(1); process.exit(1);
} }
console.log(`\n🔎 Found ${allFiles.length} TypeScript file${allFiles.length !== 1 ? 's' : ''} to check`); console.log(`Found ${allFiles.length} TypeScript files to check`);
// Process compiler options // Process compiler options
const compilerOptions = tsbuild.mergeCompilerOptions({}, argvArg); const compilerOptions = tsbuild.mergeCompilerOptions({}, argvArg);
@ -164,164 +161,13 @@ export const runCli = async () => {
const compilationCommandObject: { [key: string]: string } = {}; const compilationCommandObject: { [key: string]: string } = {};
const folderCount = sortedTsFolders.length; console.log(`compiling in this order:`);
console.log(`\n📂 TypeScript Folder Compilation Plan (${folderCount} folder${folderCount !== 1 ? 's' : ''})`); console.log(sortedTsFolders);
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) { for (const tsFolder of sortedTsFolders) {
compilationCommandObject[`./${tsFolder}/**/*.ts`] = `./dist_${tsFolder}`; compilationCommandObject[`./${tsFolder}/**/*.ts`] = `./dist_${tsFolder}`;
} }
await tsbuild.compileGlobStringObject(compilationCommandObject, {}, process.cwd(), argvArg); 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 no patterns provided, default to checking ts/**/* and then test/**/*
if (patterns.length === 0) {
console.log('\n🔬 Running default type checking sequence...\n');
// First check ts/**/* without skiplibcheck
console.log('📂 Checking ts/**/* files...');
const tsFiles = await plugins.smartfile.fs.listFileTree(process.cwd(), 'ts/**/*.ts');
const tsTsFiles = Array.isArray(tsFiles)
? tsFiles.filter((item): item is string => typeof item === 'string')
: [];
if (tsTsFiles.length > 0) {
console.log(` Found ${tsTsFiles.length} TypeScript files in ts/`);
const tsAbsoluteFiles = plugins.smartpath.transform.toAbsolute(
tsTsFiles,
process.cwd()
) as string[];
const tsCompilerOptions = tsbuild.mergeCompilerOptions({}, argvArg);
const tsSuccess = await tsbuild.checkTypes(tsAbsoluteFiles, tsCompilerOptions, argvArg);
if (!tsSuccess) {
console.error('❌ Type checking failed for ts/**/*');
process.exit(1);
}
console.log('✅ Type checking passed for ts/**/*\n');
} else {
console.log(' No TypeScript files found in ts/\n');
}
// Then check test/**/* with skiplibcheck
console.log('📂 Checking test/**/* files with --skiplibcheck...');
const testFiles = await plugins.smartfile.fs.listFileTree(process.cwd(), 'test/**/*.ts');
const testTsFiles = Array.isArray(testFiles)
? testFiles.filter((item): item is string => typeof item === 'string')
: [];
if (testTsFiles.length > 0) {
console.log(` Found ${testTsFiles.length} TypeScript files in test/`);
const testAbsoluteFiles = plugins.smartpath.transform.toAbsolute(
testTsFiles,
process.cwd()
) as string[];
// Create new argvArg with skiplibcheck for test files
const testArgvArg = { ...argvArg, skiplibcheck: true };
const testCompilerOptions = tsbuild.mergeCompilerOptions({}, testArgvArg);
const testSuccess = await tsbuild.checkTypes(testAbsoluteFiles, testCompilerOptions, testArgvArg);
if (!testSuccess) {
console.error('❌ Type checking failed for test/**/*');
process.exit(1);
}
console.log('✅ Type checking passed for test/**/*\n');
} else {
console.log(' No TypeScript files found in test/\n');
}
console.log('✅ All default type checks passed!\n');
process.exit(0);
}
const cwd = process.cwd();
let allFiles: string[] = [];
// Process each pattern - could be a direct file path or a glob pattern
for (const pattern of patterns) {
// Check if the pattern looks like a glob pattern
if (pattern.includes('*') || pattern.includes('{') || pattern.includes('?')) {
// Handle as glob pattern
console.log(`Processing glob pattern: ${pattern}`);
try {
const matchedFiles = await plugins.smartfile.fs.listFileTree(cwd, pattern);
// Ensure matchedFiles contains only strings
const stringMatchedFiles = Array.isArray(matchedFiles)
? matchedFiles.filter((item): item is string => typeof item === 'string')
: [];
if (stringMatchedFiles.length === 0) {
console.warn(`⚠️ Warning: No files matched the pattern '${pattern}'`);
} else {
console.log(`📂 Found ${stringMatchedFiles.length} files matching pattern '${pattern}'`);
// Transform to absolute paths
const absoluteMatchedFiles = plugins.smartpath.transform.toAbsolute(
stringMatchedFiles,
cwd
) as string[];
// Add to the list of all files to check
allFiles = allFiles.concat(absoluteMatchedFiles);
}
} catch (err) {
console.error(`❌ Error processing glob pattern '${pattern}': ${err}`);
}
} else {
// Handle as direct file path
const filePath = plugins.path.isAbsolute(pattern)
? pattern
: plugins.path.join(cwd, pattern);
try {
await plugins.smartfile.fs.fileExists(filePath);
allFiles.push(filePath);
} catch (err) {
console.error(`❌ Error: File not found: ${filePath}`);
process.exit(1);
}
}
}
// Filter to only TypeScript files
allFiles = allFiles.filter(file => file.endsWith('.ts') || file.endsWith('.tsx'));
if (allFiles.length === 0) {
console.error('\n❌ Error: No TypeScript files found to check');
console.error(' Please verify your file paths or glob patterns.\n');
process.exit(1);
}
console.log(`\n🔎 Found ${allFiles.length} TypeScript file${allFiles.length !== 1 ? 's' : ''} to check`);
// Process compiler options
const compilerOptions = tsbuild.mergeCompilerOptions({}, argvArg);
// Run type check without emitting
const success = await tsbuild.checkTypes(allFiles, compilerOptions, argvArg);
// Exit with appropriate code
process.exit(success ? 0 : 1);
});
tsbuildCli.startParse(); tsbuildCli.startParse();
}; };

@ -1,6 +1,6 @@
import * as plugins from './plugins.js'; import * as plugins from './plugins.js';
import type { CompilerOptions, ScriptTarget, ModuleKind } from 'typescript'; import type { CompilerOptions, ScriptTarget, ModuleKind } from 'typescript';
import { compiler, mergeCompilerOptions, emitCheck, checkTypes } from './tsbuild.classes.tsbuild.js'; import { compiler, mergeCompilerOptions, emitCheck } from './tsbuild.classes.tsbuild.js';
export type { CompilerOptions, ScriptTarget, ModuleKind }; export type { CompilerOptions, ScriptTarget, ModuleKind };
@ -32,16 +32,12 @@ export let compileGlobStringObject = async (
) => { ) => {
let compiledFiles: 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) { for (const keyArg in globStringObjectArg) {
// Type safety check for key // Type safety check for key
if (keyArg && typeof keyArg === 'string' && globStringObjectArg[keyArg]) { if (keyArg && typeof keyArg === 'string' && globStringObjectArg[keyArg]) {
console.log(
`TypeScript assignment: transpile from ${keyArg} to ${globStringObjectArg[keyArg]}`
);
// Get files matching the glob pattern // Get files matching the glob pattern
const fileTreeArray = await plugins.smartfile.fs.listFileTree(cwdArg, keyArg); const fileTreeArray = await plugins.smartfile.fs.listFileTree(cwdArg, keyArg);