203 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			203 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import { TsTest } from './tstest.classes.tstest.js';
 | ||
| import type { LogOptions } from './tstest.logging.js';
 | ||
| 
 | ||
| export enum TestExecutionMode {
 | ||
|   DIRECTORY = 'directory',
 | ||
|   FILE = 'file',
 | ||
|   GLOB = 'glob'
 | ||
| }
 | ||
| 
 | ||
| export const runCli = async () => {
 | ||
|   // Check if we're using global tstest in the tstest project itself
 | ||
|   try {
 | ||
|     const packageJsonPath = `${process.cwd()}/package.json`;
 | ||
|     const fs = await import('fs');
 | ||
|     if (fs.existsSync(packageJsonPath)) {
 | ||
|       const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
 | ||
|       if (packageJson.name === '@git.zone/tstest') {
 | ||
|         // Check if we're running from a global installation
 | ||
|         const execPath = process.argv[1];
 | ||
|         // Debug: log the paths (uncomment for debugging)
 | ||
|         // console.log('DEBUG: Checking global tstest usage...');
 | ||
|         // console.log('execPath:', execPath);
 | ||
|         // console.log('cwd:', process.cwd());
 | ||
|         // console.log('process.argv:', process.argv);
 | ||
|         
 | ||
|         // Check if this is running from global installation
 | ||
|         const isLocalCli = execPath.includes(process.cwd());
 | ||
|         const isGlobalPnpm = process.argv.some(arg => arg.includes('.pnpm') && !arg.includes(process.cwd()));
 | ||
|         const isGlobalNpm = process.argv.some(arg => arg.includes('npm/node_modules') && !arg.includes(process.cwd()));
 | ||
|         
 | ||
|         if (!isLocalCli && (isGlobalPnpm || isGlobalNpm || !execPath.includes('node_modules'))) {
 | ||
|           console.error('\n⚠️  WARNING: You are using a globally installed tstest in the tstest project itself!');
 | ||
|           console.error('   This means you are NOT testing your local changes.');
 | ||
|           console.error('   Please use one of these commands instead:');
 | ||
|           console.error('     • node cli.js <test-path>');
 | ||
|           console.error('     • pnpm test <test-path>');
 | ||
|           console.error('     • ./cli.js <test-path> (if executable)\n');
 | ||
|         }
 | ||
|       }
 | ||
|     }
 | ||
|   } catch (error) {
 | ||
|     // Silently ignore any errors in this check
 | ||
|   }
 | ||
|   
 | ||
|   // Parse command line arguments
 | ||
|   const args = process.argv.slice(2);
 | ||
|   const logOptions: LogOptions = {};
 | ||
|   let testPath: string | null = null;
 | ||
|   let tags: string[] = [];
 | ||
|   let startFromFile: number | null = null;
 | ||
|   let stopAtFile: number | null = null;
 | ||
|   let timeoutSeconds: number | null = null;
 | ||
|   let watchMode: boolean = false;
 | ||
|   let watchIgnorePatterns: string[] = [];
 | ||
|   
 | ||
|   // Parse options
 | ||
|   for (let i = 0; i < args.length; i++) {
 | ||
|     const arg = args[i];
 | ||
|     
 | ||
|     switch (arg) {
 | ||
|       case '--version':
 | ||
|         // Get version from package.json
 | ||
|         try {
 | ||
|           const fs = await import('fs');
 | ||
|           const packagePath = new URL('../package.json', import.meta.url).pathname;
 | ||
|           const packageData = JSON.parse(await fs.promises.readFile(packagePath, 'utf8'));
 | ||
|           console.log(`tstest version ${packageData.version}`);
 | ||
|         } catch (error) {
 | ||
|           console.log('tstest version unknown');
 | ||
|         }
 | ||
|         process.exit(0);
 | ||
|         break;
 | ||
|       case '--quiet':
 | ||
|       case '-q':
 | ||
|         logOptions.quiet = true;
 | ||
|         break;
 | ||
|       case '--verbose':
 | ||
|       case '-v':
 | ||
|         logOptions.verbose = true;
 | ||
|         break;
 | ||
|       case '--no-color':
 | ||
|         logOptions.noColor = true;
 | ||
|         break;
 | ||
|       case '--json':
 | ||
|         logOptions.json = true;
 | ||
|         break;
 | ||
|       case '--log-file':
 | ||
|       case '--logfile':
 | ||
|         logOptions.logFile = true; // Set this as a flag, not a value
 | ||
|         break;
 | ||
|       case '--tags':
 | ||
|         if (i + 1 < args.length) {
 | ||
|           tags = args[++i].split(',');
 | ||
|         }
 | ||
|         break;
 | ||
|       case '--startFrom':
 | ||
|         if (i + 1 < args.length) {
 | ||
|           const value = parseInt(args[++i], 10);
 | ||
|           if (isNaN(value) || value < 1) {
 | ||
|             console.error('Error: --startFrom must be a positive integer');
 | ||
|             process.exit(1);
 | ||
|           }
 | ||
|           startFromFile = value;
 | ||
|         } else {
 | ||
|           console.error('Error: --startFrom requires a number argument');
 | ||
|           process.exit(1);
 | ||
|         }
 | ||
|         break;
 | ||
|       case '--stopAt':
 | ||
|         if (i + 1 < args.length) {
 | ||
|           const value = parseInt(args[++i], 10);
 | ||
|           if (isNaN(value) || value < 1) {
 | ||
|             console.error('Error: --stopAt must be a positive integer');
 | ||
|             process.exit(1);
 | ||
|           }
 | ||
|           stopAtFile = value;
 | ||
|         } else {
 | ||
|           console.error('Error: --stopAt requires a number argument');
 | ||
|           process.exit(1);
 | ||
|         }
 | ||
|         break;
 | ||
|       case '--timeout':
 | ||
|         if (i + 1 < args.length) {
 | ||
|           const value = parseInt(args[++i], 10);
 | ||
|           if (isNaN(value) || value < 1) {
 | ||
|             console.error('Error: --timeout must be a positive integer (seconds)');
 | ||
|             process.exit(1);
 | ||
|           }
 | ||
|           timeoutSeconds = value;
 | ||
|         } else {
 | ||
|           console.error('Error: --timeout requires a number argument (seconds)');
 | ||
|           process.exit(1);
 | ||
|         }
 | ||
|         break;
 | ||
|       case '--watch':
 | ||
|       case '-w':
 | ||
|         watchMode = true;
 | ||
|         break;
 | ||
|       case '--watch-ignore':
 | ||
|         if (i + 1 < args.length) {
 | ||
|           watchIgnorePatterns = args[++i].split(',');
 | ||
|         } else {
 | ||
|           console.error('Error: --watch-ignore requires a comma-separated list of patterns');
 | ||
|           process.exit(1);
 | ||
|         }
 | ||
|         break;
 | ||
|       default:
 | ||
|         if (!arg.startsWith('-')) {
 | ||
|           testPath = arg;
 | ||
|         }
 | ||
|     }
 | ||
|   }
 | ||
|   
 | ||
|   // Validate test file range options
 | ||
|   if (startFromFile !== null && stopAtFile !== null && startFromFile > stopAtFile) {
 | ||
|     console.error('Error: --startFrom cannot be greater than --stopAt');
 | ||
|     process.exit(1);
 | ||
|   }
 | ||
|   
 | ||
|   if (!testPath) {
 | ||
|     console.error('You must specify a test directory/file/pattern as argument. Please try again.');
 | ||
|     console.error('\nUsage: tstest <path> [options]');
 | ||
|     console.error('\nOptions:');
 | ||
|     console.error('  --version         Show version information');
 | ||
|     console.error('  --quiet, -q       Minimal output');
 | ||
|     console.error('  --verbose, -v     Verbose output');
 | ||
|     console.error('  --no-color        Disable colored output');
 | ||
|     console.error('  --json            Output results as JSON');
 | ||
|     console.error('  --logfile         Write logs to .nogit/testlogs/[testfile].log');
 | ||
|     console.error('  --tags <tags>     Run only tests with specified tags (comma-separated)');
 | ||
|     console.error('  --startFrom <n>   Start running from test file number n');
 | ||
|     console.error('  --stopAt <n>      Stop running at test file number n');
 | ||
|     console.error('  --timeout <s>     Timeout test files after s seconds');
 | ||
|     console.error('  --watch, -w       Watch for file changes and re-run tests');
 | ||
|     console.error('  --watch-ignore    Patterns to ignore in watch mode (comma-separated)');
 | ||
|     process.exit(1);
 | ||
|   }
 | ||
|   
 | ||
|   let executionMode: TestExecutionMode;
 | ||
|   
 | ||
|   // Detect execution mode based on the argument
 | ||
|   if (testPath.includes('*') || testPath.includes('?') || testPath.includes('[') || testPath.includes('{')) {
 | ||
|     executionMode = TestExecutionMode.GLOB;
 | ||
|   } else if (testPath.endsWith('.ts')) {
 | ||
|     executionMode = TestExecutionMode.FILE;
 | ||
|   } else {
 | ||
|     executionMode = TestExecutionMode.DIRECTORY;
 | ||
|   }
 | ||
|   
 | ||
|   const tsTestInstance = new TsTest(process.cwd(), testPath, executionMode, logOptions, tags, startFromFile, stopAtFile, timeoutSeconds);
 | ||
|   
 | ||
|   if (watchMode) {
 | ||
|     await tsTestInstance.runWatch(watchIgnorePatterns);
 | ||
|   } else {
 | ||
|     await tsTestInstance.run();
 | ||
|   }
 | ||
| };
 | ||
| 
 | ||
| // Execute CLI when this file is run directly
 | ||
| if (import.meta.url === `file://${process.argv[1]}`) {
 | ||
|   runCli();
 | ||
| }
 |