feat(cli): Add emitcheck command to validate TS file emission without generating output
This commit is contained in:
		| @@ -1,5 +1,12 @@ | ||||
| # Changelog | ||||
|  | ||||
| ## 2025-03-20 - 2.3.0 - feat(cli) | ||||
| Add emitcheck command to validate TS file emission without generating output | ||||
|  | ||||
| - Implemented the emitcheck CLI command to allow checking if TypeScript files can be emitted without producing files | ||||
| - Updated tsbuild.classes.compiler.ts to include the emitCheck function | ||||
| - Enhanced documentation in readme.md to describe the new emitcheck usage with examples | ||||
|  | ||||
| ## 2025-03-17 - 2.2.7 - fix(compiler) | ||||
| Improve diagnostic checking and error handling in the TypeScript compiler integration | ||||
|  | ||||
|   | ||||
							
								
								
									
										25
									
								
								readme.md
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								readme.md
									
									
									
									
									
								
							| @@ -121,6 +121,31 @@ compiling in this order: | ||||
| [ 'ts_interfaces', 'ts_shared', 'ts_core', 'ts_utils', 'ts_modules' ] | ||||
| ``` | ||||
|  | ||||
| ### EmitCheck Command | ||||
|  | ||||
| Checks if TypeScript files can be emitted without actually emitting them: | ||||
|  | ||||
| ```bash | ||||
| npx tsbuild emitcheck <file_or_glob_pattern> [additional_patterns ...] | ||||
| ``` | ||||
|  | ||||
| This command: | ||||
| 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: | ||||
| ```bash | ||||
| npx tsbuild emitcheck src/main.ts src/utils.ts | ||||
| ``` | ||||
|  | ||||
| Example usage with glob patterns: | ||||
| ```bash | ||||
| npx tsbuild emitcheck "src/**/*.ts" "test/**/*.ts" | ||||
| ``` | ||||
|  | ||||
| ## Compiler Options | ||||
|  | ||||
| Additional flags can be passed to any command to modify the compilation behavior: | ||||
|   | ||||
| @@ -3,6 +3,6 @@ | ||||
|  */ | ||||
| export const commitinfo = { | ||||
|   name: '@git.zone/tsbuild', | ||||
|   version: '2.2.7', | ||||
|   version: '2.3.0', | ||||
|   description: 'A tool for compiling TypeScript files using the latest nightly features, offering flexible APIs and a CLI for streamlined development.' | ||||
| } | ||||
|   | ||||
| @@ -149,3 +149,67 @@ export const compiler = async ( | ||||
|  | ||||
|   return done.promise; | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * Function to check if a TypeScript file can be emitted without actually emitting it | ||||
|  */ | ||||
| export const emitCheck = async ( | ||||
|   fileNames: string[], | ||||
|   options: plugins.typescript.CompilerOptions = {}, | ||||
|   argvArg?: any | ||||
| ): Promise<boolean> => { | ||||
|   console.log(`Checking if ${fileNames.length} files can be emitted...`); | ||||
|    | ||||
|   // Create a program | ||||
|   const program = plugins.typescript.createProgram(fileNames, { | ||||
|     ...options, | ||||
|     noEmit: true | ||||
|   }); | ||||
|    | ||||
|   // Check for pre-emit diagnostics | ||||
|   const preEmitDiagnostics = plugins.typescript.getPreEmitDiagnostics(program); | ||||
|   let hasErrors = false; | ||||
|    | ||||
|   // Log pre-emit diagnostics if any | ||||
|   if (preEmitDiagnostics.length > 0) { | ||||
|     preEmitDiagnostics.forEach((diagnostic) => { | ||||
|       hasErrors = true; | ||||
|       if (diagnostic.file) { | ||||
|         const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start!); | ||||
|         const message = plugins.typescript.flattenDiagnosticMessageText(diagnostic.messageText, '\n'); | ||||
|         console.log(`${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`); | ||||
|       } else { | ||||
|         console.log( | ||||
|           `${plugins.typescript.flattenDiagnosticMessageText(diagnostic.messageText, '\n')}` | ||||
|         ); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
|    | ||||
|   // Run the emit phase but with noEmit: true to check for emit errors without producing files | ||||
|   const emitResult = program.emit(undefined, undefined, undefined, true); | ||||
|    | ||||
|   // Check for emit diagnostics | ||||
|   if (emitResult.diagnostics.length > 0) { | ||||
|     emitResult.diagnostics.forEach((diagnostic) => { | ||||
|       hasErrors = true; | ||||
|       if (diagnostic.file) { | ||||
|         const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start!); | ||||
|         const message = plugins.typescript.flattenDiagnosticMessageText(diagnostic.messageText, '\n'); | ||||
|         console.log(`${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`); | ||||
|       } else { | ||||
|         console.log( | ||||
|           `${plugins.typescript.flattenDiagnosticMessageText(diagnostic.messageText, '\n')}` | ||||
|         ); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
|    | ||||
|   if (!hasErrors && !emitResult.emitSkipped) { | ||||
|     console.log('TypeScript emit check passed! File can be emitted successfully.'); | ||||
|     return true; | ||||
|   } else { | ||||
|     console.error('TypeScript emit check failed. Please fix the issues above.'); | ||||
|     return false; | ||||
|   } | ||||
| }; | ||||
|   | ||||
| @@ -32,6 +32,87 @@ export const runCli = async () => { | ||||
|     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('Error: Please provide at least one TypeScript file path or glob pattern'); | ||||
|       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('Error: No TypeScript files found to check'); | ||||
|       process.exit(1); | ||||
|     } | ||||
|      | ||||
|     console.log(`Found ${allFiles.length} TypeScript files 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 | ||||
|    */ | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import * as plugins from './plugins.js'; | ||||
| import type { CompilerOptions, ScriptTarget, ModuleKind } from 'typescript'; | ||||
| import { compiler, mergeCompilerOptions } from './tsbuild.classes.compiler.js'; | ||||
| import { compiler, mergeCompilerOptions, emitCheck } from './tsbuild.classes.compiler.js'; | ||||
|  | ||||
| export type { CompilerOptions, ScriptTarget, ModuleKind }; | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user