feat(cli): Add emitcheck command to validate TS file emission without generating output
This commit is contained in:
		| @@ -1,5 +1,12 @@ | |||||||
| # Changelog | # 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) | ## 2025-03-17 - 2.2.7 - fix(compiler) | ||||||
| Improve diagnostic checking and error handling in the TypeScript compiler integration | 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' ] | [ '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 | ## 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: | ||||||
|   | |||||||
| @@ -3,6 +3,6 @@ | |||||||
|  */ |  */ | ||||||
| export const commitinfo = { | export const commitinfo = { | ||||||
|   name: '@git.zone/tsbuild', |   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.' |   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; |   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); |     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 |    * the custom command compiles any customDir to dist_customDir | ||||||
|    */ |    */ | ||||||
|   | |||||||
| @@ -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 } from './tsbuild.classes.compiler.js'; | import { compiler, mergeCompilerOptions, emitCheck } from './tsbuild.classes.compiler.js'; | ||||||
|  |  | ||||||
| export type { CompilerOptions, ScriptTarget, ModuleKind }; | export type { CompilerOptions, ScriptTarget, ModuleKind }; | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user