@curl-runner/cli 1.14.0 → 1.16.0
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/package.json +2 -1
- package/src/ci-exit.test.ts +1 -0
- package/src/cli.ts +308 -14
- package/src/commands/upgrade.ts +262 -0
- package/src/diff/baseline-manager.test.ts +181 -0
- package/src/diff/baseline-manager.ts +266 -0
- package/src/diff/diff-formatter.ts +316 -0
- package/src/diff/index.ts +3 -0
- package/src/diff/response-differ.test.ts +330 -0
- package/src/diff/response-differ.ts +489 -0
- package/src/parser/yaml.ts +2 -3
- package/src/types/bun-yaml.d.ts +11 -0
- package/src/types/config.ts +102 -0
- package/src/utils/curl-builder.ts +9 -1
- package/src/utils/installation-detector.test.ts +52 -0
- package/src/utils/installation-detector.ts +123 -0
- package/src/utils/logger.ts +1 -1
- package/src/utils/version-checker.ts +10 -17
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@curl-runner/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.16.0",
|
|
4
4
|
"description": "A powerful CLI tool for HTTP request management using YAML configuration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/cli.js",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"format": "biome format --write .",
|
|
15
15
|
"lint": "biome lint .",
|
|
16
16
|
"check": "biome check --write .",
|
|
17
|
+
"typecheck": "tsc --noEmit",
|
|
17
18
|
"test": "bun test"
|
|
18
19
|
},
|
|
19
20
|
"keywords": [
|
package/src/ci-exit.test.ts
CHANGED
package/src/cli.ts
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
3
|
import { Glob } from 'bun';
|
|
4
|
+
import { showUpgradeHelp, UpgradeCommand } from './commands/upgrade';
|
|
5
|
+
import { BaselineManager, DiffFormatter, DiffOrchestrator } from './diff';
|
|
4
6
|
import { ProfileExecutor } from './executor/profile-executor';
|
|
5
7
|
import { RequestExecutor } from './executor/request-executor';
|
|
6
8
|
import { YamlParser } from './parser/yaml';
|
|
7
9
|
import type {
|
|
10
|
+
DiffConfig,
|
|
8
11
|
ExecutionResult,
|
|
9
12
|
ExecutionSummary,
|
|
10
13
|
GlobalConfig,
|
|
14
|
+
GlobalDiffConfig,
|
|
11
15
|
ProfileConfig,
|
|
12
16
|
RequestConfig,
|
|
13
17
|
WatchConfig,
|
|
@@ -35,9 +39,9 @@ class CurlRunnerCLI {
|
|
|
35
39
|
if (await file.exists()) {
|
|
36
40
|
const yamlContent = await YamlParser.parseFile(filename);
|
|
37
41
|
// Extract global config from the YAML file
|
|
38
|
-
const config = yamlContent.global ||
|
|
42
|
+
const config = yamlContent.global || {};
|
|
39
43
|
this.logger.logInfo(`Loaded configuration from ${filename}`);
|
|
40
|
-
return config
|
|
44
|
+
return config as Partial<GlobalConfig>;
|
|
41
45
|
}
|
|
42
46
|
} catch (error) {
|
|
43
47
|
this.logger.logWarning(`Failed to load configuration from ${filename}: ${error}`);
|
|
@@ -244,6 +248,52 @@ class CurlRunnerCLI {
|
|
|
244
248
|
};
|
|
245
249
|
}
|
|
246
250
|
|
|
251
|
+
// Diff configuration
|
|
252
|
+
if (process.env.CURL_RUNNER_DIFF) {
|
|
253
|
+
envConfig.diff = {
|
|
254
|
+
...envConfig.diff,
|
|
255
|
+
enabled: process.env.CURL_RUNNER_DIFF.toLowerCase() === 'true',
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (process.env.CURL_RUNNER_DIFF_SAVE) {
|
|
260
|
+
envConfig.diff = {
|
|
261
|
+
...envConfig.diff,
|
|
262
|
+
save: process.env.CURL_RUNNER_DIFF_SAVE.toLowerCase() === 'true',
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (process.env.CURL_RUNNER_DIFF_LABEL) {
|
|
267
|
+
envConfig.diff = {
|
|
268
|
+
...envConfig.diff,
|
|
269
|
+
label: process.env.CURL_RUNNER_DIFF_LABEL,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (process.env.CURL_RUNNER_DIFF_COMPARE) {
|
|
274
|
+
envConfig.diff = {
|
|
275
|
+
...envConfig.diff,
|
|
276
|
+
compareWith: process.env.CURL_RUNNER_DIFF_COMPARE,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (process.env.CURL_RUNNER_DIFF_DIR) {
|
|
281
|
+
envConfig.diff = {
|
|
282
|
+
...envConfig.diff,
|
|
283
|
+
dir: process.env.CURL_RUNNER_DIFF_DIR,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (process.env.CURL_RUNNER_DIFF_OUTPUT) {
|
|
288
|
+
const format = process.env.CURL_RUNNER_DIFF_OUTPUT.toLowerCase();
|
|
289
|
+
if (['terminal', 'json', 'markdown'].includes(format)) {
|
|
290
|
+
envConfig.diff = {
|
|
291
|
+
...envConfig.diff,
|
|
292
|
+
outputFormat: format as 'terminal' | 'json' | 'markdown',
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
247
297
|
return envConfig;
|
|
248
298
|
}
|
|
249
299
|
|
|
@@ -269,6 +319,23 @@ class CurlRunnerCLI {
|
|
|
269
319
|
return;
|
|
270
320
|
}
|
|
271
321
|
|
|
322
|
+
// Handle upgrade subcommand: curl-runner upgrade [options]
|
|
323
|
+
if (args[0] === 'upgrade') {
|
|
324
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
325
|
+
showUpgradeHelp();
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
const upgradeCmd = new UpgradeCommand();
|
|
329
|
+
await upgradeCmd.run(args.slice(1));
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Handle diff subcommand: curl-runner diff <label1> <label2> [file]
|
|
334
|
+
if (args[0] === 'diff' && args.length >= 3) {
|
|
335
|
+
await this.executeDiffSubcommand(args.slice(1), options);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
|
|
272
339
|
// Load configuration from environment variables, config file, then CLI options
|
|
273
340
|
const envConfig = this.loadEnvironmentVariables();
|
|
274
341
|
const configFile = await this.loadConfigFile();
|
|
@@ -318,16 +385,16 @@ class CurlRunnerCLI {
|
|
|
318
385
|
globalConfig.maxConcurrency = options.maxConcurrent as number;
|
|
319
386
|
}
|
|
320
387
|
if (options.continueOnError !== undefined) {
|
|
321
|
-
globalConfig.continueOnError = options.continueOnError;
|
|
388
|
+
globalConfig.continueOnError = options.continueOnError as boolean;
|
|
322
389
|
}
|
|
323
390
|
if (options.verbose !== undefined) {
|
|
324
|
-
globalConfig.output = { ...globalConfig.output, verbose: options.verbose };
|
|
391
|
+
globalConfig.output = { ...globalConfig.output, verbose: options.verbose as boolean };
|
|
325
392
|
}
|
|
326
393
|
if (options.quiet !== undefined) {
|
|
327
394
|
globalConfig.output = { ...globalConfig.output, verbose: false };
|
|
328
395
|
}
|
|
329
396
|
if (options.output) {
|
|
330
|
-
globalConfig.output = { ...globalConfig.output, saveToFile: options.output };
|
|
397
|
+
globalConfig.output = { ...globalConfig.output, saveToFile: options.output as string };
|
|
331
398
|
}
|
|
332
399
|
if (options.outputFormat) {
|
|
333
400
|
globalConfig.output = {
|
|
@@ -342,21 +409,27 @@ class CurlRunnerCLI {
|
|
|
342
409
|
};
|
|
343
410
|
}
|
|
344
411
|
if (options.showHeaders !== undefined) {
|
|
345
|
-
globalConfig.output = {
|
|
412
|
+
globalConfig.output = {
|
|
413
|
+
...globalConfig.output,
|
|
414
|
+
showHeaders: options.showHeaders as boolean,
|
|
415
|
+
};
|
|
346
416
|
}
|
|
347
417
|
if (options.showBody !== undefined) {
|
|
348
|
-
globalConfig.output = { ...globalConfig.output, showBody: options.showBody };
|
|
418
|
+
globalConfig.output = { ...globalConfig.output, showBody: options.showBody as boolean };
|
|
349
419
|
}
|
|
350
420
|
if (options.showMetrics !== undefined) {
|
|
351
|
-
globalConfig.output = {
|
|
421
|
+
globalConfig.output = {
|
|
422
|
+
...globalConfig.output,
|
|
423
|
+
showMetrics: options.showMetrics as boolean,
|
|
424
|
+
};
|
|
352
425
|
}
|
|
353
426
|
|
|
354
427
|
// Apply timeout and retry settings to defaults
|
|
355
428
|
if (options.timeout) {
|
|
356
|
-
globalConfig.defaults = { ...globalConfig.defaults, timeout: options.timeout };
|
|
429
|
+
globalConfig.defaults = { ...globalConfig.defaults, timeout: options.timeout as number };
|
|
357
430
|
}
|
|
358
431
|
if (options.retries || options.noRetry) {
|
|
359
|
-
const retryCount = options.noRetry ? 0 : options.retries || 0;
|
|
432
|
+
const retryCount = options.noRetry ? 0 : (options.retries as number) || 0;
|
|
360
433
|
globalConfig.defaults = {
|
|
361
434
|
...globalConfig.defaults,
|
|
362
435
|
retry: {
|
|
@@ -370,7 +443,7 @@ class CurlRunnerCLI {
|
|
|
370
443
|
...globalConfig.defaults,
|
|
371
444
|
retry: {
|
|
372
445
|
...globalConfig.defaults?.retry,
|
|
373
|
-
delay: options.retryDelay,
|
|
446
|
+
delay: options.retryDelay as number,
|
|
374
447
|
},
|
|
375
448
|
};
|
|
376
449
|
}
|
|
@@ -416,6 +489,46 @@ class CurlRunnerCLI {
|
|
|
416
489
|
};
|
|
417
490
|
}
|
|
418
491
|
|
|
492
|
+
// Apply diff options
|
|
493
|
+
if (options.diff !== undefined) {
|
|
494
|
+
globalConfig.diff = {
|
|
495
|
+
...globalConfig.diff,
|
|
496
|
+
enabled: options.diff as boolean,
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
if (options.diffSave !== undefined) {
|
|
500
|
+
globalConfig.diff = {
|
|
501
|
+
...globalConfig.diff,
|
|
502
|
+
enabled: true,
|
|
503
|
+
save: options.diffSave as boolean,
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
if (options.diffLabel !== undefined) {
|
|
507
|
+
globalConfig.diff = {
|
|
508
|
+
...globalConfig.diff,
|
|
509
|
+
label: options.diffLabel as string,
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
if (options.diffCompare !== undefined) {
|
|
513
|
+
globalConfig.diff = {
|
|
514
|
+
...globalConfig.diff,
|
|
515
|
+
enabled: true,
|
|
516
|
+
compareWith: options.diffCompare as string,
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
if (options.diffDir !== undefined) {
|
|
520
|
+
globalConfig.diff = {
|
|
521
|
+
...globalConfig.diff,
|
|
522
|
+
dir: options.diffDir as string,
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
if (options.diffOutput !== undefined) {
|
|
526
|
+
globalConfig.diff = {
|
|
527
|
+
...globalConfig.diff,
|
|
528
|
+
outputFormat: options.diffOutput as 'terminal' | 'json' | 'markdown',
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
|
|
419
532
|
if (allRequests.length === 0) {
|
|
420
533
|
this.logger.logError('No requests found in YAML files');
|
|
421
534
|
process.exit(1);
|
|
@@ -575,13 +688,15 @@ class CurlRunnerCLI {
|
|
|
575
688
|
}
|
|
576
689
|
|
|
577
690
|
// Create combined summary
|
|
578
|
-
const successful = allResults.filter((r) => r.success).length;
|
|
579
|
-
const failed = allResults.filter((r) => !r.success).length;
|
|
691
|
+
const successful = allResults.filter((r) => r.success && !r.skipped).length;
|
|
692
|
+
const failed = allResults.filter((r) => !r.success && !r.skipped).length;
|
|
693
|
+
const skipped = allResults.filter((r) => r.skipped).length;
|
|
580
694
|
|
|
581
695
|
summary = {
|
|
582
696
|
total: allResults.length,
|
|
583
697
|
successful,
|
|
584
698
|
failed,
|
|
699
|
+
skipped,
|
|
585
700
|
duration: totalDuration,
|
|
586
701
|
results: allResults,
|
|
587
702
|
};
|
|
@@ -593,9 +708,138 @@ class CurlRunnerCLI {
|
|
|
593
708
|
summary = await executor.execute(allRequests);
|
|
594
709
|
}
|
|
595
710
|
|
|
711
|
+
// Handle diff mode
|
|
712
|
+
if (globalConfig.diff?.enabled || globalConfig.diff?.save || globalConfig.diff?.compareWith) {
|
|
713
|
+
await this.handleDiffMode(yamlFiles[0], summary.results, globalConfig.diff);
|
|
714
|
+
}
|
|
715
|
+
|
|
596
716
|
return summary;
|
|
597
717
|
}
|
|
598
718
|
|
|
719
|
+
private async handleDiffMode(
|
|
720
|
+
yamlPath: string,
|
|
721
|
+
results: ExecutionResult[],
|
|
722
|
+
diffConfig: GlobalDiffConfig,
|
|
723
|
+
): Promise<void> {
|
|
724
|
+
const orchestrator = new DiffOrchestrator(diffConfig);
|
|
725
|
+
const formatter = new DiffFormatter(diffConfig.outputFormat || 'terminal');
|
|
726
|
+
const config: DiffConfig = BaselineManager.mergeConfig(diffConfig, true) || {};
|
|
727
|
+
|
|
728
|
+
const currentLabel = diffConfig.label || 'current';
|
|
729
|
+
const compareLabel = diffConfig.compareWith;
|
|
730
|
+
|
|
731
|
+
// Save baseline if requested
|
|
732
|
+
if (diffConfig.save) {
|
|
733
|
+
await orchestrator.saveBaseline(yamlPath, currentLabel, results, config);
|
|
734
|
+
this.logger.logInfo(`Baseline saved as '${currentLabel}'`);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// Compare with baseline if requested
|
|
738
|
+
if (compareLabel) {
|
|
739
|
+
const diffSummary = await orchestrator.compareWithBaseline(
|
|
740
|
+
yamlPath,
|
|
741
|
+
results,
|
|
742
|
+
currentLabel,
|
|
743
|
+
compareLabel,
|
|
744
|
+
config,
|
|
745
|
+
);
|
|
746
|
+
|
|
747
|
+
// Check if baseline exists
|
|
748
|
+
if (diffSummary.newBaselines === diffSummary.totalRequests) {
|
|
749
|
+
this.logger.logWarning(
|
|
750
|
+
`No baseline '${compareLabel}' found. Saving current run as baseline.`,
|
|
751
|
+
);
|
|
752
|
+
await orchestrator.saveBaseline(yamlPath, compareLabel, results, config);
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
const output = formatter.formatSummary(diffSummary, compareLabel, currentLabel);
|
|
757
|
+
console.log(output);
|
|
758
|
+
|
|
759
|
+
// Save current as baseline if configured
|
|
760
|
+
if (diffConfig.save) {
|
|
761
|
+
await orchestrator.saveBaseline(yamlPath, currentLabel, results, config);
|
|
762
|
+
}
|
|
763
|
+
} else if (diffConfig.enabled && !diffConfig.save) {
|
|
764
|
+
// Auto-detect: list available baselines or save first baseline
|
|
765
|
+
const labels = await orchestrator.listLabels(yamlPath);
|
|
766
|
+
|
|
767
|
+
if (labels.length === 0) {
|
|
768
|
+
// No baselines exist - save current as default baseline
|
|
769
|
+
await orchestrator.saveBaseline(yamlPath, 'baseline', results, config);
|
|
770
|
+
this.logger.logInfo(`No baselines found. Saved current run as 'baseline'.`);
|
|
771
|
+
} else if (labels.length === 1) {
|
|
772
|
+
// One baseline exists - compare against it
|
|
773
|
+
const diffSummary = await orchestrator.compareWithBaseline(
|
|
774
|
+
yamlPath,
|
|
775
|
+
results,
|
|
776
|
+
currentLabel,
|
|
777
|
+
labels[0],
|
|
778
|
+
config,
|
|
779
|
+
);
|
|
780
|
+
const output = formatter.formatSummary(diffSummary, labels[0], currentLabel);
|
|
781
|
+
console.log(output);
|
|
782
|
+
} else {
|
|
783
|
+
// Multiple baselines - list them
|
|
784
|
+
this.logger.logInfo(`Available baselines: ${labels.join(', ')}`);
|
|
785
|
+
this.logger.logInfo(`Use --diff-compare <label> to compare against a specific baseline.`);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* Executes the diff subcommand to compare two stored baselines.
|
|
792
|
+
* Usage: curl-runner diff <label1> <label2> [file.yaml]
|
|
793
|
+
*/
|
|
794
|
+
private async executeDiffSubcommand(
|
|
795
|
+
args: string[],
|
|
796
|
+
options: Record<string, unknown>,
|
|
797
|
+
): Promise<void> {
|
|
798
|
+
const label1 = args[0];
|
|
799
|
+
const label2 = args[1];
|
|
800
|
+
let yamlFile = args[2];
|
|
801
|
+
|
|
802
|
+
// Find YAML file if not specified
|
|
803
|
+
if (!yamlFile) {
|
|
804
|
+
const yamlFiles = await this.findYamlFiles([], options);
|
|
805
|
+
if (yamlFiles.length === 0) {
|
|
806
|
+
this.logger.logError(
|
|
807
|
+
'No YAML files found. Specify a file: curl-runner diff <label1> <label2> <file.yaml>',
|
|
808
|
+
);
|
|
809
|
+
process.exit(1);
|
|
810
|
+
}
|
|
811
|
+
if (yamlFiles.length > 1) {
|
|
812
|
+
this.logger.logError('Multiple YAML files found. Specify which file to use.');
|
|
813
|
+
process.exit(1);
|
|
814
|
+
}
|
|
815
|
+
yamlFile = yamlFiles[0];
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
const diffConfig: GlobalDiffConfig = {
|
|
819
|
+
dir: (options.diffDir as string) || '__baselines__',
|
|
820
|
+
outputFormat: (options.diffOutput as 'terminal' | 'json' | 'markdown') || 'terminal',
|
|
821
|
+
};
|
|
822
|
+
|
|
823
|
+
const orchestrator = new DiffOrchestrator(diffConfig);
|
|
824
|
+
const formatter = new DiffFormatter(diffConfig.outputFormat || 'terminal');
|
|
825
|
+
const config: DiffConfig = { exclude: [], match: {} };
|
|
826
|
+
|
|
827
|
+
try {
|
|
828
|
+
const diffSummary = await orchestrator.compareTwoBaselines(yamlFile, label1, label2, config);
|
|
829
|
+
const output = formatter.formatSummary(diffSummary, label1, label2);
|
|
830
|
+
console.log(output);
|
|
831
|
+
|
|
832
|
+
// Exit with code 1 if differences found
|
|
833
|
+
if (diffSummary.changed > 0) {
|
|
834
|
+
process.exit(1);
|
|
835
|
+
}
|
|
836
|
+
process.exit(0);
|
|
837
|
+
} catch (error) {
|
|
838
|
+
this.logger.logError(error instanceof Error ? error.message : String(error));
|
|
839
|
+
process.exit(1);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
599
843
|
private parseArguments(args: string[]): { files: string[]; options: Record<string, unknown> } {
|
|
600
844
|
const options: Record<string, unknown> = {};
|
|
601
845
|
const files: string[] = [];
|
|
@@ -637,6 +881,10 @@ class CurlRunnerCLI {
|
|
|
637
881
|
options.snapshotUpdate = 'failing';
|
|
638
882
|
} else if (key === 'ci-snapshot') {
|
|
639
883
|
options.snapshotCi = true;
|
|
884
|
+
} else if (key === 'diff') {
|
|
885
|
+
options.diff = true;
|
|
886
|
+
} else if (key === 'diff-save') {
|
|
887
|
+
options.diffSave = true;
|
|
640
888
|
} else if (nextArg && !nextArg.startsWith('--')) {
|
|
641
889
|
if (key === 'continue-on-error') {
|
|
642
890
|
options.continueOnError = nextArg === 'true';
|
|
@@ -680,6 +928,16 @@ class CurlRunnerCLI {
|
|
|
680
928
|
options.profileExport = nextArg;
|
|
681
929
|
} else if (key === 'snapshot-dir') {
|
|
682
930
|
options.snapshotDir = nextArg;
|
|
931
|
+
} else if (key === 'diff-label') {
|
|
932
|
+
options.diffLabel = nextArg;
|
|
933
|
+
} else if (key === 'diff-compare') {
|
|
934
|
+
options.diffCompare = nextArg;
|
|
935
|
+
} else if (key === 'diff-dir') {
|
|
936
|
+
options.diffDir = nextArg;
|
|
937
|
+
} else if (key === 'diff-output') {
|
|
938
|
+
if (['terminal', 'json', 'markdown'].includes(nextArg)) {
|
|
939
|
+
options.diffOutput = nextArg;
|
|
940
|
+
}
|
|
683
941
|
} else {
|
|
684
942
|
options[key] = nextArg;
|
|
685
943
|
}
|
|
@@ -715,6 +973,9 @@ class CurlRunnerCLI {
|
|
|
715
973
|
case 'u':
|
|
716
974
|
options.snapshotUpdate = 'all';
|
|
717
975
|
break;
|
|
976
|
+
case 'd':
|
|
977
|
+
options.diff = true;
|
|
978
|
+
break;
|
|
718
979
|
case 'o': {
|
|
719
980
|
// Handle -o flag for output file
|
|
720
981
|
const outputArg = args[i + 1];
|
|
@@ -840,7 +1101,7 @@ class CurlRunnerCLI {
|
|
|
840
1101
|
variables: Record<string, string>,
|
|
841
1102
|
defaults: Partial<RequestConfig>,
|
|
842
1103
|
): RequestConfig {
|
|
843
|
-
const interpolated = YamlParser.interpolateVariables(request, variables);
|
|
1104
|
+
const interpolated = YamlParser.interpolateVariables(request, variables) as RequestConfig;
|
|
844
1105
|
return YamlParser.mergeConfigs(defaults, interpolated);
|
|
845
1106
|
}
|
|
846
1107
|
|
|
@@ -854,6 +1115,7 @@ class CurlRunnerCLI {
|
|
|
854
1115
|
ci: { ...base.ci, ...override.ci },
|
|
855
1116
|
watch: { ...base.watch, ...override.watch },
|
|
856
1117
|
snapshot: { ...base.snapshot, ...override.snapshot },
|
|
1118
|
+
diff: { ...base.diff, ...override.diff },
|
|
857
1119
|
};
|
|
858
1120
|
}
|
|
859
1121
|
|
|
@@ -960,6 +1222,23 @@ ${this.logger.color('SNAPSHOT OPTIONS:', 'yellow')}
|
|
|
960
1222
|
--snapshot-dir <dir> Custom snapshot directory (default: __snapshots__)
|
|
961
1223
|
--ci-snapshot Fail if snapshot is missing (CI mode)
|
|
962
1224
|
|
|
1225
|
+
${this.logger.color('DIFF OPTIONS:', 'yellow')}
|
|
1226
|
+
-d, --diff Enable response diffing (compare with baseline)
|
|
1227
|
+
--diff-save Save current run as baseline
|
|
1228
|
+
--diff-label <name> Label for current run (e.g., 'staging', 'v1.0')
|
|
1229
|
+
--diff-compare <label> Compare against this baseline label
|
|
1230
|
+
--diff-dir <dir> Baseline storage directory (default: __baselines__)
|
|
1231
|
+
--diff-output <format> Output format (terminal|json|markdown)
|
|
1232
|
+
|
|
1233
|
+
${this.logger.color('DIFF SUBCOMMAND:', 'yellow')}
|
|
1234
|
+
curl-runner diff <label1> <label2> [file.yaml]
|
|
1235
|
+
Compare two stored baselines without making requests
|
|
1236
|
+
|
|
1237
|
+
${this.logger.color('UPGRADE:', 'yellow')}
|
|
1238
|
+
curl-runner upgrade Upgrade to latest version (auto-detects install method)
|
|
1239
|
+
curl-runner upgrade --dry-run Preview upgrade command without executing
|
|
1240
|
+
curl-runner upgrade --force Force reinstall even if up to date
|
|
1241
|
+
|
|
963
1242
|
${this.logger.color('EXAMPLES:', 'yellow')}
|
|
964
1243
|
# Run all YAML files in current directory
|
|
965
1244
|
curl-runner
|
|
@@ -1027,6 +1306,21 @@ ${this.logger.color('EXAMPLES:', 'yellow')}
|
|
|
1027
1306
|
# CI mode - fail if snapshot missing
|
|
1028
1307
|
curl-runner api.yaml --snapshot --ci-snapshot
|
|
1029
1308
|
|
|
1309
|
+
# Response diffing - save baseline for staging
|
|
1310
|
+
curl-runner api.yaml --diff-save --diff-label staging
|
|
1311
|
+
|
|
1312
|
+
# Compare current run against staging baseline
|
|
1313
|
+
curl-runner api.yaml --diff --diff-compare staging
|
|
1314
|
+
|
|
1315
|
+
# Compare staging vs production baselines (offline)
|
|
1316
|
+
curl-runner diff staging production api.yaml
|
|
1317
|
+
|
|
1318
|
+
# Auto-diff: creates baseline on first run, compares on subsequent runs
|
|
1319
|
+
curl-runner api.yaml --diff
|
|
1320
|
+
|
|
1321
|
+
# Diff with JSON output for CI
|
|
1322
|
+
curl-runner api.yaml --diff --diff-compare staging --diff-output json
|
|
1323
|
+
|
|
1030
1324
|
${this.logger.color('YAML STRUCTURE:', 'yellow')}
|
|
1031
1325
|
Single request:
|
|
1032
1326
|
request:
|