@boolesai/tspec-cli 0.0.1

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/README.md ADDED
@@ -0,0 +1,234 @@
1
+ # @boolesai/tspec-cli
2
+
3
+ Command-line interface for TSpec - a multi-protocol testing DSL designed for Developer + AI collaboration.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@boolesai/tspec-cli.svg)](https://www.npmjs.com/package/@boolesai/tspec-cli)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ ## Installation
9
+
10
+ ```bash
11
+ npm install -g @boolesai/tspec-cli
12
+ ```
13
+
14
+ Or run directly with npx:
15
+
16
+ ```bash
17
+ npx @boolesai/tspec-cli <command>
18
+ ```
19
+
20
+ ## Commands
21
+
22
+ ### `tspec validate`
23
+
24
+ Validate `.tspec` files for schema correctness.
25
+
26
+ ```bash
27
+ tspec validate <files...> [options]
28
+ ```
29
+
30
+ **Options:**
31
+ - `-o, --output <format>` - Output format: `json`, `text` (default: `text`)
32
+ - `-q, --quiet` - Only output errors
33
+
34
+ **Examples:**
35
+ ```bash
36
+ # Validate a single file
37
+ tspec validate tests/login.http.tspec
38
+
39
+ # Validate multiple files with glob pattern
40
+ tspec validate "tests/**/*.tspec"
41
+
42
+ # JSON output for CI/CD
43
+ tspec validate tests/*.tspec --output json
44
+ ```
45
+
46
+ ### `tspec run`
47
+
48
+ Execute test cases and report results.
49
+
50
+ ```bash
51
+ tspec run <files...> [options]
52
+ ```
53
+
54
+ **Options:**
55
+ - `-o, --output <format>` - Output format: `json`, `text` (default: `text`)
56
+ - `-c, --concurrency <n>` - Max concurrent tests (default: `5`)
57
+ - `-e, --env <key=value>` - Environment variables (repeatable)
58
+ - `-p, --params <key=value>` - Parameters (repeatable)
59
+ - `-v, --verbose` - Verbose output
60
+ - `-q, --quiet` - Only output summary
61
+ - `--fail-fast` - Stop on first failure
62
+
63
+ **Examples:**
64
+ ```bash
65
+ # Run tests with default settings
66
+ tspec run tests/*.http.tspec
67
+
68
+ # Run with environment variables
69
+ tspec run tests/*.tspec -e API_HOST=api.example.com -e API_KEY=secret
70
+
71
+ # Run with parameters
72
+ tspec run tests/*.tspec -p username=testuser -p timeout=5000
73
+
74
+ # Run with higher concurrency
75
+ tspec run tests/*.tspec -c 10
76
+
77
+ # Verbose output for debugging
78
+ tspec run tests/*.tspec -v
79
+
80
+ # JSON output for CI/CD
81
+ tspec run tests/*.tspec --output json
82
+
83
+ # Stop on first failure
84
+ tspec run tests/*.tspec --fail-fast
85
+ ```
86
+
87
+ ### `tspec parse`
88
+
89
+ Parse and display test case information without execution.
90
+
91
+ ```bash
92
+ tspec parse <files...> [options]
93
+ ```
94
+
95
+ **Options:**
96
+ - `-o, --output <format>` - Output format: `json`, `text` (default: `text`)
97
+ - `-v, --verbose` - Show detailed information
98
+ - `-q, --quiet` - Minimal output
99
+ - `-e, --env <key=value>` - Environment variables
100
+ - `-p, --params <key=value>` - Parameters
101
+
102
+ **Examples:**
103
+ ```bash
104
+ # Parse and display test cases
105
+ tspec parse tests/login.http.tspec
106
+
107
+ # JSON output for inspection
108
+ tspec parse tests/*.tspec --output json
109
+
110
+ # With variable substitution
111
+ tspec parse tests/*.tspec -e API_HOST=localhost
112
+ ```
113
+
114
+ ### `tspec list`
115
+
116
+ List supported protocols and configuration.
117
+
118
+ ```bash
119
+ tspec list [options]
120
+ ```
121
+
122
+ **Options:**
123
+ - `-o, --output <format>` - Output format: `json`, `text` (default: `text`)
124
+
125
+ **Examples:**
126
+ ```bash
127
+ # List supported protocols
128
+ tspec list
129
+
130
+ # JSON output
131
+ tspec list --output json
132
+ ```
133
+
134
+ ## Exit Codes
135
+
136
+ | Code | Description |
137
+ |------|-------------|
138
+ | `0` | Success (all tests passed / validation passed) |
139
+ | `1` | Failure (tests failed / validation errors) |
140
+ | `2` | Error (invalid input / configuration error) |
141
+
142
+ ## CI/CD Integration
143
+
144
+ ### GitHub Actions
145
+
146
+ ```yaml
147
+ - name: Run TSpec tests
148
+ run: |
149
+ npx @boolesai/tspec-cli run tests/*.tspec --output json > results.json
150
+
151
+ - name: Validate TSpec files
152
+ run: npx @boolesai/tspec-cli validate tests/*.tspec
153
+ ```
154
+
155
+ ### GitLab CI
156
+
157
+ ```yaml
158
+ test:
159
+ script:
160
+ - npx @boolesai/tspec-cli run tests/*.tspec --output json
161
+ artifacts:
162
+ reports:
163
+ junit: results.json
164
+ ```
165
+
166
+ ## Build from Source
167
+
168
+ Prerequisites:
169
+ - Node.js >= 18.0.0
170
+ - npm >= 9.0.0
171
+
172
+ ```bash
173
+ # Clone the repository
174
+ git clone https://github.com/boolesai/testing-spec.git
175
+ cd testing-spec
176
+
177
+ # Build core package first (required dependency)
178
+ cd core
179
+ npm install
180
+ npm run package
181
+ npm link
182
+
183
+ # Build CLI package
184
+ cd ../cli
185
+ npm install
186
+ npm link @boolesai/tspec
187
+ npm run build
188
+ ```
189
+
190
+ ### Build Output
191
+
192
+ - `dist/` - Compiled JavaScript
193
+ - `types/` - TypeScript type definitions
194
+ - `bin/` - Executable entry point
195
+
196
+ ### Install Built CLI Globally
197
+
198
+ ```bash
199
+ # From the cli directory
200
+ npm install -g .
201
+
202
+ # Or link for development
203
+ npm link
204
+ ```
205
+
206
+ ### Create Distribution Package
207
+
208
+ ```bash
209
+ # Create a tarball for distribution
210
+ npm run package
211
+
212
+ # This generates @boolesai-tspec-cli-0.0.1.tgz
213
+ # Install from tarball:
214
+ npm install -g ./boolesai-tspec-cli-0.0.1.tgz
215
+ ```
216
+
217
+ ### Development Mode
218
+
219
+ ```bash
220
+ # Watch mode for development
221
+ npm run dev
222
+ ```
223
+
224
+ ## Documentation
225
+
226
+ For complete TSpec DSL documentation, see the [docs](../doc) directory.
227
+
228
+ ## Related
229
+
230
+ - [@boolesai/tspec](https://www.npmjs.com/package/@boolesai/tspec) - Core library for TSpec
231
+
232
+ ## License
233
+
234
+ MIT
package/bin/tspec.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import '../dist/index.js';
package/dist/index.js ADDED
@@ -0,0 +1,420 @@
1
+ import { Command } from "commander";
2
+ import ora from "ora";
3
+ import { validateTestCase, parseTestCases, scheduler, registry } from "@boolesai/tspec";
4
+ import { glob } from "glob";
5
+ import { isAbsolute, resolve } from "path";
6
+ import { existsSync, statSync } from "fs";
7
+ import chalk from "chalk";
8
+ async function resolveFiles(patterns, cwd) {
9
+ const workingDir = process.cwd();
10
+ const files = [];
11
+ const errors = [];
12
+ for (const pattern of patterns) {
13
+ const absolutePath = isAbsolute(pattern) ? pattern : resolve(workingDir, pattern);
14
+ if (existsSync(absolutePath)) {
15
+ const stat = statSync(absolutePath);
16
+ if (stat.isFile()) {
17
+ files.push(absolutePath);
18
+ continue;
19
+ } else if (stat.isDirectory()) {
20
+ const dirFiles = await glob("**/*.tspec", { cwd: absolutePath, absolute: true });
21
+ if (dirFiles.length === 0) {
22
+ errors.push(`No .tspec files found in directory: ${pattern}`);
23
+ } else {
24
+ files.push(...dirFiles);
25
+ }
26
+ continue;
27
+ }
28
+ }
29
+ const matches = await glob(pattern, { cwd: workingDir, absolute: true });
30
+ if (matches.length === 0) {
31
+ errors.push(`No files matched pattern: ${pattern}`);
32
+ } else {
33
+ files.push(...matches);
34
+ }
35
+ }
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");
44
+ }
45
+ function formatJson(data) {
46
+ return JSON.stringify(data, null, 2);
47
+ }
48
+ function formatValidationResult(result, filePath) {
49
+ if (result.valid) {
50
+ return chalk.green(`✓ ${filePath}`);
51
+ }
52
+ const errors = result.errors.map((e) => chalk.red(` - ${e}`)).join("\n");
53
+ return `${chalk.red(`✗ ${filePath}`)}
54
+ ${errors}`;
55
+ }
56
+ function formatValidationResults(results, options = {}) {
57
+ const { format = "text" } = options;
58
+ if (format === "json") {
59
+ return formatJson(results.map((r) => ({
60
+ file: r.file,
61
+ valid: r.result.valid,
62
+ errors: r.result.errors
63
+ })));
64
+ }
65
+ const lines = results.map((r) => formatValidationResult(r.result, r.file));
66
+ const passed = results.filter((r) => r.result.valid).length;
67
+ const failed = results.length - passed;
68
+ lines.push("");
69
+ lines.push(chalk.bold(`Validation Summary: ${passed} passed, ${failed} failed`));
70
+ return lines.join("\n");
71
+ }
72
+ function formatTestResult(result, verbose = false) {
73
+ const status = result.passed ? chalk.green("✓ PASS") : chalk.red("✗ FAIL");
74
+ const duration = chalk.gray(`(${result.duration}ms)`);
75
+ let output = `${status} ${result.testCaseId} ${duration}`;
76
+ if (verbose || !result.passed) {
77
+ const assertionLines = result.assertions.filter((a) => verbose || !a.passed).map((a) => {
78
+ const icon = a.passed ? chalk.green(" ✓") : chalk.red(" ✗");
79
+ return `${icon} [${a.type}] ${a.message}`;
80
+ });
81
+ if (assertionLines.length > 0) {
82
+ output += "\n" + assertionLines.join("\n");
83
+ }
84
+ }
85
+ return output;
86
+ }
87
+ function formatTestSummary(summary) {
88
+ const passRate = summary.passRate.toFixed(1);
89
+ const statusColor = summary.failed === 0 ? chalk.green : chalk.red;
90
+ return [
91
+ "",
92
+ chalk.bold("─".repeat(50)),
93
+ chalk.bold("Test Summary"),
94
+ ` Total: ${summary.total}`,
95
+ ` ${chalk.green("Passed:")} ${summary.passed}`,
96
+ ` ${chalk.red("Failed:")} ${summary.failed}`,
97
+ ` Pass Rate: ${statusColor(passRate + "%")}`,
98
+ ` Duration: ${summary.duration}ms`,
99
+ chalk.bold("─".repeat(50))
100
+ ].join("\n");
101
+ }
102
+ function formatTestResults(results, summary, options = {}) {
103
+ const { format = "text", verbose = false } = options;
104
+ if (format === "json") {
105
+ return formatJson({ results, summary });
106
+ }
107
+ const lines = results.map((r) => formatTestResult(r, verbose));
108
+ lines.push(formatTestSummary(summary));
109
+ return lines.join("\n");
110
+ }
111
+ function formatProtocolList(protocols, options = {}) {
112
+ const { format = "text" } = options;
113
+ if (format === "json") {
114
+ return formatJson({ protocols });
115
+ }
116
+ return [
117
+ chalk.bold("Supported Protocols:"),
118
+ ...protocols.map((p) => ` - ${p}`)
119
+ ].join("\n");
120
+ }
121
+ function formatParsedTestCase(testCase, options = {}) {
122
+ const { format = "text" } = options;
123
+ if (format === "json") {
124
+ return formatJson(testCase);
125
+ }
126
+ const tc = testCase;
127
+ const lines = [];
128
+ lines.push(chalk.bold(`Test Case: ${tc.id || "unknown"}`));
129
+ if (tc.description) lines.push(` Description: ${tc.description}`);
130
+ if (tc.type) lines.push(` Protocol: ${tc.type}`);
131
+ return lines.join("\n");
132
+ }
133
+ let globalOptions = {};
134
+ function setLoggerOptions(options) {
135
+ globalOptions = { ...globalOptions, ...options };
136
+ }
137
+ function debug(message, ...args) {
138
+ if (globalOptions.verbose && !globalOptions.quiet) {
139
+ console.log(chalk.gray(`[debug] ${message}`), ...args);
140
+ }
141
+ }
142
+ function info(message, ...args) {
143
+ if (!globalOptions.quiet) {
144
+ console.log(chalk.blue(message), ...args);
145
+ }
146
+ }
147
+ function success(message, ...args) {
148
+ if (!globalOptions.quiet) {
149
+ console.log(chalk.green(message), ...args);
150
+ }
151
+ }
152
+ function warn(message, ...args) {
153
+ console.warn(chalk.yellow(`[warn] ${message}`), ...args);
154
+ }
155
+ function error(message, ...args) {
156
+ console.error(chalk.red(`[error] ${message}`), ...args);
157
+ }
158
+ function log(message, ...args) {
159
+ console.log(message, ...args);
160
+ }
161
+ function newline() {
162
+ if (!globalOptions.quiet) {
163
+ console.log();
164
+ }
165
+ }
166
+ const logger = {
167
+ debug,
168
+ info,
169
+ success,
170
+ warn,
171
+ error,
172
+ log,
173
+ newline,
174
+ setOptions: setLoggerOptions
175
+ };
176
+ 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
+ setLoggerOptions({ quiet: options.quiet });
178
+ const spinner = options.quiet ? null : ora("Resolving files...").start();
179
+ try {
180
+ const { files: resolvedFiles, errors: resolveErrors } = await resolveFiles(files);
181
+ const tspecFiles = getTspecFiles(resolvedFiles);
182
+ if (resolveErrors.length > 0 && !options.quiet) {
183
+ resolveErrors.forEach((err) => logger.warn(err));
184
+ }
185
+ if (tspecFiles.length === 0) {
186
+ spinner?.fail("No .tspec files found");
187
+ process.exit(2);
188
+ }
189
+ if (spinner) spinner.text = `Validating ${tspecFiles.length} file(s)...`;
190
+ const results = tspecFiles.map((file) => ({
191
+ file,
192
+ result: validateTestCase(file)
193
+ }));
194
+ spinner?.stop();
195
+ const output = formatValidationResults(results, { format: options.output });
196
+ logger.log(output);
197
+ const hasErrors = results.some((r) => !r.result.valid);
198
+ process.exit(hasErrors ? 1 : 0);
199
+ } catch (err) {
200
+ spinner?.fail("Validation failed");
201
+ const message = err instanceof Error ? err.message : String(err);
202
+ logger.error(message);
203
+ process.exit(2);
204
+ }
205
+ });
206
+ function parseKeyValue$1(value, previous = {}) {
207
+ const [key, val] = value.split("=");
208
+ if (key && val !== void 0) {
209
+ previous[key] = val;
210
+ }
211
+ return previous;
212
+ }
213
+ function formatResult(result) {
214
+ return {
215
+ testCaseId: result.testCaseId,
216
+ passed: result.passed,
217
+ duration: result.duration,
218
+ assertions: result.assertions.map((a) => ({
219
+ passed: a.passed,
220
+ type: a.type,
221
+ message: a.message
222
+ }))
223
+ };
224
+ }
225
+ 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
+ setLoggerOptions({ verbose: options.verbose, quiet: options.quiet });
227
+ const concurrency = parseInt(options.concurrency || "5", 10);
228
+ const spinner = options.quiet ? null : ora("Resolving files...").start();
229
+ try {
230
+ const { files: resolvedFiles, errors: resolveErrors } = await resolveFiles(files);
231
+ const tspecFiles = getTspecFiles(resolvedFiles);
232
+ if (resolveErrors.length > 0 && !options.quiet) {
233
+ resolveErrors.forEach((err) => logger.warn(err));
234
+ }
235
+ if (tspecFiles.length === 0) {
236
+ spinner?.fail("No .tspec files found");
237
+ process.exit(2);
238
+ }
239
+ if (spinner) spinner.text = `Parsing ${tspecFiles.length} file(s)...`;
240
+ const allTestCases = [];
241
+ const parseErrors = [];
242
+ for (const file of tspecFiles) {
243
+ try {
244
+ const testCases = parseTestCases(file, {
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, 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}`));
258
+ }
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
+ }
278
+ }
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
+ }
294
+ spinner?.stop();
295
+ const formattedResults = scheduleResult.results.map(formatResult);
296
+ const summary = {
297
+ ...scheduleResult.summary,
298
+ duration: scheduleResult.duration
299
+ };
300
+ if (options.output === "json") {
301
+ logger.log(formatJson({
302
+ results: formattedResults,
303
+ summary,
304
+ parseErrors
305
+ }));
306
+ } else {
307
+ if (!options.quiet) {
308
+ const output = formatTestResults(formattedResults, summary, {
309
+ format: options.output,
310
+ verbose: options.verbose
311
+ });
312
+ logger.log(output);
313
+ } else {
314
+ const statusColor = summary.failed === 0 ? chalk.green : chalk.red;
315
+ logger.log(statusColor(`${summary.passed}/${summary.total} tests passed (${summary.passRate.toFixed(1)}%)`));
316
+ }
317
+ if (parseErrors.length > 0) {
318
+ logger.newline();
319
+ logger.warn(`${parseErrors.length} file(s) failed to parse:`);
320
+ for (const { file, error: error2 } of parseErrors) {
321
+ logger.error(` ${file}: ${error2}`);
322
+ }
323
+ }
324
+ }
325
+ process.exit(scheduleResult.summary.failed > 0 ? 1 : 0);
326
+ } catch (err) {
327
+ spinner?.fail("Execution failed");
328
+ const message = err instanceof Error ? err.message : String(err);
329
+ logger.error(message);
330
+ process.exit(2);
331
+ }
332
+ });
333
+ function parseKeyValue(value, previous = {}) {
334
+ const [key, val] = value.split("=");
335
+ if (key && val !== void 0) {
336
+ previous[key] = val;
337
+ }
338
+ return previous;
339
+ }
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
+ setLoggerOptions({ verbose: options.verbose, quiet: options.quiet });
342
+ const spinner = options.quiet ? null : ora("Resolving files...").start();
343
+ try {
344
+ const { files: resolvedFiles, errors: resolveErrors } = await resolveFiles(files);
345
+ const tspecFiles = getTspecFiles(resolvedFiles);
346
+ if (resolveErrors.length > 0 && !options.quiet) {
347
+ resolveErrors.forEach((err) => logger.warn(err));
348
+ }
349
+ if (tspecFiles.length === 0) {
350
+ spinner?.fail("No .tspec files found");
351
+ process.exit(2);
352
+ }
353
+ if (spinner) spinner.text = `Parsing ${tspecFiles.length} file(s)...`;
354
+ const allTestCases = [];
355
+ const parseErrors = [];
356
+ for (const file of tspecFiles) {
357
+ try {
358
+ const testCases = parseTestCases(file, {
359
+ env: options.env,
360
+ params: options.params
361
+ });
362
+ allTestCases.push(...testCases);
363
+ } catch (err) {
364
+ const message = err instanceof Error ? err.message : String(err);
365
+ parseErrors.push({ file, error: message });
366
+ }
367
+ }
368
+ spinner?.stop();
369
+ if (options.output === "json") {
370
+ logger.log(formatJson({
371
+ testCases: allTestCases,
372
+ errors: parseErrors,
373
+ summary: {
374
+ totalFiles: tspecFiles.length,
375
+ totalTestCases: allTestCases.length,
376
+ parseErrors: parseErrors.length
377
+ }
378
+ }));
379
+ } else {
380
+ logger.info(`Parsed ${allTestCases.length} test case(s) from ${tspecFiles.length} file(s)`);
381
+ logger.newline();
382
+ for (const testCase of allTestCases) {
383
+ logger.log(formatParsedTestCase(testCase, { format: options.output, verbose: options.verbose }));
384
+ logger.newline();
385
+ }
386
+ if (parseErrors.length > 0) {
387
+ logger.newline();
388
+ logger.warn(`${parseErrors.length} file(s) failed to parse:`);
389
+ for (const { file, error: error2 } of parseErrors) {
390
+ logger.error(` ${file}: ${error2}`);
391
+ }
392
+ }
393
+ }
394
+ process.exit(parseErrors.length > 0 ? 1 : 0);
395
+ } catch (err) {
396
+ spinner?.fail("Parse failed");
397
+ const message = err instanceof Error ? err.message : String(err);
398
+ logger.error(message);
399
+ process.exit(2);
400
+ }
401
+ });
402
+ const listCommand = new Command("list").description("List supported protocols and configuration").option("-o, --output <format>", "Output format: json, text", "text").action(async (options) => {
403
+ try {
404
+ const protocols = registry.getRegisteredTypes();
405
+ const output = formatProtocolList(protocols, { format: options.output });
406
+ logger.log(output);
407
+ } catch (err) {
408
+ const message = err instanceof Error ? err.message : String(err);
409
+ logger.error(`Failed to list protocols: ${message}`);
410
+ process.exit(2);
411
+ }
412
+ });
413
+ const program = new Command();
414
+ program.name("tspec").description("CLI for @boolesai/tspec testing framework").version("1.0.0");
415
+ program.addCommand(validateCommand);
416
+ program.addCommand(runCommand);
417
+ program.addCommand(parseCommand);
418
+ program.addCommand(listCommand);
419
+ program.parse();
420
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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":"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"}
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@boolesai/tspec-cli",
3
+ "version": "0.0.1",
4
+ "description": "CLI for @boolesai/tspec testing framework",
5
+ "type": "module",
6
+ "bin": {
7
+ "tspec": "./bin/tspec.js"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "types": "./types/index.d.ts",
11
+ "files": [
12
+ "dist",
13
+ "types",
14
+ "bin"
15
+ ],
16
+ "scripts": {
17
+ "build": "vite build && npm run types",
18
+ "types": "tsc --emitDeclarationOnly",
19
+ "dev": "vite build --watch",
20
+ "test": "node --test test/**/*.js",
21
+ "package": "npm run build && npm pack",
22
+ "prepublishOnly": "npm run build"
23
+ },
24
+ "dependencies": {
25
+ "@boolesai/tspec": "^0.0.1",
26
+ "commander": "^12.0.0",
27
+ "chalk": "^5.0.0",
28
+ "glob": "^11.0.0",
29
+ "ora": "^8.0.0"
30
+ },
31
+ "devDependencies": {
32
+ "typescript": "^5.0.0",
33
+ "vite": "^7.0.0",
34
+ "@types/node": "^22.0.0"
35
+ },
36
+ "keywords": [
37
+ "testing",
38
+ "cli",
39
+ "tspec",
40
+ "specification",
41
+ "api-testing"
42
+ ],
43
+ "license": "MIT",
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "https://github.com/boolesai/testing-spec.git",
47
+ "directory": "cli"
48
+ },
49
+ "engines": {
50
+ "node": ">=18.0.0"
51
+ },
52
+ "author": "Booles AI",
53
+ "homepage": "https://github.com/boolesai/testing-spec#readme",
54
+ "bugs": {
55
+ "url": "https://github.com/boolesai/testing-spec/issues"
56
+ },
57
+ "publishConfig": {
58
+ "access": "public"
59
+ }
60
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const listCommand: Command;
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const parseCommand: Command;
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const runCommand: Command;
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const validateCommand: Command;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ export interface FileResolutionResult {
2
+ files: string[];
3
+ errors: string[];
4
+ }
5
+ export declare function resolveFiles(patterns: string[], cwd?: string): Promise<FileResolutionResult>;
6
+ export declare function filterByExtension(files: string[], extension: string): string[];
7
+ export declare function getTspecFiles(files: string[]): string[];
@@ -0,0 +1,35 @@
1
+ import type { ValidationResult } from '@boolesai/tspec';
2
+ export type OutputFormat = 'json' | 'text' | 'table';
3
+ export interface FormatOptions {
4
+ format?: OutputFormat;
5
+ verbose?: boolean;
6
+ quiet?: boolean;
7
+ }
8
+ export declare function formatJson(data: unknown): string;
9
+ export declare function formatValidationResult(result: ValidationResult, filePath: string): string;
10
+ export declare function formatValidationResults(results: Array<{
11
+ file: string;
12
+ result: ValidationResult;
13
+ }>, options?: FormatOptions): string;
14
+ export interface TestResultSummary {
15
+ total: number;
16
+ passed: number;
17
+ failed: number;
18
+ passRate: number;
19
+ duration: number;
20
+ }
21
+ export interface FormattedTestResult {
22
+ testCaseId: string;
23
+ passed: boolean;
24
+ duration: number;
25
+ assertions: Array<{
26
+ passed: boolean;
27
+ type: string;
28
+ message: string;
29
+ }>;
30
+ }
31
+ export declare function formatTestResult(result: FormattedTestResult, verbose?: boolean): string;
32
+ export declare function formatTestSummary(summary: TestResultSummary): string;
33
+ export declare function formatTestResults(results: FormattedTestResult[], summary: TestResultSummary, options?: FormatOptions): string;
34
+ export declare function formatProtocolList(protocols: string[], options?: FormatOptions): string;
35
+ export declare function formatParsedTestCase(testCase: unknown, options?: FormatOptions): string;
@@ -0,0 +1,23 @@
1
+ export type LogLevel = 'debug' | 'info' | 'success' | 'warn' | 'error';
2
+ export interface LoggerOptions {
3
+ verbose?: boolean;
4
+ quiet?: boolean;
5
+ }
6
+ export declare function setLoggerOptions(options: LoggerOptions): void;
7
+ export declare function debug(message: string, ...args: unknown[]): void;
8
+ export declare function info(message: string, ...args: unknown[]): void;
9
+ export declare function success(message: string, ...args: unknown[]): void;
10
+ export declare function warn(message: string, ...args: unknown[]): void;
11
+ export declare function error(message: string, ...args: unknown[]): void;
12
+ export declare function log(message: string, ...args: unknown[]): void;
13
+ export declare function newline(): void;
14
+ export declare const logger: {
15
+ debug: typeof debug;
16
+ info: typeof info;
17
+ success: typeof success;
18
+ warn: typeof warn;
19
+ error: typeof error;
20
+ log: typeof log;
21
+ newline: typeof newline;
22
+ setOptions: typeof setLoggerOptions;
23
+ };