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();
|
||
}
|