@boolesai/tspec-cli 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Command } from "commander";
2
2
  import ora from "ora";
3
- import { validateTestCase, parseTestCases, scheduler, registry } from "@boolesai/tspec";
3
+ import { getTypeFromFilePath, validateTestCase, clearTemplateCache, parseTestCases, scheduler, registry } from "@boolesai/tspec";
4
4
  import { glob } from "glob";
5
5
  import { isAbsolute, resolve, basename, relative } from "path";
6
6
  import { existsSync, statSync } from "fs";
@@ -40,7 +40,8 @@ async function discoverTSpecFiles(patterns, cwd) {
40
40
  const files = uniquePaths.map((filePath) => ({
41
41
  path: filePath,
42
42
  relativePath: relative(workingDir, filePath),
43
- fileName: basename(filePath)
43
+ fileName: basename(filePath),
44
+ protocol: getTypeFromFilePath(filePath)
44
45
  }));
45
46
  return { files, errors };
46
47
  }
@@ -223,8 +224,48 @@ function formatResult(result) {
223
224
  }))
224
225
  };
225
226
  }
227
+ async function runFileTestCases(descriptor, options, concurrency, spinner) {
228
+ const result = {
229
+ file: descriptor.path,
230
+ testCases: 0,
231
+ results: []
232
+ };
233
+ let testCases;
234
+ try {
235
+ testCases = parseTestCases(descriptor.path, {
236
+ env: options.env,
237
+ params: options.params
238
+ });
239
+ result.testCases = testCases.length;
240
+ } catch (err) {
241
+ result.parseError = err instanceof Error ? err.message : String(err);
242
+ return result;
243
+ }
244
+ if (testCases.length === 0) {
245
+ return result;
246
+ }
247
+ if (spinner) spinner.text = `Running: ${descriptor.relativePath} (${testCases.length} test(s))...`;
248
+ if (options.failFast) {
249
+ for (const testCase of testCases) {
250
+ try {
251
+ const scheduleResult = await scheduler.schedule([testCase], { concurrency: 1 });
252
+ result.results.push(...scheduleResult.results);
253
+ if (!scheduleResult.results[0]?.passed) {
254
+ break;
255
+ }
256
+ } catch {
257
+ break;
258
+ }
259
+ }
260
+ } else {
261
+ const scheduleResult = await scheduler.schedule(testCases, { concurrency });
262
+ result.results = scheduleResult.results;
263
+ }
264
+ return result;
265
+ }
226
266
  const runCommand = new Command("run").description("Execute test cases and report results").argument("<files...>", "Files or glob patterns to run").option("-o, --output <format>", "Output format: json, text", "text").option("-c, --concurrency <number>", "Max concurrent tests", "5").option("-e, --env <key=value>", "Environment variables", parseKeyValue$1, {}).option("-p, --params <key=value>", "Parameters", parseKeyValue$1, {}).option("-v, --verbose", "Verbose output").option("-q, --quiet", "Only output summary").option("--fail-fast", "Stop on first failure").action(async (files, options) => {
227
267
  setLoggerOptions({ verbose: options.verbose, quiet: options.quiet });
268
+ clearTemplateCache();
228
269
  const concurrency = parseInt(options.concurrency || "5", 10);
229
270
  const spinner = options.quiet ? null : ora("Discovering files...").start();
230
271
  try {
@@ -236,67 +277,40 @@ const runCommand = new Command("run").description("Execute test cases and report
236
277
  spinner?.fail("No .tspec files found");
237
278
  process.exit(2);
238
279
  }
239
- if (spinner) spinner.text = `Parsing ${fileDescriptors.length} file(s)...`;
240
- const allTestCases = [];
280
+ if (spinner) spinner.text = `Running ${fileDescriptors.length} file(s)...`;
281
+ const allResults = [];
241
282
  const parseErrors = [];
283
+ let totalTestCases = 0;
284
+ let stopped = false;
242
285
  for (const descriptor of fileDescriptors) {
243
- try {
244
- const testCases = parseTestCases(descriptor.path, {
245
- env: options.env,
246
- params: options.params
247
- });
248
- allTestCases.push(...testCases);
249
- } catch (err) {
250
- const message = err instanceof Error ? err.message : String(err);
251
- parseErrors.push({ file: descriptor.path, error: message });
252
- }
253
- }
254
- if (allTestCases.length === 0) {
255
- spinner?.fail("No test cases found");
256
- if (parseErrors.length > 0) {
257
- parseErrors.forEach(({ file, error: error2 }) => logger.error(` ${file}: ${error2}`));
286
+ if (stopped) break;
287
+ const fileResult = await runFileTestCases(descriptor, options, concurrency, spinner);
288
+ if (fileResult.parseError) {
289
+ parseErrors.push({ file: fileResult.file, error: fileResult.parseError });
290
+ continue;
258
291
  }
259
- process.exit(2);
260
- }
261
- if (spinner) spinner.text = `Running ${allTestCases.length} test(s) with concurrency ${concurrency}...`;
262
- let scheduleResult;
263
- if (options.failFast) {
264
- const results = [];
265
- let stopped = false;
266
- for (const testCase of allTestCases) {
267
- if (stopped) break;
268
- if (spinner) spinner.text = `Running: ${testCase.id}...`;
269
- try {
270
- const result = await scheduler.schedule([testCase], { concurrency: 1 });
271
- results.push(...result.results);
272
- if (!result.results[0]?.passed) {
273
- stopped = true;
274
- }
275
- } catch (err) {
276
- stopped = true;
277
- }
292
+ totalTestCases += fileResult.testCases;
293
+ allResults.push(...fileResult.results);
294
+ if (options.failFast && fileResult.results.some((r) => !r.passed)) {
295
+ stopped = true;
278
296
  }
279
- const passed = results.filter((r) => r.passed).length;
280
- const failed = results.length - passed;
281
- scheduleResult = {
282
- results,
283
- duration: 0,
284
- summary: {
285
- total: results.length,
286
- passed,
287
- failed,
288
- passRate: results.length > 0 ? passed / results.length * 100 : 0
289
- }
290
- };
291
- } else {
292
- scheduleResult = await scheduler.schedule(allTestCases, { concurrency });
293
297
  }
294
298
  spinner?.stop();
295
- const formattedResults = scheduleResult.results.map(formatResult);
299
+ if (totalTestCases === 0 && parseErrors.length === fileDescriptors.length) {
300
+ logger.error("No test cases found - all files failed to parse");
301
+ parseErrors.forEach(({ file, error: error2 }) => logger.error(` ${file}: ${error2}`));
302
+ process.exit(2);
303
+ }
304
+ const passed = allResults.filter((r) => r.passed).length;
305
+ const failed = allResults.length - passed;
296
306
  const summary = {
297
- ...scheduleResult.summary,
298
- duration: scheduleResult.duration
307
+ total: allResults.length,
308
+ passed,
309
+ failed,
310
+ passRate: allResults.length > 0 ? passed / allResults.length * 100 : 0,
311
+ duration: 0
299
312
  };
313
+ const formattedResults = allResults.map(formatResult);
300
314
  if (options.output === "json") {
301
315
  logger.log(formatJson({
302
316
  results: formattedResults,
@@ -322,7 +336,7 @@ const runCommand = new Command("run").description("Execute test cases and report
322
336
  }
323
337
  }
324
338
  }
325
- process.exit(scheduleResult.summary.failed > 0 ? 1 : 0);
339
+ process.exit(failed > 0 ? 1 : 0);
326
340
  } catch (err) {
327
341
  spinner?.fail("Execution failed");
328
342
  const message = err instanceof Error ? err.message : String(err);
@@ -339,6 +353,7 @@ function parseKeyValue(value, previous = {}) {
339
353
  }
340
354
  const parseCommand = new Command("parse").description("Parse and display test case information without execution").argument("<files...>", "Files or glob patterns to parse").option("-o, --output <format>", "Output format: json, text", "text").option("-v, --verbose", "Show detailed information").option("-q, --quiet", "Minimal output").option("-e, --env <key=value>", "Environment variables", parseKeyValue, {}).option("-p, --params <key=value>", "Parameters", parseKeyValue, {}).action(async (files, options) => {
341
355
  setLoggerOptions({ verbose: options.verbose, quiet: options.quiet });
356
+ clearTemplateCache();
342
357
  const spinner = options.quiet ? null : ora("Discovering files...").start();
343
358
  try {
344
359
  const { files: fileDescriptors, errors: resolveErrors } = await discoverTSpecFiles(files);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/utils/files.ts","../src/utils/formatter.ts","../src/utils/logger.ts","../src/commands/validate.ts","../src/commands/run.ts","../src/commands/parse.ts","../src/commands/list.ts","../src/index.ts"],"sourcesContent":["import { glob } from 'glob';\nimport { resolve, isAbsolute, relative, basename } from 'path';\nimport { existsSync, statSync } from 'fs';\n\nexport interface FileResolutionResult {\n files: string[];\n errors: string[];\n}\n\n/**\n * Represents a discovered .tspec file without loading its content.\n * Used for lazy file loading - files are scanned first, content read on-demand.\n */\nexport interface TSpecFileDescriptor {\n /** Absolute path to the file */\n path: string;\n /** Path relative to working directory */\n relativePath: string;\n /** Base filename (e.g., \"login.http.tspec\") */\n fileName: string;\n}\n\nexport interface DiscoveryResult {\n files: TSpecFileDescriptor[];\n errors: string[];\n}\n\nexport async function resolveFiles(patterns: string[], cwd?: string): Promise<FileResolutionResult> {\n const workingDir = cwd || process.cwd();\n const files: string[] = [];\n const errors: string[] = [];\n\n for (const pattern of patterns) {\n // Check if it's a direct file path\n const absolutePath = isAbsolute(pattern) ? pattern : resolve(workingDir, pattern);\n \n if (existsSync(absolutePath)) {\n const stat = statSync(absolutePath);\n if (stat.isFile()) {\n files.push(absolutePath);\n continue;\n } else if (stat.isDirectory()) {\n // If it's a directory, glob for .tspec files\n const dirFiles = await glob('**/*.tspec', { cwd: absolutePath, absolute: true });\n if (dirFiles.length === 0) {\n errors.push(`No .tspec files found in directory: ${pattern}`);\n } else {\n files.push(...dirFiles);\n }\n continue;\n }\n }\n\n // Treat as glob pattern\n const matches = await glob(pattern, { cwd: workingDir, absolute: true });\n if (matches.length === 0) {\n errors.push(`No files matched pattern: ${pattern}`);\n } else {\n files.push(...matches);\n }\n }\n\n // Deduplicate files\n const uniqueFiles = [...new Set(files)];\n\n return { files: uniqueFiles, errors };\n}\n\nexport function filterByExtension(files: string[], extension: string): string[] {\n return files.filter(f => f.endsWith(extension));\n}\n\nexport function getTspecFiles(files: string[]): string[] {\n return filterByExtension(files, '.tspec');\n}\n\n/**\n * Discovers .tspec files without loading their content.\n * Files are scanned and classified, content is read on-demand during validation/parsing/execution.\n * \n * @param patterns - File paths, directory paths, or glob patterns\n * @param cwd - Working directory for resolving relative paths\n * @returns DiscoveryResult with file descriptors and any errors\n */\nexport async function discoverTSpecFiles(patterns: string[], cwd?: string): Promise<DiscoveryResult> {\n const workingDir = cwd || process.cwd();\n const filePaths: string[] = [];\n const errors: string[] = [];\n\n for (const pattern of patterns) {\n // Check if it's a direct file path\n const absolutePath = isAbsolute(pattern) ? pattern : resolve(workingDir, pattern);\n \n if (existsSync(absolutePath)) {\n const stat = statSync(absolutePath);\n if (stat.isFile()) {\n if (absolutePath.endsWith('.tspec')) {\n filePaths.push(absolutePath);\n }\n continue;\n } else if (stat.isDirectory()) {\n // If it's a directory, glob for .tspec files\n const dirFiles = await glob('**/*.tspec', { cwd: absolutePath, absolute: true });\n if (dirFiles.length === 0) {\n errors.push(`No .tspec files found in directory: ${pattern}`);\n } else {\n filePaths.push(...dirFiles);\n }\n continue;\n }\n }\n\n // Treat as glob pattern\n const matches = await glob(pattern, { cwd: workingDir, absolute: true });\n const tspecMatches = matches.filter(f => f.endsWith('.tspec'));\n if (tspecMatches.length === 0) {\n errors.push(`No .tspec files matched pattern: ${pattern}`);\n } else {\n filePaths.push(...tspecMatches);\n }\n }\n\n // Deduplicate and create descriptors\n const uniquePaths = [...new Set(filePaths)];\n const files: TSpecFileDescriptor[] = uniquePaths.map(filePath => ({\n path: filePath,\n relativePath: relative(workingDir, filePath),\n fileName: basename(filePath),\n }));\n\n return { files, errors };\n}\n","import chalk from 'chalk';\nimport type { ValidationResult } from '@boolesai/tspec';\n\nexport type OutputFormat = 'json' | 'text' | 'table';\n\nexport interface FormatOptions {\n format?: OutputFormat;\n verbose?: boolean;\n quiet?: boolean;\n}\n\nexport function formatJson(data: unknown): string {\n return JSON.stringify(data, null, 2);\n}\n\nexport function formatValidationResult(result: ValidationResult, filePath: string): string {\n if (result.valid) {\n return chalk.green(`✓ ${filePath}`);\n }\n const errors = result.errors.map(e => chalk.red(` - ${e}`)).join('\\n');\n return `${chalk.red(`✗ ${filePath}`)}\\n${errors}`;\n}\n\nexport function formatValidationResults(\n results: Array<{ file: string; result: ValidationResult }>,\n options: FormatOptions = {}\n): string {\n const { format = 'text' } = options;\n\n if (format === 'json') {\n return formatJson(results.map(r => ({\n file: r.file,\n valid: r.result.valid,\n errors: r.result.errors\n })));\n }\n\n const lines = results.map(r => formatValidationResult(r.result, r.file));\n const passed = results.filter(r => r.result.valid).length;\n const failed = results.length - passed;\n\n lines.push('');\n lines.push(chalk.bold(`Validation Summary: ${passed} passed, ${failed} failed`));\n\n return lines.join('\\n');\n}\n\nexport interface TestResultSummary {\n total: number;\n passed: number;\n failed: number;\n passRate: number;\n duration: number;\n}\n\nexport interface FormattedTestResult {\n testCaseId: string;\n passed: boolean;\n duration: number;\n assertions: Array<{\n passed: boolean;\n type: string;\n message: string;\n }>;\n}\n\nexport function formatTestResult(result: FormattedTestResult, verbose = false): string {\n const status = result.passed\n ? chalk.green('✓ PASS')\n : chalk.red('✗ FAIL');\n \n const duration = chalk.gray(`(${result.duration}ms)`);\n let output = `${status} ${result.testCaseId} ${duration}`;\n\n if (verbose || !result.passed) {\n const assertionLines = result.assertions\n .filter(a => verbose || !a.passed)\n .map(a => {\n const icon = a.passed ? chalk.green(' ✓') : chalk.red(' ✗');\n return `${icon} [${a.type}] ${a.message}`;\n });\n if (assertionLines.length > 0) {\n output += '\\n' + assertionLines.join('\\n');\n }\n }\n\n return output;\n}\n\nexport function formatTestSummary(summary: TestResultSummary): string {\n const passRate = summary.passRate.toFixed(1);\n const statusColor = summary.failed === 0 ? chalk.green : chalk.red;\n \n return [\n '',\n chalk.bold('─'.repeat(50)),\n chalk.bold('Test Summary'),\n ` Total: ${summary.total}`,\n ` ${chalk.green('Passed:')} ${summary.passed}`,\n ` ${chalk.red('Failed:')} ${summary.failed}`,\n ` Pass Rate: ${statusColor(passRate + '%')}`,\n ` Duration: ${summary.duration}ms`,\n chalk.bold('─'.repeat(50))\n ].join('\\n');\n}\n\nexport function formatTestResults(\n results: FormattedTestResult[],\n summary: TestResultSummary,\n options: FormatOptions = {}\n): string {\n const { format = 'text', verbose = false } = options;\n\n if (format === 'json') {\n return formatJson({ results, summary });\n }\n\n const lines = results.map(r => formatTestResult(r, verbose));\n lines.push(formatTestSummary(summary));\n\n return lines.join('\\n');\n}\n\nexport function formatProtocolList(protocols: string[], options: FormatOptions = {}): string {\n const { format = 'text' } = options;\n\n if (format === 'json') {\n return formatJson({ protocols });\n }\n\n return [\n chalk.bold('Supported Protocols:'),\n ...protocols.map(p => ` - ${p}`)\n ].join('\\n');\n}\n\nexport function formatParsedTestCase(testCase: unknown, options: FormatOptions = {}): string {\n const { format = 'text' } = options;\n\n if (format === 'json') {\n return formatJson(testCase);\n }\n\n // For text format, show a simplified view\n const tc = testCase as Record<string, unknown>;\n const lines: string[] = [];\n \n lines.push(chalk.bold(`Test Case: ${tc.id || 'unknown'}`));\n if (tc.description) lines.push(` Description: ${tc.description}`);\n if (tc.type) lines.push(` Protocol: ${tc.type}`);\n \n return lines.join('\\n');\n}\n","import chalk from 'chalk';\n\nexport type LogLevel = 'debug' | 'info' | 'success' | 'warn' | 'error';\n\nexport interface LoggerOptions {\n verbose?: boolean;\n quiet?: boolean;\n}\n\nlet globalOptions: LoggerOptions = {};\n\nexport function setLoggerOptions(options: LoggerOptions): void {\n globalOptions = { ...globalOptions, ...options };\n}\n\nexport function debug(message: string, ...args: unknown[]): void {\n if (globalOptions.verbose && !globalOptions.quiet) {\n console.log(chalk.gray(`[debug] ${message}`), ...args);\n }\n}\n\nexport function info(message: string, ...args: unknown[]): void {\n if (!globalOptions.quiet) {\n console.log(chalk.blue(message), ...args);\n }\n}\n\nexport function success(message: string, ...args: unknown[]): void {\n if (!globalOptions.quiet) {\n console.log(chalk.green(message), ...args);\n }\n}\n\nexport function warn(message: string, ...args: unknown[]): void {\n console.warn(chalk.yellow(`[warn] ${message}`), ...args);\n}\n\nexport function error(message: string, ...args: unknown[]): void {\n console.error(chalk.red(`[error] ${message}`), ...args);\n}\n\nexport function log(message: string, ...args: unknown[]): void {\n console.log(message, ...args);\n}\n\nexport function newline(): void {\n if (!globalOptions.quiet) {\n console.log();\n }\n}\n\nexport const logger = {\n debug,\n info,\n success,\n warn,\n error,\n log,\n newline,\n setOptions: setLoggerOptions\n};\n","import { Command } from 'commander';\nimport ora from 'ora';\nimport { validateTestCase } from '@boolesai/tspec';\nimport { discoverTSpecFiles } from '../utils/files.js';\nimport { formatValidationResults } from '../utils/formatter.js';\nimport { logger, setLoggerOptions } from '../utils/logger.js';\nimport type { OutputFormat } from '../utils/formatter.js';\n\ninterface ValidateOptions {\n output?: OutputFormat;\n quiet?: boolean;\n}\n\nexport const validateCommand = new Command('validate')\n .description('Validate .tspec files for schema correctness')\n .argument('<files...>', 'Files or glob patterns to validate')\n .option('-o, --output <format>', 'Output format: json, text', 'text')\n .option('-q, --quiet', 'Only output errors')\n .action(async (files: string[], options: ValidateOptions) => {\n setLoggerOptions({ quiet: options.quiet });\n \n const spinner = options.quiet ? null : ora('Discovering files...').start();\n \n try {\n // Discover files without loading content\n const { files: fileDescriptors, errors: resolveErrors } = await discoverTSpecFiles(files);\n \n if (resolveErrors.length > 0 && !options.quiet) {\n resolveErrors.forEach(err => logger.warn(err));\n }\n \n if (fileDescriptors.length === 0) {\n spinner?.fail('No .tspec files found');\n process.exit(2);\n }\n \n if (spinner) spinner.text = `Validating ${fileDescriptors.length} file(s)...`;\n \n // Validate each file individually (lazy loading - content read on-demand)\n const results = fileDescriptors.map(descriptor => ({\n file: descriptor.path,\n result: validateTestCase(descriptor.path)\n }));\n \n spinner?.stop();\n \n const output = formatValidationResults(results, { format: options.output });\n logger.log(output);\n \n const hasErrors = results.some(r => !r.result.valid);\n process.exit(hasErrors ? 1 : 0);\n } catch (err) {\n spinner?.fail('Validation failed');\n const message = err instanceof Error ? err.message : String(err);\n logger.error(message);\n process.exit(2);\n }\n });\n","import { Command } from 'commander';\nimport ora from 'ora';\nimport chalk from 'chalk';\nimport { parseTestCases, scheduler } from '@boolesai/tspec';\nimport type { TestCase, TestResult, ScheduleResult } from '@boolesai/tspec';\nimport { discoverTSpecFiles } from '../utils/files.js';\nimport { formatTestResults, formatJson, type FormattedTestResult, type TestResultSummary } from '../utils/formatter.js';\nimport { logger, setLoggerOptions } from '../utils/logger.js';\nimport type { OutputFormat } from '../utils/formatter.js';\n\ninterface RunOptions {\n output?: OutputFormat;\n concurrency?: string;\n verbose?: boolean;\n quiet?: boolean;\n failFast?: boolean;\n env: Record<string, string>;\n params: Record<string, string>;\n}\n\nfunction parseKeyValue(value: string, previous: Record<string, string> = {}): Record<string, string> {\n const [key, val] = value.split('=');\n if (key && val !== undefined) {\n previous[key] = val;\n }\n return previous;\n}\n\nfunction formatResult(result: TestResult): FormattedTestResult {\n return {\n testCaseId: result.testCaseId,\n passed: result.passed,\n duration: result.duration,\n assertions: result.assertions.map(a => ({\n passed: a.passed,\n type: a.type,\n message: a.message\n }))\n };\n}\n\nexport const runCommand = new Command('run')\n .description('Execute test cases and report results')\n .argument('<files...>', 'Files or glob patterns to run')\n .option('-o, --output <format>', 'Output format: json, text', 'text')\n .option('-c, --concurrency <number>', 'Max concurrent tests', '5')\n .option('-e, --env <key=value>', 'Environment variables', parseKeyValue, {})\n .option('-p, --params <key=value>', 'Parameters', parseKeyValue, {})\n .option('-v, --verbose', 'Verbose output')\n .option('-q, --quiet', 'Only output summary')\n .option('--fail-fast', 'Stop on first failure')\n .action(async (files: string[], options: RunOptions) => {\n setLoggerOptions({ verbose: options.verbose, quiet: options.quiet });\n \n const concurrency = parseInt(options.concurrency || '5', 10);\n const spinner = options.quiet ? null : ora('Discovering files...').start();\n \n try {\n // Discover files without loading content\n const { files: fileDescriptors, errors: resolveErrors } = await discoverTSpecFiles(files);\n \n if (resolveErrors.length > 0 && !options.quiet) {\n resolveErrors.forEach(err => logger.warn(err));\n }\n \n if (fileDescriptors.length === 0) {\n spinner?.fail('No .tspec files found');\n process.exit(2);\n }\n \n // Parse test cases (lazy loading - content read on-demand per file)\n if (spinner) spinner.text = `Parsing ${fileDescriptors.length} file(s)...`;\n \n const allTestCases: TestCase[] = [];\n const parseErrors: Array<{ file: string; error: string }> = [];\n \n for (const descriptor of fileDescriptors) {\n try {\n const testCases = parseTestCases(descriptor.path, {\n env: options.env,\n params: options.params\n });\n allTestCases.push(...testCases);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n parseErrors.push({ file: descriptor.path, error: message });\n }\n }\n \n if (allTestCases.length === 0) {\n spinner?.fail('No test cases found');\n if (parseErrors.length > 0) {\n parseErrors.forEach(({ file, error }) => logger.error(` ${file}: ${error}`));\n }\n process.exit(2);\n }\n \n // Execute tests\n if (spinner) spinner.text = `Running ${allTestCases.length} test(s) with concurrency ${concurrency}...`;\n \n let scheduleResult: ScheduleResult;\n \n if (options.failFast) {\n // For fail-fast, execute sequentially and stop on first failure\n const results: TestResult[] = [];\n let stopped = false;\n \n for (const testCase of allTestCases) {\n if (stopped) break;\n \n if (spinner) spinner.text = `Running: ${testCase.id}...`;\n \n try {\n const result = await scheduler.schedule([testCase], { concurrency: 1 });\n results.push(...result.results);\n \n if (!result.results[0]?.passed) {\n stopped = true;\n }\n } catch (err) {\n stopped = true;\n }\n }\n \n const passed = results.filter(r => r.passed).length;\n const failed = results.length - passed;\n \n scheduleResult = {\n results,\n duration: 0,\n summary: {\n total: results.length,\n passed,\n failed,\n passRate: results.length > 0 ? (passed / results.length) * 100 : 0\n }\n };\n } else {\n scheduleResult = await scheduler.schedule(allTestCases, { concurrency });\n }\n \n spinner?.stop();\n \n // Format and output results\n const formattedResults = scheduleResult.results.map(formatResult);\n const summary: TestResultSummary = {\n ...scheduleResult.summary,\n duration: scheduleResult.duration\n };\n \n if (options.output === 'json') {\n logger.log(formatJson({\n results: formattedResults,\n summary,\n parseErrors\n }));\n } else {\n if (!options.quiet) {\n const output = formatTestResults(formattedResults, summary, {\n format: options.output,\n verbose: options.verbose\n });\n logger.log(output);\n } else {\n // Quiet mode: just show summary line\n const statusColor = summary.failed === 0 ? chalk.green : chalk.red;\n logger.log(statusColor(`${summary.passed}/${summary.total} tests passed (${summary.passRate.toFixed(1)}%)`));\n }\n \n if (parseErrors.length > 0) {\n logger.newline();\n logger.warn(`${parseErrors.length} file(s) failed to parse:`);\n for (const { file, error } of parseErrors) {\n logger.error(` ${file}: ${error}`);\n }\n }\n }\n \n // Exit code: 0 if all passed, 1 if any failed\n process.exit(scheduleResult.summary.failed > 0 ? 1 : 0);\n } catch (err) {\n spinner?.fail('Execution failed');\n const message = err instanceof Error ? err.message : String(err);\n logger.error(message);\n process.exit(2);\n }\n });\n","import { Command } from 'commander';\nimport ora from 'ora';\nimport { parseTestCases } from '@boolesai/tspec';\nimport { discoverTSpecFiles } from '../utils/files.js';\nimport { formatParsedTestCase, formatJson } from '../utils/formatter.js';\nimport { logger, setLoggerOptions } from '../utils/logger.js';\nimport type { OutputFormat } from '../utils/formatter.js';\n\ninterface ParseOptions {\n output?: OutputFormat;\n verbose?: boolean;\n quiet?: boolean;\n}\n\nfunction parseKeyValue(value: string, previous: Record<string, string> = {}): Record<string, string> {\n const [key, val] = value.split('=');\n if (key && val !== undefined) {\n previous[key] = val;\n }\n return previous;\n}\n\nexport const parseCommand = new Command('parse')\n .description('Parse and display test case information without execution')\n .argument('<files...>', 'Files or glob patterns to parse')\n .option('-o, --output <format>', 'Output format: json, text', 'text')\n .option('-v, --verbose', 'Show detailed information')\n .option('-q, --quiet', 'Minimal output')\n .option('-e, --env <key=value>', 'Environment variables', parseKeyValue, {})\n .option('-p, --params <key=value>', 'Parameters', parseKeyValue, {})\n .action(async (files: string[], options: ParseOptions & { env: Record<string, string>; params: Record<string, string> }) => {\n setLoggerOptions({ verbose: options.verbose, quiet: options.quiet });\n \n const spinner = options.quiet ? null : ora('Discovering files...').start();\n \n try {\n // Discover files without loading content\n const { files: fileDescriptors, errors: resolveErrors } = await discoverTSpecFiles(files);\n \n if (resolveErrors.length > 0 && !options.quiet) {\n resolveErrors.forEach(err => logger.warn(err));\n }\n \n if (fileDescriptors.length === 0) {\n spinner?.fail('No .tspec files found');\n process.exit(2);\n }\n \n if (spinner) spinner.text = `Parsing ${fileDescriptors.length} file(s)...`;\n \n const allTestCases: unknown[] = [];\n const parseErrors: Array<{ file: string; error: string }> = [];\n \n // Parse each file individually (lazy loading - content read on-demand)\n for (const descriptor of fileDescriptors) {\n try {\n const testCases = parseTestCases(descriptor.path, {\n env: options.env,\n params: options.params\n });\n allTestCases.push(...testCases);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n parseErrors.push({ file: descriptor.path, error: message });\n }\n }\n \n spinner?.stop();\n \n if (options.output === 'json') {\n logger.log(formatJson({\n testCases: allTestCases,\n errors: parseErrors,\n summary: {\n totalFiles: fileDescriptors.length,\n totalTestCases: allTestCases.length,\n parseErrors: parseErrors.length\n }\n }));\n } else {\n logger.info(`Parsed ${allTestCases.length} test case(s) from ${fileDescriptors.length} file(s)`);\n logger.newline();\n \n for (const testCase of allTestCases) {\n logger.log(formatParsedTestCase(testCase, { format: options.output, verbose: options.verbose }));\n logger.newline();\n }\n \n if (parseErrors.length > 0) {\n logger.newline();\n logger.warn(`${parseErrors.length} file(s) failed to parse:`);\n for (const { file, error } of parseErrors) {\n logger.error(` ${file}: ${error}`);\n }\n }\n }\n \n process.exit(parseErrors.length > 0 ? 1 : 0);\n } catch (err) {\n spinner?.fail('Parse failed');\n const message = err instanceof Error ? err.message : String(err);\n logger.error(message);\n process.exit(2);\n }\n });\n","import { Command } from 'commander';\nimport { registry } from '@boolesai/tspec';\nimport { formatProtocolList } from '../utils/formatter.js';\nimport { logger } from '../utils/logger.js';\nimport type { OutputFormat } from '../utils/formatter.js';\n\ninterface ListOptions {\n output?: OutputFormat;\n}\n\nexport const listCommand = new Command('list')\n .description('List supported protocols and configuration')\n .option('-o, --output <format>', 'Output format: json, text', 'text')\n .action(async (options: ListOptions) => {\n try {\n const protocols = registry.getRegisteredTypes();\n const output = formatProtocolList(protocols, { format: options.output });\n logger.log(output);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n logger.error(`Failed to list protocols: ${message}`);\n process.exit(2);\n }\n });\n","import { Command } from 'commander';\nimport { validateCommand } from './commands/validate.js';\nimport { runCommand } from './commands/run.js';\nimport { parseCommand } from './commands/parse.js';\nimport { listCommand } from './commands/list.js';\n\nconst program = new Command();\n\nprogram\n .name('tspec')\n .description('CLI for @boolesai/tspec testing framework')\n .version('1.0.0');\n\nprogram.addCommand(validateCommand);\nprogram.addCommand(runCommand);\nprogram.addCommand(parseCommand);\nprogram.addCommand(listCommand);\n\nprogram.parse();\n"],"names":["parseKeyValue","error"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/utils/files.ts","../src/utils/formatter.ts","../src/utils/logger.ts","../src/commands/validate.ts","../src/commands/run.ts","../src/commands/parse.ts","../src/commands/list.ts","../src/index.ts"],"sourcesContent":["import { glob } from 'glob';\nimport { resolve, isAbsolute, relative, basename } from 'path';\nimport { existsSync, statSync } from 'fs';\nimport { getTypeFromFilePath } from '@boolesai/tspec';\n\nexport type ProtocolType = 'http' | 'grpc' | 'graphql' | 'websocket';\n\nexport interface FileResolutionResult {\n files: string[];\n errors: string[];\n}\n\n/**\n * Represents a discovered .tspec file without loading its content.\n * Used for lazy file loading - files are scanned first, content read on-demand.\n */\nexport interface TSpecFileDescriptor {\n /** Absolute path to the file */\n path: string;\n /** Path relative to working directory */\n relativePath: string;\n /** Base filename (e.g., \"login.http.tspec\") */\n fileName: string;\n /** Protocol type extracted from filename (e.g., \"http\" from \"login.http.tspec\") */\n protocol: ProtocolType | null;\n}\n\nexport interface DiscoveryResult {\n files: TSpecFileDescriptor[];\n errors: string[];\n}\n\nexport async function resolveFiles(patterns: string[], cwd?: string): Promise<FileResolutionResult> {\n const workingDir = cwd || process.cwd();\n const files: string[] = [];\n const errors: string[] = [];\n\n for (const pattern of patterns) {\n // Check if it's a direct file path\n const absolutePath = isAbsolute(pattern) ? pattern : resolve(workingDir, pattern);\n \n if (existsSync(absolutePath)) {\n const stat = statSync(absolutePath);\n if (stat.isFile()) {\n files.push(absolutePath);\n continue;\n } else if (stat.isDirectory()) {\n // If it's a directory, glob for .tspec files\n const dirFiles = await glob('**/*.tspec', { cwd: absolutePath, absolute: true });\n if (dirFiles.length === 0) {\n errors.push(`No .tspec files found in directory: ${pattern}`);\n } else {\n files.push(...dirFiles);\n }\n continue;\n }\n }\n\n // Treat as glob pattern\n const matches = await glob(pattern, { cwd: workingDir, absolute: true });\n if (matches.length === 0) {\n errors.push(`No files matched pattern: ${pattern}`);\n } else {\n files.push(...matches);\n }\n }\n\n // Deduplicate files\n const uniqueFiles = [...new Set(files)];\n\n return { files: uniqueFiles, errors };\n}\n\nexport function filterByExtension(files: string[], extension: string): string[] {\n return files.filter(f => f.endsWith(extension));\n}\n\nexport function getTspecFiles(files: string[]): string[] {\n return filterByExtension(files, '.tspec');\n}\n\n/**\n * Discovers .tspec files without loading their content.\n * Files are scanned and classified, content is read on-demand during validation/parsing/execution.\n * \n * @param patterns - File paths, directory paths, or glob patterns\n * @param cwd - Working directory for resolving relative paths\n * @returns DiscoveryResult with file descriptors and any errors\n */\nexport async function discoverTSpecFiles(patterns: string[], cwd?: string): Promise<DiscoveryResult> {\n const workingDir = cwd || process.cwd();\n const filePaths: string[] = [];\n const errors: string[] = [];\n\n for (const pattern of patterns) {\n // Check if it's a direct file path\n const absolutePath = isAbsolute(pattern) ? pattern : resolve(workingDir, pattern);\n \n if (existsSync(absolutePath)) {\n const stat = statSync(absolutePath);\n if (stat.isFile()) {\n if (absolutePath.endsWith('.tspec')) {\n filePaths.push(absolutePath);\n }\n continue;\n } else if (stat.isDirectory()) {\n // If it's a directory, glob for .tspec files\n const dirFiles = await glob('**/*.tspec', { cwd: absolutePath, absolute: true });\n if (dirFiles.length === 0) {\n errors.push(`No .tspec files found in directory: ${pattern}`);\n } else {\n filePaths.push(...dirFiles);\n }\n continue;\n }\n }\n\n // Treat as glob pattern\n const matches = await glob(pattern, { cwd: workingDir, absolute: true });\n const tspecMatches = matches.filter(f => f.endsWith('.tspec'));\n if (tspecMatches.length === 0) {\n errors.push(`No .tspec files matched pattern: ${pattern}`);\n } else {\n filePaths.push(...tspecMatches);\n }\n }\n\n // Deduplicate and create descriptors with protocol classification\n const uniquePaths = [...new Set(filePaths)];\n const files: TSpecFileDescriptor[] = uniquePaths.map(filePath => ({\n path: filePath,\n relativePath: relative(workingDir, filePath),\n fileName: basename(filePath),\n protocol: getTypeFromFilePath(filePath) as ProtocolType | null,\n }));\n\n return { files, errors };\n}\n","import chalk from 'chalk';\nimport type { ValidationResult } from '@boolesai/tspec';\n\nexport type OutputFormat = 'json' | 'text' | 'table';\n\nexport interface FormatOptions {\n format?: OutputFormat;\n verbose?: boolean;\n quiet?: boolean;\n}\n\nexport function formatJson(data: unknown): string {\n return JSON.stringify(data, null, 2);\n}\n\nexport function formatValidationResult(result: ValidationResult, filePath: string): string {\n if (result.valid) {\n return chalk.green(`✓ ${filePath}`);\n }\n const errors = result.errors.map(e => chalk.red(` - ${e}`)).join('\\n');\n return `${chalk.red(`✗ ${filePath}`)}\\n${errors}`;\n}\n\nexport function formatValidationResults(\n results: Array<{ file: string; result: ValidationResult }>,\n options: FormatOptions = {}\n): string {\n const { format = 'text' } = options;\n\n if (format === 'json') {\n return formatJson(results.map(r => ({\n file: r.file,\n valid: r.result.valid,\n errors: r.result.errors\n })));\n }\n\n const lines = results.map(r => formatValidationResult(r.result, r.file));\n const passed = results.filter(r => r.result.valid).length;\n const failed = results.length - passed;\n\n lines.push('');\n lines.push(chalk.bold(`Validation Summary: ${passed} passed, ${failed} failed`));\n\n return lines.join('\\n');\n}\n\nexport interface TestResultSummary {\n total: number;\n passed: number;\n failed: number;\n passRate: number;\n duration: number;\n}\n\nexport interface FormattedTestResult {\n testCaseId: string;\n passed: boolean;\n duration: number;\n assertions: Array<{\n passed: boolean;\n type: string;\n message: string;\n }>;\n}\n\nexport function formatTestResult(result: FormattedTestResult, verbose = false): string {\n const status = result.passed\n ? chalk.green('✓ PASS')\n : chalk.red('✗ FAIL');\n \n const duration = chalk.gray(`(${result.duration}ms)`);\n let output = `${status} ${result.testCaseId} ${duration}`;\n\n if (verbose || !result.passed) {\n const assertionLines = result.assertions\n .filter(a => verbose || !a.passed)\n .map(a => {\n const icon = a.passed ? chalk.green(' ✓') : chalk.red(' ✗');\n return `${icon} [${a.type}] ${a.message}`;\n });\n if (assertionLines.length > 0) {\n output += '\\n' + assertionLines.join('\\n');\n }\n }\n\n return output;\n}\n\nexport function formatTestSummary(summary: TestResultSummary): string {\n const passRate = summary.passRate.toFixed(1);\n const statusColor = summary.failed === 0 ? chalk.green : chalk.red;\n \n return [\n '',\n chalk.bold('─'.repeat(50)),\n chalk.bold('Test Summary'),\n ` Total: ${summary.total}`,\n ` ${chalk.green('Passed:')} ${summary.passed}`,\n ` ${chalk.red('Failed:')} ${summary.failed}`,\n ` Pass Rate: ${statusColor(passRate + '%')}`,\n ` Duration: ${summary.duration}ms`,\n chalk.bold('─'.repeat(50))\n ].join('\\n');\n}\n\nexport function formatTestResults(\n results: FormattedTestResult[],\n summary: TestResultSummary,\n options: FormatOptions = {}\n): string {\n const { format = 'text', verbose = false } = options;\n\n if (format === 'json') {\n return formatJson({ results, summary });\n }\n\n const lines = results.map(r => formatTestResult(r, verbose));\n lines.push(formatTestSummary(summary));\n\n return lines.join('\\n');\n}\n\nexport function formatProtocolList(protocols: string[], options: FormatOptions = {}): string {\n const { format = 'text' } = options;\n\n if (format === 'json') {\n return formatJson({ protocols });\n }\n\n return [\n chalk.bold('Supported Protocols:'),\n ...protocols.map(p => ` - ${p}`)\n ].join('\\n');\n}\n\nexport function formatParsedTestCase(testCase: unknown, options: FormatOptions = {}): string {\n const { format = 'text' } = options;\n\n if (format === 'json') {\n return formatJson(testCase);\n }\n\n // For text format, show a simplified view\n const tc = testCase as Record<string, unknown>;\n const lines: string[] = [];\n \n lines.push(chalk.bold(`Test Case: ${tc.id || 'unknown'}`));\n if (tc.description) lines.push(` Description: ${tc.description}`);\n if (tc.type) lines.push(` Protocol: ${tc.type}`);\n \n return lines.join('\\n');\n}\n","import chalk from 'chalk';\n\nexport type LogLevel = 'debug' | 'info' | 'success' | 'warn' | 'error';\n\nexport interface LoggerOptions {\n verbose?: boolean;\n quiet?: boolean;\n}\n\nlet globalOptions: LoggerOptions = {};\n\nexport function setLoggerOptions(options: LoggerOptions): void {\n globalOptions = { ...globalOptions, ...options };\n}\n\nexport function debug(message: string, ...args: unknown[]): void {\n if (globalOptions.verbose && !globalOptions.quiet) {\n console.log(chalk.gray(`[debug] ${message}`), ...args);\n }\n}\n\nexport function info(message: string, ...args: unknown[]): void {\n if (!globalOptions.quiet) {\n console.log(chalk.blue(message), ...args);\n }\n}\n\nexport function success(message: string, ...args: unknown[]): void {\n if (!globalOptions.quiet) {\n console.log(chalk.green(message), ...args);\n }\n}\n\nexport function warn(message: string, ...args: unknown[]): void {\n console.warn(chalk.yellow(`[warn] ${message}`), ...args);\n}\n\nexport function error(message: string, ...args: unknown[]): void {\n console.error(chalk.red(`[error] ${message}`), ...args);\n}\n\nexport function log(message: string, ...args: unknown[]): void {\n console.log(message, ...args);\n}\n\nexport function newline(): void {\n if (!globalOptions.quiet) {\n console.log();\n }\n}\n\nexport const logger = {\n debug,\n info,\n success,\n warn,\n error,\n log,\n newline,\n setOptions: setLoggerOptions\n};\n","import { Command } from 'commander';\nimport ora from 'ora';\nimport { validateTestCase } from '@boolesai/tspec';\nimport { discoverTSpecFiles } from '../utils/files.js';\nimport { formatValidationResults } from '../utils/formatter.js';\nimport { logger, setLoggerOptions } from '../utils/logger.js';\nimport type { OutputFormat } from '../utils/formatter.js';\n\ninterface ValidateOptions {\n output?: OutputFormat;\n quiet?: boolean;\n}\n\nexport const validateCommand = new Command('validate')\n .description('Validate .tspec files for schema correctness')\n .argument('<files...>', 'Files or glob patterns to validate')\n .option('-o, --output <format>', 'Output format: json, text', 'text')\n .option('-q, --quiet', 'Only output errors')\n .action(async (files: string[], options: ValidateOptions) => {\n setLoggerOptions({ quiet: options.quiet });\n \n const spinner = options.quiet ? null : ora('Discovering files...').start();\n \n try {\n // Discover files without loading content\n const { files: fileDescriptors, errors: resolveErrors } = await discoverTSpecFiles(files);\n \n if (resolveErrors.length > 0 && !options.quiet) {\n resolveErrors.forEach(err => logger.warn(err));\n }\n \n if (fileDescriptors.length === 0) {\n spinner?.fail('No .tspec files found');\n process.exit(2);\n }\n \n if (spinner) spinner.text = `Validating ${fileDescriptors.length} file(s)...`;\n \n // Validate each file individually (lazy loading - content read on-demand)\n const results = fileDescriptors.map(descriptor => ({\n file: descriptor.path,\n result: validateTestCase(descriptor.path)\n }));\n \n spinner?.stop();\n \n const output = formatValidationResults(results, { format: options.output });\n logger.log(output);\n \n const hasErrors = results.some(r => !r.result.valid);\n process.exit(hasErrors ? 1 : 0);\n } catch (err) {\n spinner?.fail('Validation failed');\n const message = err instanceof Error ? err.message : String(err);\n logger.error(message);\n process.exit(2);\n }\n });\n","import { Command } from 'commander';\nimport ora from 'ora';\nimport chalk from 'chalk';\nimport { parseTestCases, scheduler, clearTemplateCache } from '@boolesai/tspec';\nimport type { TestCase, TestResult, ScheduleResult } from '@boolesai/tspec';\nimport { discoverTSpecFiles, type TSpecFileDescriptor } from '../utils/files.js';\nimport { formatTestResults, formatJson, type FormattedTestResult, type TestResultSummary } from '../utils/formatter.js';\nimport { logger, setLoggerOptions } from '../utils/logger.js';\nimport type { OutputFormat } from '../utils/formatter.js';\n\ninterface RunOptions {\n output?: OutputFormat;\n concurrency?: string;\n verbose?: boolean;\n quiet?: boolean;\n failFast?: boolean;\n env: Record<string, string>;\n params: Record<string, string>;\n}\n\ninterface FileRunResult {\n file: string;\n testCases: number;\n results: TestResult[];\n parseError?: string;\n}\n\nfunction parseKeyValue(value: string, previous: Record<string, string> = {}): Record<string, string> {\n const [key, val] = value.split('=');\n if (key && val !== undefined) {\n previous[key] = val;\n }\n return previous;\n}\n\nfunction formatResult(result: TestResult): FormattedTestResult {\n return {\n testCaseId: result.testCaseId,\n passed: result.passed,\n duration: result.duration,\n assertions: result.assertions.map(a => ({\n passed: a.passed,\n type: a.type,\n message: a.message\n }))\n };\n}\n\nasync function runFileTestCases(\n descriptor: TSpecFileDescriptor,\n options: RunOptions,\n concurrency: number,\n spinner: ReturnType<typeof ora> | null\n): Promise<FileRunResult> {\n const result: FileRunResult = {\n file: descriptor.path,\n testCases: 0,\n results: []\n };\n\n // Parse file on-demand\n let testCases: TestCase[];\n try {\n testCases = parseTestCases(descriptor.path, {\n env: options.env,\n params: options.params\n });\n result.testCases = testCases.length;\n } catch (err) {\n result.parseError = err instanceof Error ? err.message : String(err);\n return result;\n }\n\n if (testCases.length === 0) {\n return result;\n }\n\n // Execute test cases for this file\n if (spinner) spinner.text = `Running: ${descriptor.relativePath} (${testCases.length} test(s))...`;\n\n if (options.failFast) {\n // For fail-fast, execute sequentially\n for (const testCase of testCases) {\n try {\n const scheduleResult = await scheduler.schedule([testCase], { concurrency: 1 });\n result.results.push(...scheduleResult.results);\n \n if (!scheduleResult.results[0]?.passed) {\n break; // Stop on first failure within file\n }\n } catch {\n break;\n }\n }\n } else {\n // Execute all test cases for this file with concurrency\n const scheduleResult = await scheduler.schedule(testCases, { concurrency });\n result.results = scheduleResult.results;\n }\n\n return result;\n}\n\nexport const runCommand = new Command('run')\n .description('Execute test cases and report results')\n .argument('<files...>', 'Files or glob patterns to run')\n .option('-o, --output <format>', 'Output format: json, text', 'text')\n .option('-c, --concurrency <number>', 'Max concurrent tests', '5')\n .option('-e, --env <key=value>', 'Environment variables', parseKeyValue, {})\n .option('-p, --params <key=value>', 'Parameters', parseKeyValue, {})\n .option('-v, --verbose', 'Verbose output')\n .option('-q, --quiet', 'Only output summary')\n .option('--fail-fast', 'Stop on first failure')\n .action(async (files: string[], options: RunOptions) => {\n setLoggerOptions({ verbose: options.verbose, quiet: options.quiet });\n \n // Clear template cache to ensure fresh reads for this run\n clearTemplateCache();\n \n const concurrency = parseInt(options.concurrency || '5', 10);\n const spinner = options.quiet ? null : ora('Discovering files...').start();\n \n try {\n // Discover files without loading content\n const { files: fileDescriptors, errors: resolveErrors } = await discoverTSpecFiles(files);\n \n if (resolveErrors.length > 0 && !options.quiet) {\n resolveErrors.forEach(err => logger.warn(err));\n }\n \n if (fileDescriptors.length === 0) {\n spinner?.fail('No .tspec files found');\n process.exit(2);\n }\n \n if (spinner) spinner.text = `Running ${fileDescriptors.length} file(s)...`;\n \n // Parse and execute one file at a time (lazy loading)\n const allResults: TestResult[] = [];\n const parseErrors: Array<{ file: string; error: string }> = [];\n let totalTestCases = 0;\n let stopped = false;\n \n for (const descriptor of fileDescriptors) {\n if (stopped) break;\n \n const fileResult = await runFileTestCases(descriptor, options, concurrency, spinner);\n \n if (fileResult.parseError) {\n parseErrors.push({ file: fileResult.file, error: fileResult.parseError });\n continue;\n }\n \n totalTestCases += fileResult.testCases;\n allResults.push(...fileResult.results);\n \n // Check fail-fast across files\n if (options.failFast && fileResult.results.some(r => !r.passed)) {\n stopped = true;\n }\n }\n \n spinner?.stop();\n \n if (totalTestCases === 0 && parseErrors.length === fileDescriptors.length) {\n logger.error('No test cases found - all files failed to parse');\n parseErrors.forEach(({ file, error }) => logger.error(` ${file}: ${error}`));\n process.exit(2);\n }\n \n // Calculate summary\n const passed = allResults.filter(r => r.passed).length;\n const failed = allResults.length - passed;\n const summary: TestResultSummary = {\n total: allResults.length,\n passed,\n failed,\n passRate: allResults.length > 0 ? (passed / allResults.length) * 100 : 0,\n duration: 0\n };\n \n // Format and output results\n const formattedResults = allResults.map(formatResult);\n \n if (options.output === 'json') {\n logger.log(formatJson({\n results: formattedResults,\n summary,\n parseErrors\n }));\n } else {\n if (!options.quiet) {\n const output = formatTestResults(formattedResults, summary, {\n format: options.output,\n verbose: options.verbose\n });\n logger.log(output);\n } else {\n // Quiet mode: just show summary line\n const statusColor = summary.failed === 0 ? chalk.green : chalk.red;\n logger.log(statusColor(`${summary.passed}/${summary.total} tests passed (${summary.passRate.toFixed(1)}%)`));\n }\n \n if (parseErrors.length > 0) {\n logger.newline();\n logger.warn(`${parseErrors.length} file(s) failed to parse:`);\n for (const { file, error } of parseErrors) {\n logger.error(` ${file}: ${error}`);\n }\n }\n }\n \n // Exit code: 0 if all passed, 1 if any failed\n process.exit(failed > 0 ? 1 : 0);\n } catch (err) {\n spinner?.fail('Execution failed');\n const message = err instanceof Error ? err.message : String(err);\n logger.error(message);\n process.exit(2);\n }\n });\n","import { Command } from 'commander';\nimport ora from 'ora';\nimport { parseTestCases, clearTemplateCache } from '@boolesai/tspec';\nimport { discoverTSpecFiles } from '../utils/files.js';\nimport { formatParsedTestCase, formatJson } from '../utils/formatter.js';\nimport { logger, setLoggerOptions } from '../utils/logger.js';\nimport type { OutputFormat } from '../utils/formatter.js';\n\ninterface ParseOptions {\n output?: OutputFormat;\n verbose?: boolean;\n quiet?: boolean;\n}\n\nfunction parseKeyValue(value: string, previous: Record<string, string> = {}): Record<string, string> {\n const [key, val] = value.split('=');\n if (key && val !== undefined) {\n previous[key] = val;\n }\n return previous;\n}\n\nexport const parseCommand = new Command('parse')\n .description('Parse and display test case information without execution')\n .argument('<files...>', 'Files or glob patterns to parse')\n .option('-o, --output <format>', 'Output format: json, text', 'text')\n .option('-v, --verbose', 'Show detailed information')\n .option('-q, --quiet', 'Minimal output')\n .option('-e, --env <key=value>', 'Environment variables', parseKeyValue, {})\n .option('-p, --params <key=value>', 'Parameters', parseKeyValue, {})\n .action(async (files: string[], options: ParseOptions & { env: Record<string, string>; params: Record<string, string> }) => {\n setLoggerOptions({ verbose: options.verbose, quiet: options.quiet });\n \n // Clear template cache to ensure fresh reads for this parse\n clearTemplateCache();\n \n const spinner = options.quiet ? null : ora('Discovering files...').start();\n \n try {\n // Discover files without loading content\n const { files: fileDescriptors, errors: resolveErrors } = await discoverTSpecFiles(files);\n \n if (resolveErrors.length > 0 && !options.quiet) {\n resolveErrors.forEach(err => logger.warn(err));\n }\n \n if (fileDescriptors.length === 0) {\n spinner?.fail('No .tspec files found');\n process.exit(2);\n }\n \n if (spinner) spinner.text = `Parsing ${fileDescriptors.length} file(s)...`;\n \n const allTestCases: unknown[] = [];\n const parseErrors: Array<{ file: string; error: string }> = [];\n \n // Parse each file individually (lazy loading - content read on-demand)\n for (const descriptor of fileDescriptors) {\n try {\n const testCases = parseTestCases(descriptor.path, {\n env: options.env,\n params: options.params\n });\n allTestCases.push(...testCases);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n parseErrors.push({ file: descriptor.path, error: message });\n }\n }\n \n spinner?.stop();\n \n if (options.output === 'json') {\n logger.log(formatJson({\n testCases: allTestCases,\n errors: parseErrors,\n summary: {\n totalFiles: fileDescriptors.length,\n totalTestCases: allTestCases.length,\n parseErrors: parseErrors.length\n }\n }));\n } else {\n logger.info(`Parsed ${allTestCases.length} test case(s) from ${fileDescriptors.length} file(s)`);\n logger.newline();\n \n for (const testCase of allTestCases) {\n logger.log(formatParsedTestCase(testCase, { format: options.output, verbose: options.verbose }));\n logger.newline();\n }\n \n if (parseErrors.length > 0) {\n logger.newline();\n logger.warn(`${parseErrors.length} file(s) failed to parse:`);\n for (const { file, error } of parseErrors) {\n logger.error(` ${file}: ${error}`);\n }\n }\n }\n \n process.exit(parseErrors.length > 0 ? 1 : 0);\n } catch (err) {\n spinner?.fail('Parse failed');\n const message = err instanceof Error ? err.message : String(err);\n logger.error(message);\n process.exit(2);\n }\n });\n","import { Command } from 'commander';\nimport { registry } from '@boolesai/tspec';\nimport { formatProtocolList } from '../utils/formatter.js';\nimport { logger } from '../utils/logger.js';\nimport type { OutputFormat } from '../utils/formatter.js';\n\ninterface ListOptions {\n output?: OutputFormat;\n}\n\nexport const listCommand = new Command('list')\n .description('List supported protocols and configuration')\n .option('-o, --output <format>', 'Output format: json, text', 'text')\n .action(async (options: ListOptions) => {\n try {\n const protocols = registry.getRegisteredTypes();\n const output = formatProtocolList(protocols, { format: options.output });\n logger.log(output);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n logger.error(`Failed to list protocols: ${message}`);\n process.exit(2);\n }\n });\n","import { Command } from 'commander';\nimport { validateCommand } from './commands/validate.js';\nimport { runCommand } from './commands/run.js';\nimport { parseCommand } from './commands/parse.js';\nimport { listCommand } from './commands/list.js';\n\nconst program = new Command();\n\nprogram\n .name('tspec')\n .description('CLI for @boolesai/tspec testing framework')\n .version('1.0.0');\n\nprogram.addCommand(validateCommand);\nprogram.addCommand(runCommand);\nprogram.addCommand(parseCommand);\nprogram.addCommand(listCommand);\n\nprogram.parse();\n"],"names":["parseKeyValue","error"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boolesai/tspec-cli",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "CLI for @boolesai/tspec testing framework",
5
5
  "type": "module",
6
6
  "bin": {
@@ -22,7 +22,7 @@
22
22
  "prepublishOnly": "npm run build"
23
23
  },
24
24
  "dependencies": {
25
- "@boolesai/tspec": "^0.0.2",
25
+ "@boolesai/tspec": "^0.0.3",
26
26
  "chalk": "^5.0.0",
27
27
  "commander": "^12.0.0",
28
28
  "glob": "^11.0.0",
@@ -1,3 +1,4 @@
1
+ export type ProtocolType = 'http' | 'grpc' | 'graphql' | 'websocket';
1
2
  export interface FileResolutionResult {
2
3
  files: string[];
3
4
  errors: string[];
@@ -13,6 +14,8 @@ export interface TSpecFileDescriptor {
13
14
  relativePath: string;
14
15
  /** Base filename (e.g., "login.http.tspec") */
15
16
  fileName: string;
17
+ /** Protocol type extracted from filename (e.g., "http" from "login.http.tspec") */
18
+ protocol: ProtocolType | null;
16
19
  }
17
20
  export interface DiscoveryResult {
18
21
  files: TSpecFileDescriptor[];