@curl-runner/cli 1.16.0 → 1.16.2

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.
Files changed (40) hide show
  1. package/package.json +2 -2
  2. package/src/ci-exit.test.ts +0 -216
  3. package/src/cli.ts +0 -1351
  4. package/src/commands/upgrade.ts +0 -262
  5. package/src/diff/baseline-manager.test.ts +0 -181
  6. package/src/diff/baseline-manager.ts +0 -266
  7. package/src/diff/diff-formatter.ts +0 -316
  8. package/src/diff/index.ts +0 -3
  9. package/src/diff/response-differ.test.ts +0 -330
  10. package/src/diff/response-differ.ts +0 -489
  11. package/src/executor/max-concurrency.test.ts +0 -139
  12. package/src/executor/profile-executor.test.ts +0 -132
  13. package/src/executor/profile-executor.ts +0 -167
  14. package/src/executor/request-executor.ts +0 -663
  15. package/src/parser/yaml.test.ts +0 -480
  16. package/src/parser/yaml.ts +0 -271
  17. package/src/snapshot/index.ts +0 -3
  18. package/src/snapshot/snapshot-differ.test.ts +0 -358
  19. package/src/snapshot/snapshot-differ.ts +0 -296
  20. package/src/snapshot/snapshot-formatter.ts +0 -170
  21. package/src/snapshot/snapshot-manager.test.ts +0 -204
  22. package/src/snapshot/snapshot-manager.ts +0 -342
  23. package/src/types/bun-yaml.d.ts +0 -11
  24. package/src/types/config.ts +0 -638
  25. package/src/utils/colors.ts +0 -30
  26. package/src/utils/condition-evaluator.test.ts +0 -415
  27. package/src/utils/condition-evaluator.ts +0 -327
  28. package/src/utils/curl-builder.test.ts +0 -165
  29. package/src/utils/curl-builder.ts +0 -209
  30. package/src/utils/installation-detector.test.ts +0 -52
  31. package/src/utils/installation-detector.ts +0 -123
  32. package/src/utils/logger.ts +0 -856
  33. package/src/utils/response-store.test.ts +0 -213
  34. package/src/utils/response-store.ts +0 -108
  35. package/src/utils/stats.test.ts +0 -161
  36. package/src/utils/stats.ts +0 -151
  37. package/src/utils/version-checker.ts +0 -158
  38. package/src/version.ts +0 -43
  39. package/src/watcher/file-watcher.test.ts +0 -186
  40. package/src/watcher/file-watcher.ts +0 -140
@@ -1,489 +0,0 @@
1
- import type {
2
- Baseline,
3
- DiffCompareResult,
4
- DiffConfig,
5
- DiffSummary,
6
- ExecutionResult,
7
- GlobalDiffConfig,
8
- JsonValue,
9
- ResponseDiff,
10
- } from '../types/config';
11
- import { BaselineManager } from './baseline-manager';
12
-
13
- /**
14
- * Compares responses against baselines with support for exclusions and match rules.
15
- */
16
- export class ResponseDiffer {
17
- private excludePaths: Set<string>;
18
- private matchRules: Map<string, string>;
19
- private includeTimings: boolean;
20
-
21
- constructor(config: DiffConfig) {
22
- this.excludePaths = new Set(config.exclude || []);
23
- this.matchRules = new Map(Object.entries(config.match || {}));
24
- this.includeTimings = config.includeTimings || false;
25
- }
26
-
27
- /**
28
- * Compares current response against baseline.
29
- */
30
- compare(
31
- baseline: Baseline,
32
- current: Baseline,
33
- baselineLabel: string,
34
- currentLabel: string,
35
- requestName: string,
36
- ): DiffCompareResult {
37
- const differences: ResponseDiff[] = [];
38
-
39
- // Compare status
40
- if (baseline.status !== undefined || current.status !== undefined) {
41
- if (baseline.status !== current.status && !this.isExcluded('status')) {
42
- differences.push({
43
- path: 'status',
44
- baseline: baseline.status,
45
- current: current.status,
46
- type: 'changed',
47
- });
48
- }
49
- }
50
-
51
- // Compare headers
52
- if (baseline.headers || current.headers) {
53
- const headerDiffs = this.compareObjects(
54
- baseline.headers || {},
55
- current.headers || {},
56
- 'headers',
57
- );
58
- differences.push(...headerDiffs);
59
- }
60
-
61
- // Compare body
62
- if (baseline.body !== undefined || current.body !== undefined) {
63
- const bodyDiffs = this.deepCompare(baseline.body, current.body, 'body');
64
- differences.push(...bodyDiffs);
65
- }
66
-
67
- const result: DiffCompareResult = {
68
- requestName,
69
- hasDifferences: differences.length > 0,
70
- isNewBaseline: false,
71
- baselineLabel,
72
- currentLabel,
73
- differences,
74
- };
75
-
76
- // Add timing diff if enabled
77
- if (this.includeTimings && baseline.timing !== undefined && current.timing !== undefined) {
78
- const changePercent = ((current.timing - baseline.timing) / baseline.timing) * 100;
79
- result.timingDiff = {
80
- baseline: baseline.timing,
81
- current: current.timing,
82
- changePercent,
83
- };
84
- }
85
-
86
- return result;
87
- }
88
-
89
- /**
90
- * Deep comparison of two values with path tracking.
91
- */
92
- deepCompare(baseline: unknown, current: unknown, path: string): ResponseDiff[] {
93
- if (this.isExcluded(path)) {
94
- return [];
95
- }
96
-
97
- if (this.matchesRule(path, current)) {
98
- return [];
99
- }
100
-
101
- if (baseline === null && current === null) {
102
- return [];
103
- }
104
- if (baseline === undefined && current === undefined) {
105
- return [];
106
- }
107
-
108
- const baselineType = this.getType(baseline);
109
- const currentType = this.getType(current);
110
-
111
- if (baselineType !== currentType) {
112
- return [
113
- {
114
- path,
115
- baseline,
116
- current,
117
- type: 'type_mismatch',
118
- },
119
- ];
120
- }
121
-
122
- if (baselineType !== 'object' && baselineType !== 'array') {
123
- if (baseline !== current) {
124
- return [
125
- {
126
- path,
127
- baseline,
128
- current,
129
- type: 'changed',
130
- },
131
- ];
132
- }
133
- return [];
134
- }
135
-
136
- if (baselineType === 'array') {
137
- return this.compareArrays(baseline as JsonValue[], current as JsonValue[], path);
138
- }
139
-
140
- return this.compareObjects(
141
- baseline as Record<string, unknown>,
142
- current as Record<string, unknown>,
143
- path,
144
- );
145
- }
146
-
147
- /**
148
- * Compares two arrays.
149
- */
150
- private compareArrays(baseline: JsonValue[], current: JsonValue[], path: string): ResponseDiff[] {
151
- const differences: ResponseDiff[] = [];
152
- const maxLen = Math.max(baseline.length, current.length);
153
-
154
- for (let i = 0; i < maxLen; i++) {
155
- const itemPath = `${path}[${i}]`;
156
-
157
- if (i >= baseline.length) {
158
- if (!this.isExcluded(itemPath)) {
159
- differences.push({
160
- path: itemPath,
161
- baseline: undefined,
162
- current: current[i],
163
- type: 'added',
164
- });
165
- }
166
- } else if (i >= current.length) {
167
- if (!this.isExcluded(itemPath)) {
168
- differences.push({
169
- path: itemPath,
170
- baseline: baseline[i],
171
- current: undefined,
172
- type: 'removed',
173
- });
174
- }
175
- } else {
176
- const itemDiffs = this.deepCompare(baseline[i], current[i], itemPath);
177
- differences.push(...itemDiffs);
178
- }
179
- }
180
-
181
- return differences;
182
- }
183
-
184
- /**
185
- * Compares two objects.
186
- */
187
- private compareObjects(
188
- baseline: Record<string, unknown>,
189
- current: Record<string, unknown>,
190
- path: string,
191
- ): ResponseDiff[] {
192
- const differences: ResponseDiff[] = [];
193
- const allKeys = new Set([...Object.keys(baseline), ...Object.keys(current)]);
194
-
195
- for (const key of allKeys) {
196
- const keyPath = path ? `${path}.${key}` : key;
197
- const hasBaseline = key in baseline;
198
- const hasCurrent = key in current;
199
-
200
- if (!hasBaseline && hasCurrent) {
201
- if (!this.isExcluded(keyPath)) {
202
- differences.push({
203
- path: keyPath,
204
- baseline: undefined,
205
- current: current[key],
206
- type: 'added',
207
- });
208
- }
209
- } else if (hasBaseline && !hasCurrent) {
210
- if (!this.isExcluded(keyPath)) {
211
- differences.push({
212
- path: keyPath,
213
- baseline: baseline[key],
214
- current: undefined,
215
- type: 'removed',
216
- });
217
- }
218
- } else {
219
- const keyDiffs = this.deepCompare(baseline[key], current[key], keyPath);
220
- differences.push(...keyDiffs);
221
- }
222
- }
223
-
224
- return differences;
225
- }
226
-
227
- /**
228
- * Checks if a path should be excluded from comparison.
229
- */
230
- isExcluded(path: string): boolean {
231
- if (this.excludePaths.has(path)) {
232
- return true;
233
- }
234
-
235
- for (const pattern of this.excludePaths) {
236
- if (pattern.startsWith('*.')) {
237
- const suffix = pattern.slice(2);
238
- if (path.endsWith(`.${suffix}`)) {
239
- return true;
240
- }
241
- const lastPart = path.split('.').pop();
242
- if (lastPart === suffix) {
243
- return true;
244
- }
245
- }
246
-
247
- if (pattern.includes('[*]')) {
248
- const regex = new RegExp(
249
- `^${pattern.replace(/\[\*\]/g, '\\[\\d+\\]').replace(/\./g, '\\.')}$`,
250
- );
251
- if (regex.test(path)) {
252
- return true;
253
- }
254
- }
255
- }
256
-
257
- return false;
258
- }
259
-
260
- /**
261
- * Checks if a value matches a custom rule for its path.
262
- */
263
- matchesRule(path: string, value: unknown): boolean {
264
- const rule = this.matchRules.get(path);
265
- if (!rule) {
266
- return false;
267
- }
268
-
269
- if (rule === '*') {
270
- return true;
271
- }
272
-
273
- if (rule.startsWith('regex:')) {
274
- const pattern = rule.slice(6);
275
- try {
276
- const regex = new RegExp(pattern);
277
- return regex.test(String(value));
278
- } catch {
279
- return false;
280
- }
281
- }
282
-
283
- return false;
284
- }
285
-
286
- /**
287
- * Gets the type of a value for comparison.
288
- */
289
- private getType(value: unknown): string {
290
- if (value === null) {
291
- return 'null';
292
- }
293
- if (value === undefined) {
294
- return 'undefined';
295
- }
296
- if (Array.isArray(value)) {
297
- return 'array';
298
- }
299
- return typeof value;
300
- }
301
- }
302
-
303
- /**
304
- * Orchestrates response diffing between runs.
305
- */
306
- export class DiffOrchestrator {
307
- private manager: BaselineManager;
308
-
309
- constructor(globalConfig: GlobalDiffConfig = {}) {
310
- this.manager = new BaselineManager(globalConfig);
311
- }
312
-
313
- /**
314
- * Compares execution results against a baseline.
315
- */
316
- async compareWithBaseline(
317
- yamlPath: string,
318
- results: ExecutionResult[],
319
- currentLabel: string,
320
- baselineLabel: string,
321
- config: DiffConfig,
322
- ): Promise<DiffSummary> {
323
- const baselineFile = await this.manager.load(yamlPath, baselineLabel);
324
- const diffResults: DiffCompareResult[] = [];
325
-
326
- for (const result of results) {
327
- if (result.skipped || !result.success) {
328
- continue;
329
- }
330
-
331
- const requestName = result.request.name || result.request.url;
332
- const currentBaseline = this.manager.createBaseline(result, config);
333
-
334
- if (!baselineFile) {
335
- // No baseline exists - mark as new
336
- diffResults.push({
337
- requestName,
338
- hasDifferences: false,
339
- isNewBaseline: true,
340
- baselineLabel,
341
- currentLabel,
342
- differences: [],
343
- });
344
- continue;
345
- }
346
-
347
- const storedBaseline = baselineFile.baselines[requestName];
348
-
349
- if (!storedBaseline) {
350
- // Request not in baseline - mark as new
351
- diffResults.push({
352
- requestName,
353
- hasDifferences: false,
354
- isNewBaseline: true,
355
- baselineLabel,
356
- currentLabel,
357
- differences: [],
358
- });
359
- continue;
360
- }
361
-
362
- const differ = new ResponseDiffer(config);
363
- const compareResult = differ.compare(
364
- storedBaseline,
365
- currentBaseline,
366
- baselineLabel,
367
- currentLabel,
368
- requestName,
369
- );
370
- diffResults.push(compareResult);
371
- }
372
-
373
- return {
374
- totalRequests: diffResults.length,
375
- unchanged: diffResults.filter((r) => !r.hasDifferences && !r.isNewBaseline).length,
376
- changed: diffResults.filter((r) => r.hasDifferences).length,
377
- newBaselines: diffResults.filter((r) => r.isNewBaseline).length,
378
- results: diffResults,
379
- };
380
- }
381
-
382
- /**
383
- * Compares two stored baselines (offline comparison).
384
- */
385
- async compareTwoBaselines(
386
- yamlPath: string,
387
- label1: string,
388
- label2: string,
389
- config: DiffConfig,
390
- ): Promise<DiffSummary> {
391
- const file1 = await this.manager.load(yamlPath, label1);
392
- const file2 = await this.manager.load(yamlPath, label2);
393
-
394
- if (!file1) {
395
- throw new Error(`Baseline '${label1}' not found`);
396
- }
397
- if (!file2) {
398
- throw new Error(`Baseline '${label2}' not found`);
399
- }
400
-
401
- const allRequestNames = new Set([
402
- ...Object.keys(file1.baselines),
403
- ...Object.keys(file2.baselines),
404
- ]);
405
-
406
- const diffResults: DiffCompareResult[] = [];
407
- const differ = new ResponseDiffer(config);
408
-
409
- for (const requestName of allRequestNames) {
410
- const baseline1 = file1.baselines[requestName];
411
- const baseline2 = file2.baselines[requestName];
412
-
413
- if (!baseline1) {
414
- diffResults.push({
415
- requestName,
416
- hasDifferences: true,
417
- isNewBaseline: false,
418
- baselineLabel: label1,
419
- currentLabel: label2,
420
- differences: [
421
- {
422
- path: '',
423
- baseline: undefined,
424
- current: 'exists',
425
- type: 'added',
426
- },
427
- ],
428
- });
429
- continue;
430
- }
431
-
432
- if (!baseline2) {
433
- diffResults.push({
434
- requestName,
435
- hasDifferences: true,
436
- isNewBaseline: false,
437
- baselineLabel: label1,
438
- currentLabel: label2,
439
- differences: [
440
- {
441
- path: '',
442
- baseline: 'exists',
443
- current: undefined,
444
- type: 'removed',
445
- },
446
- ],
447
- });
448
- continue;
449
- }
450
-
451
- const compareResult = differ.compare(baseline1, baseline2, label1, label2, requestName);
452
- diffResults.push(compareResult);
453
- }
454
-
455
- return {
456
- totalRequests: diffResults.length,
457
- unchanged: diffResults.filter((r) => !r.hasDifferences).length,
458
- changed: diffResults.filter((r) => r.hasDifferences).length,
459
- newBaselines: 0,
460
- results: diffResults,
461
- };
462
- }
463
-
464
- /**
465
- * Saves current results as baseline.
466
- */
467
- async saveBaseline(
468
- yamlPath: string,
469
- label: string,
470
- results: ExecutionResult[],
471
- config: DiffConfig,
472
- ): Promise<void> {
473
- await this.manager.saveBaseline(yamlPath, label, results, config);
474
- }
475
-
476
- /**
477
- * Lists available baseline labels.
478
- */
479
- async listLabels(yamlPath: string): Promise<string[]> {
480
- return this.manager.listLabels(yamlPath);
481
- }
482
-
483
- /**
484
- * Gets the baseline manager instance.
485
- */
486
- getManager(): BaselineManager {
487
- return this.manager;
488
- }
489
- }
@@ -1,139 +0,0 @@
1
- import { describe, expect, test } from 'bun:test';
2
- import type { GlobalConfig, RequestConfig } from '../types/config';
3
-
4
- /**
5
- * Simulates the chunked execution logic from executeParallel
6
- * Returns the chunks that would be created based on maxConcurrency
7
- */
8
- function getExecutionChunks(
9
- requests: RequestConfig[],
10
- maxConcurrency: number | undefined,
11
- ): RequestConfig[][] {
12
- if (!maxConcurrency || maxConcurrency >= requests.length) {
13
- return [requests]; // All requests in a single batch
14
- }
15
-
16
- const chunks: RequestConfig[][] = [];
17
- for (let i = 0; i < requests.length; i += maxConcurrency) {
18
- chunks.push(requests.slice(i, i + maxConcurrency));
19
- }
20
- return chunks;
21
- }
22
-
23
- /**
24
- * Creates mock requests for testing
25
- */
26
- function createMockRequests(count: number): RequestConfig[] {
27
- return Array.from({ length: count }, (_, i) => ({
28
- url: `https://api.example.com/request/${i + 1}`,
29
- method: 'GET' as const,
30
- name: `Request ${i + 1}`,
31
- }));
32
- }
33
-
34
- describe('maxConcurrency parallel execution', () => {
35
- describe('chunk creation', () => {
36
- test('should execute all requests at once when maxConcurrency is not set', () => {
37
- const requests = createMockRequests(10);
38
- const chunks = getExecutionChunks(requests, undefined);
39
- expect(chunks).toHaveLength(1);
40
- expect(chunks[0]).toHaveLength(10);
41
- });
42
-
43
- test('should execute all requests at once when maxConcurrency >= requests.length', () => {
44
- const requests = createMockRequests(5);
45
- const chunks = getExecutionChunks(requests, 10);
46
- expect(chunks).toHaveLength(1);
47
- expect(chunks[0]).toHaveLength(5);
48
- });
49
-
50
- test('should create correct chunks when maxConcurrency is 1', () => {
51
- const requests = createMockRequests(3);
52
- const chunks = getExecutionChunks(requests, 1);
53
- expect(chunks).toHaveLength(3);
54
- expect(chunks[0]).toHaveLength(1);
55
- expect(chunks[1]).toHaveLength(1);
56
- expect(chunks[2]).toHaveLength(1);
57
- });
58
-
59
- test('should create correct chunks when maxConcurrency is 2', () => {
60
- const requests = createMockRequests(5);
61
- const chunks = getExecutionChunks(requests, 2);
62
- expect(chunks).toHaveLength(3);
63
- expect(chunks[0]).toHaveLength(2);
64
- expect(chunks[1]).toHaveLength(2);
65
- expect(chunks[2]).toHaveLength(1);
66
- });
67
-
68
- test('should create correct chunks when maxConcurrency is 3', () => {
69
- const requests = createMockRequests(10);
70
- const chunks = getExecutionChunks(requests, 3);
71
- expect(chunks).toHaveLength(4);
72
- expect(chunks[0]).toHaveLength(3);
73
- expect(chunks[1]).toHaveLength(3);
74
- expect(chunks[2]).toHaveLength(3);
75
- expect(chunks[3]).toHaveLength(1);
76
- });
77
-
78
- test('should handle edge case with maxConcurrency equal to requests length', () => {
79
- const requests = createMockRequests(5);
80
- const chunks = getExecutionChunks(requests, 5);
81
- expect(chunks).toHaveLength(1);
82
- expect(chunks[0]).toHaveLength(5);
83
- });
84
-
85
- test('should preserve request order across chunks', () => {
86
- const requests = createMockRequests(6);
87
- const chunks = getExecutionChunks(requests, 2);
88
-
89
- // Verify order is preserved
90
- expect(chunks[0][0].name).toBe('Request 1');
91
- expect(chunks[0][1].name).toBe('Request 2');
92
- expect(chunks[1][0].name).toBe('Request 3');
93
- expect(chunks[1][1].name).toBe('Request 4');
94
- expect(chunks[2][0].name).toBe('Request 5');
95
- expect(chunks[2][1].name).toBe('Request 6');
96
- });
97
- });
98
-
99
- describe('GlobalConfig maxConcurrency validation', () => {
100
- test('should accept valid maxConcurrency values', () => {
101
- const config1: GlobalConfig = { execution: 'parallel', maxConcurrency: 1 };
102
- expect(config1.maxConcurrency).toBe(1);
103
-
104
- const config2: GlobalConfig = { execution: 'parallel', maxConcurrency: 5 };
105
- expect(config2.maxConcurrency).toBe(5);
106
-
107
- const config3: GlobalConfig = { execution: 'parallel', maxConcurrency: 100 };
108
- expect(config3.maxConcurrency).toBe(100);
109
- });
110
-
111
- test('should allow undefined maxConcurrency', () => {
112
- const config: GlobalConfig = { execution: 'parallel' };
113
- expect(config.maxConcurrency).toBeUndefined();
114
- });
115
- });
116
-
117
- describe('integration with other settings', () => {
118
- test('maxConcurrency should coexist with continueOnError', () => {
119
- const config: GlobalConfig = {
120
- execution: 'parallel',
121
- maxConcurrency: 5,
122
- continueOnError: true,
123
- };
124
- expect(config.maxConcurrency).toBe(5);
125
- expect(config.continueOnError).toBe(true);
126
- });
127
-
128
- test('maxConcurrency should coexist with CI settings', () => {
129
- const config: GlobalConfig = {
130
- execution: 'parallel',
131
- maxConcurrency: 3,
132
- ci: { strictExit: true, failOn: 2 },
133
- };
134
- expect(config.maxConcurrency).toBe(3);
135
- expect(config.ci?.strictExit).toBe(true);
136
- expect(config.ci?.failOn).toBe(2);
137
- });
138
- });
139
- });