@boolesai/tspec-cli 0.0.1 → 0.0.3

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
@@ -2,45 +2,47 @@ import { Command } from "commander";
2
2
  import ora from "ora";
3
3
  import { validateTestCase, parseTestCases, scheduler, registry } from "@boolesai/tspec";
4
4
  import { glob } from "glob";
5
- import { isAbsolute, resolve } from "path";
5
+ import { isAbsolute, resolve, basename, relative } from "path";
6
6
  import { existsSync, statSync } from "fs";
7
7
  import chalk from "chalk";
8
- async function resolveFiles(patterns, cwd) {
8
+ async function discoverTSpecFiles(patterns, cwd) {
9
9
  const workingDir = process.cwd();
10
- const files = [];
10
+ const filePaths = [];
11
11
  const errors = [];
12
12
  for (const pattern of patterns) {
13
13
  const absolutePath = isAbsolute(pattern) ? pattern : resolve(workingDir, pattern);
14
14
  if (existsSync(absolutePath)) {
15
15
  const stat = statSync(absolutePath);
16
16
  if (stat.isFile()) {
17
- files.push(absolutePath);
17
+ if (absolutePath.endsWith(".tspec")) {
18
+ filePaths.push(absolutePath);
19
+ }
18
20
  continue;
19
21
  } else if (stat.isDirectory()) {
20
22
  const dirFiles = await glob("**/*.tspec", { cwd: absolutePath, absolute: true });
21
23
  if (dirFiles.length === 0) {
22
24
  errors.push(`No .tspec files found in directory: ${pattern}`);
23
25
  } else {
24
- files.push(...dirFiles);
26
+ filePaths.push(...dirFiles);
25
27
  }
26
28
  continue;
27
29
  }
28
30
  }
29
31
  const matches = await glob(pattern, { cwd: workingDir, absolute: true });
30
- if (matches.length === 0) {
31
- errors.push(`No files matched pattern: ${pattern}`);
32
+ const tspecMatches = matches.filter((f) => f.endsWith(".tspec"));
33
+ if (tspecMatches.length === 0) {
34
+ errors.push(`No .tspec files matched pattern: ${pattern}`);
32
35
  } else {
33
- files.push(...matches);
36
+ filePaths.push(...tspecMatches);
34
37
  }
35
38
  }
36
- const uniqueFiles = [...new Set(files)];
37
- return { files: uniqueFiles, errors };
38
- }
39
- function filterByExtension(files, extension) {
40
- return files.filter((f) => f.endsWith(extension));
41
- }
42
- function getTspecFiles(files) {
43
- return filterByExtension(files, ".tspec");
39
+ const uniquePaths = [...new Set(filePaths)];
40
+ const files = uniquePaths.map((filePath) => ({
41
+ path: filePath,
42
+ relativePath: relative(workingDir, filePath),
43
+ fileName: basename(filePath)
44
+ }));
45
+ return { files, errors };
44
46
  }
45
47
  function formatJson(data) {
46
48
  return JSON.stringify(data, null, 2);
@@ -175,21 +177,20 @@ const logger = {
175
177
  };
176
178
  const validateCommand = new Command("validate").description("Validate .tspec files for schema correctness").argument("<files...>", "Files or glob patterns to validate").option("-o, --output <format>", "Output format: json, text", "text").option("-q, --quiet", "Only output errors").action(async (files, options) => {
177
179
  setLoggerOptions({ quiet: options.quiet });
178
- const spinner = options.quiet ? null : ora("Resolving files...").start();
180
+ const spinner = options.quiet ? null : ora("Discovering files...").start();
179
181
  try {
180
- const { files: resolvedFiles, errors: resolveErrors } = await resolveFiles(files);
181
- const tspecFiles = getTspecFiles(resolvedFiles);
182
+ const { files: fileDescriptors, errors: resolveErrors } = await discoverTSpecFiles(files);
182
183
  if (resolveErrors.length > 0 && !options.quiet) {
183
184
  resolveErrors.forEach((err) => logger.warn(err));
184
185
  }
185
- if (tspecFiles.length === 0) {
186
+ if (fileDescriptors.length === 0) {
186
187
  spinner?.fail("No .tspec files found");
187
188
  process.exit(2);
188
189
  }
189
- if (spinner) spinner.text = `Validating ${tspecFiles.length} file(s)...`;
190
- const results = tspecFiles.map((file) => ({
191
- file,
192
- result: validateTestCase(file)
190
+ if (spinner) spinner.text = `Validating ${fileDescriptors.length} file(s)...`;
191
+ const results = fileDescriptors.map((descriptor) => ({
192
+ file: descriptor.path,
193
+ result: validateTestCase(descriptor.path)
193
194
  }));
194
195
  spinner?.stop();
195
196
  const output = formatValidationResults(results, { format: options.output });
@@ -225,30 +226,29 @@ function formatResult(result) {
225
226
  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) => {
226
227
  setLoggerOptions({ verbose: options.verbose, quiet: options.quiet });
227
228
  const concurrency = parseInt(options.concurrency || "5", 10);
228
- const spinner = options.quiet ? null : ora("Resolving files...").start();
229
+ const spinner = options.quiet ? null : ora("Discovering files...").start();
229
230
  try {
230
- const { files: resolvedFiles, errors: resolveErrors } = await resolveFiles(files);
231
- const tspecFiles = getTspecFiles(resolvedFiles);
231
+ const { files: fileDescriptors, errors: resolveErrors } = await discoverTSpecFiles(files);
232
232
  if (resolveErrors.length > 0 && !options.quiet) {
233
233
  resolveErrors.forEach((err) => logger.warn(err));
234
234
  }
235
- if (tspecFiles.length === 0) {
235
+ if (fileDescriptors.length === 0) {
236
236
  spinner?.fail("No .tspec files found");
237
237
  process.exit(2);
238
238
  }
239
- if (spinner) spinner.text = `Parsing ${tspecFiles.length} file(s)...`;
239
+ if (spinner) spinner.text = `Parsing ${fileDescriptors.length} file(s)...`;
240
240
  const allTestCases = [];
241
241
  const parseErrors = [];
242
- for (const file of tspecFiles) {
242
+ for (const descriptor of fileDescriptors) {
243
243
  try {
244
- const testCases = parseTestCases(file, {
244
+ const testCases = parseTestCases(descriptor.path, {
245
245
  env: options.env,
246
246
  params: options.params
247
247
  });
248
248
  allTestCases.push(...testCases);
249
249
  } catch (err) {
250
250
  const message = err instanceof Error ? err.message : String(err);
251
- parseErrors.push({ file, error: message });
251
+ parseErrors.push({ file: descriptor.path, error: message });
252
252
  }
253
253
  }
254
254
  if (allTestCases.length === 0) {
@@ -339,30 +339,29 @@ function parseKeyValue(value, previous = {}) {
339
339
  }
340
340
  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
341
  setLoggerOptions({ verbose: options.verbose, quiet: options.quiet });
342
- const spinner = options.quiet ? null : ora("Resolving files...").start();
342
+ const spinner = options.quiet ? null : ora("Discovering files...").start();
343
343
  try {
344
- const { files: resolvedFiles, errors: resolveErrors } = await resolveFiles(files);
345
- const tspecFiles = getTspecFiles(resolvedFiles);
344
+ const { files: fileDescriptors, errors: resolveErrors } = await discoverTSpecFiles(files);
346
345
  if (resolveErrors.length > 0 && !options.quiet) {
347
346
  resolveErrors.forEach((err) => logger.warn(err));
348
347
  }
349
- if (tspecFiles.length === 0) {
348
+ if (fileDescriptors.length === 0) {
350
349
  spinner?.fail("No .tspec files found");
351
350
  process.exit(2);
352
351
  }
353
- if (spinner) spinner.text = `Parsing ${tspecFiles.length} file(s)...`;
352
+ if (spinner) spinner.text = `Parsing ${fileDescriptors.length} file(s)...`;
354
353
  const allTestCases = [];
355
354
  const parseErrors = [];
356
- for (const file of tspecFiles) {
355
+ for (const descriptor of fileDescriptors) {
357
356
  try {
358
- const testCases = parseTestCases(file, {
357
+ const testCases = parseTestCases(descriptor.path, {
359
358
  env: options.env,
360
359
  params: options.params
361
360
  });
362
361
  allTestCases.push(...testCases);
363
362
  } catch (err) {
364
363
  const message = err instanceof Error ? err.message : String(err);
365
- parseErrors.push({ file, error: message });
364
+ parseErrors.push({ file: descriptor.path, error: message });
366
365
  }
367
366
  }
368
367
  spinner?.stop();
@@ -371,13 +370,13 @@ const parseCommand = new Command("parse").description("Parse and display test ca
371
370
  testCases: allTestCases,
372
371
  errors: parseErrors,
373
372
  summary: {
374
- totalFiles: tspecFiles.length,
373
+ totalFiles: fileDescriptors.length,
375
374
  totalTestCases: allTestCases.length,
376
375
  parseErrors: parseErrors.length
377
376
  }
378
377
  }));
379
378
  } else {
380
- logger.info(`Parsed ${allTestCases.length} test case(s) from ${tspecFiles.length} file(s)`);
379
+ logger.info(`Parsed ${allTestCases.length} test case(s) from ${fileDescriptors.length} file(s)`);
381
380
  logger.newline();
382
381
  for (const testCase of allTestCases) {
383
382
  logger.log(formatParsedTestCase(testCase, { format: options.output, verbose: options.verbose }));
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 } from 'path';\nimport { existsSync, statSync } from 'fs';\n\nexport interface FileResolutionResult {\n files: string[];\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","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 { resolveFiles, getTspecFiles } 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('Resolving files...').start();\n \n try {\n const { files: resolvedFiles, errors: resolveErrors } = await resolveFiles(files);\n const tspecFiles = getTspecFiles(resolvedFiles);\n \n if (resolveErrors.length > 0 && !options.quiet) {\n resolveErrors.forEach(err => logger.warn(err));\n }\n \n if (tspecFiles.length === 0) {\n spinner?.fail('No .tspec files found');\n process.exit(2);\n }\n \n if (spinner) spinner.text = `Validating ${tspecFiles.length} file(s)...`;\n \n const results = tspecFiles.map(file => ({\n file,\n result: validateTestCase(file)\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 { resolveFiles, getTspecFiles } 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('Resolving files...').start();\n \n try {\n // Resolve files\n const { files: resolvedFiles, errors: resolveErrors } = await resolveFiles(files);\n const tspecFiles = getTspecFiles(resolvedFiles);\n \n if (resolveErrors.length > 0 && !options.quiet) {\n resolveErrors.forEach(err => logger.warn(err));\n }\n \n if (tspecFiles.length === 0) {\n spinner?.fail('No .tspec files found');\n process.exit(2);\n }\n \n // Parse test cases\n if (spinner) spinner.text = `Parsing ${tspecFiles.length} file(s)...`;\n \n const allTestCases: TestCase[] = [];\n const parseErrors: Array<{ file: string; error: string }> = [];\n \n for (const file of tspecFiles) {\n try {\n const testCases = parseTestCases(file, {\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, 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 { resolveFiles, getTspecFiles } 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('Resolving files...').start();\n \n try {\n const { files: resolvedFiles, errors: resolveErrors } = await resolveFiles(files);\n const tspecFiles = getTspecFiles(resolvedFiles);\n \n if (resolveErrors.length > 0 && !options.quiet) {\n resolveErrors.forEach(err => logger.warn(err));\n }\n \n if (tspecFiles.length === 0) {\n spinner?.fail('No .tspec files found');\n process.exit(2);\n }\n \n if (spinner) spinner.text = `Parsing ${tspecFiles.length} file(s)...`;\n \n const allTestCases: unknown[] = [];\n const parseErrors: Array<{ file: string; error: string }> = [];\n \n for (const file of tspecFiles) {\n try {\n const testCases = parseTestCases(file, {\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, 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: tspecFiles.length,\n totalTestCases: allTestCases.length,\n parseErrors: parseErrors.length\n }\n }));\n } else {\n logger.info(`Parsed ${allTestCases.length} test case(s) from ${tspecFiles.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":";;;;;;;AASA,eAAsB,aAAa,UAAoB,KAA6C;AAClG,QAAM,aAAoB,QAAQ,IAAA;AAClC,QAAM,QAAkB,CAAA;AACxB,QAAM,SAAmB,CAAA;AAEzB,aAAW,WAAW,UAAU;AAE9B,UAAM,eAAe,WAAW,OAAO,IAAI,UAAU,QAAQ,YAAY,OAAO;AAEhF,QAAI,WAAW,YAAY,GAAG;AAC5B,YAAM,OAAO,SAAS,YAAY;AAClC,UAAI,KAAK,UAAU;AACjB,cAAM,KAAK,YAAY;AACvB;AAAA,MACF,WAAW,KAAK,eAAe;AAE7B,cAAM,WAAW,MAAM,KAAK,cAAc,EAAE,KAAK,cAAc,UAAU,MAAM;AAC/E,YAAI,SAAS,WAAW,GAAG;AACzB,iBAAO,KAAK,uCAAuC,OAAO,EAAE;AAAA,QAC9D,OAAO;AACL,gBAAM,KAAK,GAAG,QAAQ;AAAA,QACxB;AACA;AAAA,MACF;AAAA,IACF;AAGA,UAAM,UAAU,MAAM,KAAK,SAAS,EAAE,KAAK,YAAY,UAAU,MAAM;AACvE,QAAI,QAAQ,WAAW,GAAG;AACxB,aAAO,KAAK,6BAA6B,OAAO,EAAE;AAAA,IACpD,OAAO;AACL,YAAM,KAAK,GAAG,OAAO;AAAA,IACvB;AAAA,EACF;AAGA,QAAM,cAAc,CAAC,GAAG,IAAI,IAAI,KAAK,CAAC;AAEtC,SAAO,EAAE,OAAO,aAAa,OAAA;AAC/B;AAEO,SAAS,kBAAkB,OAAiB,WAA6B;AAC9E,SAAO,MAAM,OAAO,CAAA,MAAK,EAAE,SAAS,SAAS,CAAC;AAChD;AAEO,SAAS,cAAc,OAA2B;AACvD,SAAO,kBAAkB,OAAO,QAAQ;AAC1C;AC7CO,SAAS,WAAW,MAAuB;AAChD,SAAO,KAAK,UAAU,MAAM,MAAM,CAAC;AACrC;AAEO,SAAS,uBAAuB,QAA0B,UAA0B;AACzF,MAAI,OAAO,OAAO;AAChB,WAAO,MAAM,MAAM,KAAK,QAAQ,EAAE;AAAA,EACpC;AACA,QAAM,SAAS,OAAO,OAAO,IAAI,CAAA,MAAK,MAAM,IAAI,OAAO,CAAC,EAAE,CAAC,EAAE,KAAK,IAAI;AACtE,SAAO,GAAG,MAAM,IAAI,KAAK,QAAQ,EAAE,CAAC;AAAA,EAAK,MAAM;AACjD;AAEO,SAAS,wBACd,SACA,UAAyB,IACjB;AACR,QAAM,EAAE,SAAS,OAAA,IAAW;AAE5B,MAAI,WAAW,QAAQ;AACrB,WAAO,WAAW,QAAQ,IAAI,CAAA,OAAM;AAAA,MAClC,MAAM,EAAE;AAAA,MACR,OAAO,EAAE,OAAO;AAAA,MAChB,QAAQ,EAAE,OAAO;AAAA,IAAA,EACjB,CAAC;AAAA,EACL;AAEA,QAAM,QAAQ,QAAQ,IAAI,CAAA,MAAK,uBAAuB,EAAE,QAAQ,EAAE,IAAI,CAAC;AACvE,QAAM,SAAS,QAAQ,OAAO,OAAK,EAAE,OAAO,KAAK,EAAE;AACnD,QAAM,SAAS,QAAQ,SAAS;AAEhC,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,MAAM,KAAK,uBAAuB,MAAM,YAAY,MAAM,SAAS,CAAC;AAE/E,SAAO,MAAM,KAAK,IAAI;AACxB;AAqBO,SAAS,iBAAiB,QAA6B,UAAU,OAAe;AACrF,QAAM,SAAS,OAAO,SAClB,MAAM,MAAM,QAAQ,IACpB,MAAM,IAAI,QAAQ;AAEtB,QAAM,WAAW,MAAM,KAAK,IAAI,OAAO,QAAQ,KAAK;AACpD,MAAI,SAAS,GAAG,MAAM,IAAI,OAAO,UAAU,IAAI,QAAQ;AAEvD,MAAI,WAAW,CAAC,OAAO,QAAQ;AAC7B,UAAM,iBAAiB,OAAO,WAC3B,OAAO,CAAA,MAAK,WAAW,CAAC,EAAE,MAAM,EAChC,IAAI,CAAA,MAAK;AACR,YAAM,OAAO,EAAE,SAAS,MAAM,MAAM,KAAK,IAAI,MAAM,IAAI,KAAK;AAC5D,aAAO,GAAG,IAAI,KAAK,EAAE,IAAI,KAAK,EAAE,OAAO;AAAA,IACzC,CAAC;AACH,QAAI,eAAe,SAAS,GAAG;AAC7B,gBAAU,OAAO,eAAe,KAAK,IAAI;AAAA,IAC3C;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,kBAAkB,SAAoC;AACpE,QAAM,WAAW,QAAQ,SAAS,QAAQ,CAAC;AAC3C,QAAM,cAAc,QAAQ,WAAW,IAAI,MAAM,QAAQ,MAAM;AAE/D,SAAO;AAAA,IACL;AAAA,IACA,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;AAAA,IACzB,MAAM,KAAK,cAAc;AAAA,IACzB,eAAe,QAAQ,KAAK;AAAA,IAC5B,KAAK,MAAM,MAAM,SAAS,CAAC,KAAK,QAAQ,MAAM;AAAA,IAC9C,KAAK,MAAM,IAAI,SAAS,CAAC,KAAK,QAAQ,MAAM;AAAA,IAC5C,gBAAgB,YAAY,WAAW,GAAG,CAAC;AAAA,IAC3C,gBAAgB,QAAQ,QAAQ;AAAA,IAChC,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;AAAA,EAAA,EACzB,KAAK,IAAI;AACb;AAEO,SAAS,kBACd,SACA,SACA,UAAyB,CAAA,GACjB;AACR,QAAM,EAAE,SAAS,QAAQ,UAAU,UAAU;AAE7C,MAAI,WAAW,QAAQ;AACrB,WAAO,WAAW,EAAE,SAAS,SAAS;AAAA,EACxC;AAEA,QAAM,QAAQ,QAAQ,IAAI,OAAK,iBAAiB,GAAG,OAAO,CAAC;AAC3D,QAAM,KAAK,kBAAkB,OAAO,CAAC;AAErC,SAAO,MAAM,KAAK,IAAI;AACxB;AAEO,SAAS,mBAAmB,WAAqB,UAAyB,IAAY;AAC3F,QAAM,EAAE,SAAS,OAAA,IAAW;AAE5B,MAAI,WAAW,QAAQ;AACrB,WAAO,WAAW,EAAE,WAAW;AAAA,EACjC;AAEA,SAAO;AAAA,IACL,MAAM,KAAK,sBAAsB;AAAA,IACjC,GAAG,UAAU,IAAI,CAAA,MAAK,OAAO,CAAC,EAAE;AAAA,EAAA,EAChC,KAAK,IAAI;AACb;AAEO,SAAS,qBAAqB,UAAmB,UAAyB,IAAY;AAC3F,QAAM,EAAE,SAAS,OAAA,IAAW;AAE5B,MAAI,WAAW,QAAQ;AACrB,WAAO,WAAW,QAAQ;AAAA,EAC5B;AAGA,QAAM,KAAK;AACX,QAAM,QAAkB,CAAA;AAExB,QAAM,KAAK,MAAM,KAAK,cAAc,GAAG,MAAM,SAAS,EAAE,CAAC;AACzD,MAAI,GAAG,YAAa,OAAM,KAAK,kBAAkB,GAAG,WAAW,EAAE;AACjE,MAAI,GAAG,KAAM,OAAM,KAAK,eAAe,GAAG,IAAI,EAAE;AAEhD,SAAO,MAAM,KAAK,IAAI;AACxB;AC/IA,IAAI,gBAA+B,CAAA;AAE5B,SAAS,iBAAiB,SAA8B;AAC7D,kBAAgB,EAAE,GAAG,eAAe,GAAG,QAAA;AACzC;AAEO,SAAS,MAAM,YAAoB,MAAuB;AAC/D,MAAI,cAAc,WAAW,CAAC,cAAc,OAAO;AACjD,YAAQ,IAAI,MAAM,KAAK,WAAW,OAAO,EAAE,GAAG,GAAG,IAAI;AAAA,EACvD;AACF;AAEO,SAAS,KAAK,YAAoB,MAAuB;AAC9D,MAAI,CAAC,cAAc,OAAO;AACxB,YAAQ,IAAI,MAAM,KAAK,OAAO,GAAG,GAAG,IAAI;AAAA,EAC1C;AACF;AAEO,SAAS,QAAQ,YAAoB,MAAuB;AACjE,MAAI,CAAC,cAAc,OAAO;AACxB,YAAQ,IAAI,MAAM,MAAM,OAAO,GAAG,GAAG,IAAI;AAAA,EAC3C;AACF;AAEO,SAAS,KAAK,YAAoB,MAAuB;AAC9D,UAAQ,KAAK,MAAM,OAAO,UAAU,OAAO,EAAE,GAAG,GAAG,IAAI;AACzD;AAEO,SAAS,MAAM,YAAoB,MAAuB;AAC/D,UAAQ,MAAM,MAAM,IAAI,WAAW,OAAO,EAAE,GAAG,GAAG,IAAI;AACxD;AAEO,SAAS,IAAI,YAAoB,MAAuB;AAC7D,UAAQ,IAAI,SAAS,GAAG,IAAI;AAC9B;AAEO,SAAS,UAAgB;AAC9B,MAAI,CAAC,cAAc,OAAO;AACxB,YAAQ,IAAA;AAAA,EACV;AACF;AAEO,MAAM,SAAS;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAY;AACd;AC/CO,MAAM,kBAAkB,IAAI,QAAQ,UAAU,EAClD,YAAY,8CAA8C,EAC1D,SAAS,cAAc,oCAAoC,EAC3D,OAAO,yBAAyB,6BAA6B,MAAM,EACnE,OAAO,eAAe,oBAAoB,EAC1C,OAAO,OAAO,OAAiB,YAA6B;AAC3D,mBAAiB,EAAE,OAAO,QAAQ,MAAA,CAAO;AAEzC,QAAM,UAAU,QAAQ,QAAQ,OAAO,IAAI,oBAAoB,EAAE,MAAA;AAEjE,MAAI;AACF,UAAM,EAAE,OAAO,eAAe,QAAQ,kBAAkB,MAAM,aAAa,KAAK;AAChF,UAAM,aAAa,cAAc,aAAa;AAE9C,QAAI,cAAc,SAAS,KAAK,CAAC,QAAQ,OAAO;AAC9C,oBAAc,QAAQ,CAAA,QAAO,OAAO,KAAK,GAAG,CAAC;AAAA,IAC/C;AAEA,QAAI,WAAW,WAAW,GAAG;AAC3B,eAAS,KAAK,uBAAuB;AACrC,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,QAAI,QAAS,SAAQ,OAAO,cAAc,WAAW,MAAM;AAE3D,UAAM,UAAU,WAAW,IAAI,CAAA,UAAS;AAAA,MACtC;AAAA,MACA,QAAQ,iBAAiB,IAAI;AAAA,IAAA,EAC7B;AAEF,aAAS,KAAA;AAET,UAAM,SAAS,wBAAwB,SAAS,EAAE,QAAQ,QAAQ,QAAQ;AAC1E,WAAO,IAAI,MAAM;AAEjB,UAAM,YAAY,QAAQ,KAAK,OAAK,CAAC,EAAE,OAAO,KAAK;AACnD,YAAQ,KAAK,YAAY,IAAI,CAAC;AAAA,EAChC,SAAS,KAAK;AACZ,aAAS,KAAK,mBAAmB;AACjC,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,WAAO,MAAM,OAAO;AACpB,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;ACpCH,SAASA,gBAAc,OAAe,WAAmC,IAA4B;AACnG,QAAM,CAAC,KAAK,GAAG,IAAI,MAAM,MAAM,GAAG;AAClC,MAAI,OAAO,QAAQ,QAAW;AAC5B,aAAS,GAAG,IAAI;AAAA,EAClB;AACA,SAAO;AACT;AAEA,SAAS,aAAa,QAAyC;AAC7D,SAAO;AAAA,IACL,YAAY,OAAO;AAAA,IACnB,QAAQ,OAAO;AAAA,IACf,UAAU,OAAO;AAAA,IACjB,YAAY,OAAO,WAAW,IAAI,CAAA,OAAM;AAAA,MACtC,QAAQ,EAAE;AAAA,MACV,MAAM,EAAE;AAAA,MACR,SAAS,EAAE;AAAA,IAAA,EACX;AAAA,EAAA;AAEN;AAEO,MAAM,aAAa,IAAI,QAAQ,KAAK,EACxC,YAAY,uCAAuC,EACnD,SAAS,cAAc,+BAA+B,EACtD,OAAO,yBAAyB,6BAA6B,MAAM,EACnE,OAAO,8BAA8B,wBAAwB,GAAG,EAChE,OAAO,yBAAyB,yBAAyBA,iBAAe,CAAA,CAAE,EAC1E,OAAO,4BAA4B,cAAcA,iBAAe,EAAE,EAClE,OAAO,iBAAiB,gBAAgB,EACxC,OAAO,eAAe,qBAAqB,EAC3C,OAAO,eAAe,uBAAuB,EAC7C,OAAO,OAAO,OAAiB,YAAwB;AACtD,mBAAiB,EAAE,SAAS,QAAQ,SAAS,OAAO,QAAQ,OAAO;AAEnE,QAAM,cAAc,SAAS,QAAQ,eAAe,KAAK,EAAE;AAC3D,QAAM,UAAU,QAAQ,QAAQ,OAAO,IAAI,oBAAoB,EAAE,MAAA;AAEjE,MAAI;AAEF,UAAM,EAAE,OAAO,eAAe,QAAQ,kBAAkB,MAAM,aAAa,KAAK;AAChF,UAAM,aAAa,cAAc,aAAa;AAE9C,QAAI,cAAc,SAAS,KAAK,CAAC,QAAQ,OAAO;AAC9C,oBAAc,QAAQ,CAAA,QAAO,OAAO,KAAK,GAAG,CAAC;AAAA,IAC/C;AAEA,QAAI,WAAW,WAAW,GAAG;AAC3B,eAAS,KAAK,uBAAuB;AACrC,cAAQ,KAAK,CAAC;AAAA,IAChB;AAGA,QAAI,QAAS,SAAQ,OAAO,WAAW,WAAW,MAAM;AAExD,UAAM,eAA2B,CAAA;AACjC,UAAM,cAAsD,CAAA;AAE5D,eAAW,QAAQ,YAAY;AAC7B,UAAI;AACF,cAAM,YAAY,eAAe,MAAM;AAAA,UACrC,KAAK,QAAQ;AAAA,UACb,QAAQ,QAAQ;AAAA,QAAA,CACjB;AACD,qBAAa,KAAK,GAAG,SAAS;AAAA,MAChC,SAAS,KAAK;AACZ,cAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,oBAAY,KAAK,EAAE,MAAM,OAAO,SAAS;AAAA,MAC3C;AAAA,IACF;AAEA,QAAI,aAAa,WAAW,GAAG;AAC7B,eAAS,KAAK,qBAAqB;AACnC,UAAI,YAAY,SAAS,GAAG;AAC1B,oBAAY,QAAQ,CAAC,EAAE,MAAM,OAAAC,OAAA,MAAY,OAAO,MAAM,KAAK,IAAI,KAAKA,MAAK,EAAE,CAAC;AAAA,MAC9E;AACA,cAAQ,KAAK,CAAC;AAAA,IAChB;AAGA,QAAI,QAAS,SAAQ,OAAO,WAAW,aAAa,MAAM,6BAA6B,WAAW;AAElG,QAAI;AAEJ,QAAI,QAAQ,UAAU;AAEpB,YAAM,UAAwB,CAAA;AAC9B,UAAI,UAAU;AAEd,iBAAW,YAAY,cAAc;AACnC,YAAI,QAAS;AAEb,YAAI,QAAS,SAAQ,OAAO,YAAY,SAAS,EAAE;AAEnD,YAAI;AACF,gBAAM,SAAS,MAAM,UAAU,SAAS,CAAC,QAAQ,GAAG,EAAE,aAAa,GAAG;AACtE,kBAAQ,KAAK,GAAG,OAAO,OAAO;AAE9B,cAAI,CAAC,OAAO,QAAQ,CAAC,GAAG,QAAQ;AAC9B,sBAAU;AAAA,UACZ;AAAA,QACF,SAAS,KAAK;AACZ,oBAAU;AAAA,QACZ;AAAA,MACF;AAEA,YAAM,SAAS,QAAQ,OAAO,CAAA,MAAK,EAAE,MAAM,EAAE;AAC7C,YAAM,SAAS,QAAQ,SAAS;AAEhC,uBAAiB;AAAA,QACf;AAAA,QACA,UAAU;AAAA,QACV,SAAS;AAAA,UACP,OAAO,QAAQ;AAAA,UACf;AAAA,UACA;AAAA,UACA,UAAU,QAAQ,SAAS,IAAK,SAAS,QAAQ,SAAU,MAAM;AAAA,QAAA;AAAA,MACnE;AAAA,IAEJ,OAAO;AACL,uBAAiB,MAAM,UAAU,SAAS,cAAc,EAAE,aAAa;AAAA,IACzE;AAEA,aAAS,KAAA;AAGT,UAAM,mBAAmB,eAAe,QAAQ,IAAI,YAAY;AAChE,UAAM,UAA6B;AAAA,MACjC,GAAG,eAAe;AAAA,MAClB,UAAU,eAAe;AAAA,IAAA;AAG3B,QAAI,QAAQ,WAAW,QAAQ;AAC7B,aAAO,IAAI,WAAW;AAAA,QACpB,SAAS;AAAA,QACT;AAAA,QACA;AAAA,MAAA,CACD,CAAC;AAAA,IACJ,OAAO;AACL,UAAI,CAAC,QAAQ,OAAO;AAClB,cAAM,SAAS,kBAAkB,kBAAkB,SAAS;AAAA,UAC1D,QAAQ,QAAQ;AAAA,UAChB,SAAS,QAAQ;AAAA,QAAA,CAClB;AACD,eAAO,IAAI,MAAM;AAAA,MACnB,OAAO;AAEL,cAAM,cAAc,QAAQ,WAAW,IAAI,MAAM,QAAQ,MAAM;AAC/D,eAAO,IAAI,YAAY,GAAG,QAAQ,MAAM,IAAI,QAAQ,KAAK,kBAAkB,QAAQ,SAAS,QAAQ,CAAC,CAAC,IAAI,CAAC;AAAA,MAC7G;AAEA,UAAI,YAAY,SAAS,GAAG;AAC1B,eAAO,QAAA;AACP,eAAO,KAAK,GAAG,YAAY,MAAM,2BAA2B;AAC5D,mBAAW,EAAE,MAAM,OAAAA,OAAA,KAAW,aAAa;AACzC,iBAAO,MAAM,KAAK,IAAI,KAAKA,MAAK,EAAE;AAAA,QACpC;AAAA,MACF;AAAA,IACF;AAGA,YAAQ,KAAK,eAAe,QAAQ,SAAS,IAAI,IAAI,CAAC;AAAA,EACxD,SAAS,KAAK;AACZ,aAAS,KAAK,kBAAkB;AAChC,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,WAAO,MAAM,OAAO;AACpB,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;AC7KH,SAAS,cAAc,OAAe,WAAmC,IAA4B;AACnG,QAAM,CAAC,KAAK,GAAG,IAAI,MAAM,MAAM,GAAG;AAClC,MAAI,OAAO,QAAQ,QAAW;AAC5B,aAAS,GAAG,IAAI;AAAA,EAClB;AACA,SAAO;AACT;AAEO,MAAM,eAAe,IAAI,QAAQ,OAAO,EAC5C,YAAY,2DAA2D,EACvE,SAAS,cAAc,iCAAiC,EACxD,OAAO,yBAAyB,6BAA6B,MAAM,EACnE,OAAO,iBAAiB,2BAA2B,EACnD,OAAO,eAAe,gBAAgB,EACtC,OAAO,yBAAyB,yBAAyB,eAAe,EAAE,EAC1E,OAAO,4BAA4B,cAAc,eAAe,CAAA,CAAE,EAClE,OAAO,OAAO,OAAiB,YAA4F;AAC1H,mBAAiB,EAAE,SAAS,QAAQ,SAAS,OAAO,QAAQ,OAAO;AAEnE,QAAM,UAAU,QAAQ,QAAQ,OAAO,IAAI,oBAAoB,EAAE,MAAA;AAEjE,MAAI;AACF,UAAM,EAAE,OAAO,eAAe,QAAQ,kBAAkB,MAAM,aAAa,KAAK;AAChF,UAAM,aAAa,cAAc,aAAa;AAE9C,QAAI,cAAc,SAAS,KAAK,CAAC,QAAQ,OAAO;AAC9C,oBAAc,QAAQ,CAAA,QAAO,OAAO,KAAK,GAAG,CAAC;AAAA,IAC/C;AAEA,QAAI,WAAW,WAAW,GAAG;AAC3B,eAAS,KAAK,uBAAuB;AACrC,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,QAAI,QAAS,SAAQ,OAAO,WAAW,WAAW,MAAM;AAExD,UAAM,eAA0B,CAAA;AAChC,UAAM,cAAsD,CAAA;AAE5D,eAAW,QAAQ,YAAY;AAC7B,UAAI;AACF,cAAM,YAAY,eAAe,MAAM;AAAA,UACrC,KAAK,QAAQ;AAAA,UACb,QAAQ,QAAQ;AAAA,QAAA,CACjB;AACD,qBAAa,KAAK,GAAG,SAAS;AAAA,MAChC,SAAS,KAAK;AACZ,cAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,oBAAY,KAAK,EAAE,MAAM,OAAO,SAAS;AAAA,MAC3C;AAAA,IACF;AAEA,aAAS,KAAA;AAET,QAAI,QAAQ,WAAW,QAAQ;AAC7B,aAAO,IAAI,WAAW;AAAA,QACpB,WAAW;AAAA,QACX,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,YAAY,WAAW;AAAA,UACvB,gBAAgB,aAAa;AAAA,UAC7B,aAAa,YAAY;AAAA,QAAA;AAAA,MAC3B,CACD,CAAC;AAAA,IACJ,OAAO;AACL,aAAO,KAAK,UAAU,aAAa,MAAM,sBAAsB,WAAW,MAAM,UAAU;AAC1F,aAAO,QAAA;AAEP,iBAAW,YAAY,cAAc;AACnC,eAAO,IAAI,qBAAqB,UAAU,EAAE,QAAQ,QAAQ,QAAQ,SAAS,QAAQ,QAAA,CAAS,CAAC;AAC/F,eAAO,QAAA;AAAA,MACT;AAEA,UAAI,YAAY,SAAS,GAAG;AAC1B,eAAO,QAAA;AACP,eAAO,KAAK,GAAG,YAAY,MAAM,2BAA2B;AAC5D,mBAAW,EAAE,MAAM,OAAAA,OAAA,KAAW,aAAa;AACzC,iBAAO,MAAM,KAAK,IAAI,KAAKA,MAAK,EAAE;AAAA,QACpC;AAAA,MACF;AAAA,IACF;AAEA,YAAQ,KAAK,YAAY,SAAS,IAAI,IAAI,CAAC;AAAA,EAC7C,SAAS,KAAK;AACZ,aAAS,KAAK,cAAc;AAC5B,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,WAAO,MAAM,OAAO;AACpB,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;AC7FI,MAAM,cAAc,IAAI,QAAQ,MAAM,EAC1C,YAAY,4CAA4C,EACxD,OAAO,yBAAyB,6BAA6B,MAAM,EACnE,OAAO,OAAO,YAAyB;AACtC,MAAI;AACF,UAAM,YAAY,SAAS,mBAAA;AAC3B,UAAM,SAAS,mBAAmB,WAAW,EAAE,QAAQ,QAAQ,QAAQ;AACvE,WAAO,IAAI,MAAM;AAAA,EACnB,SAAS,KAAK;AACZ,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,WAAO,MAAM,6BAA6B,OAAO,EAAE;AACnD,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;ACjBH,MAAM,UAAU,IAAI,QAAA;AAEpB,QACG,KAAK,OAAO,EACZ,YAAY,2CAA2C,EACvD,QAAQ,OAAO;AAElB,QAAQ,WAAW,eAAe;AAClC,QAAQ,WAAW,UAAU;AAC7B,QAAQ,WAAW,YAAY;AAC/B,QAAQ,WAAW,WAAW;AAE9B,QAAQ,MAAA;"}
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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boolesai/tspec-cli",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "CLI for @boolesai/tspec testing framework",
5
5
  "type": "module",
6
6
  "bin": {
@@ -22,16 +22,16 @@
22
22
  "prepublishOnly": "npm run build"
23
23
  },
24
24
  "dependencies": {
25
- "@boolesai/tspec": "^0.0.1",
26
- "commander": "^12.0.0",
25
+ "@boolesai/tspec": "^0.0.2",
27
26
  "chalk": "^5.0.0",
27
+ "commander": "^12.0.0",
28
28
  "glob": "^11.0.0",
29
29
  "ora": "^8.0.0"
30
30
  },
31
31
  "devDependencies": {
32
+ "@types/node": "^22.0.0",
32
33
  "typescript": "^5.0.0",
33
- "vite": "^7.0.0",
34
- "@types/node": "^22.0.0"
34
+ "vite": "^7.0.0"
35
35
  },
36
36
  "keywords": [
37
37
  "testing",
@@ -2,6 +2,31 @@ export interface FileResolutionResult {
2
2
  files: string[];
3
3
  errors: string[];
4
4
  }
5
+ /**
6
+ * Represents a discovered .tspec file without loading its content.
7
+ * Used for lazy file loading - files are scanned first, content read on-demand.
8
+ */
9
+ export interface TSpecFileDescriptor {
10
+ /** Absolute path to the file */
11
+ path: string;
12
+ /** Path relative to working directory */
13
+ relativePath: string;
14
+ /** Base filename (e.g., "login.http.tspec") */
15
+ fileName: string;
16
+ }
17
+ export interface DiscoveryResult {
18
+ files: TSpecFileDescriptor[];
19
+ errors: string[];
20
+ }
5
21
  export declare function resolveFiles(patterns: string[], cwd?: string): Promise<FileResolutionResult>;
6
22
  export declare function filterByExtension(files: string[], extension: string): string[];
7
23
  export declare function getTspecFiles(files: string[]): string[];
24
+ /**
25
+ * Discovers .tspec files without loading their content.
26
+ * Files are scanned and classified, content is read on-demand during validation/parsing/execution.
27
+ *
28
+ * @param patterns - File paths, directory paths, or glob patterns
29
+ * @param cwd - Working directory for resolving relative paths
30
+ * @returns DiscoveryResult with file descriptors and any errors
31
+ */
32
+ export declare function discoverTSpecFiles(patterns: string[], cwd?: string): Promise<DiscoveryResult>;