@curl-runner/cli 1.3.0 → 1.6.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 +1 -1
- package/src/parser/yaml.test.ts +304 -0
- package/src/parser/yaml.ts +137 -10
- package/dist/cli.js +0 -102
package/package.json
CHANGED
package/src/parser/yaml.test.ts
CHANGED
|
@@ -174,3 +174,307 @@ describe('YamlParser.resolveVariable', () => {
|
|
|
174
174
|
expect(result).toBe('store-value');
|
|
175
175
|
});
|
|
176
176
|
});
|
|
177
|
+
|
|
178
|
+
describe('YamlParser string transforms', () => {
|
|
179
|
+
test('should transform variable to uppercase with :upper', () => {
|
|
180
|
+
const variables = { ENV: 'production' };
|
|
181
|
+
const result = YamlParser.resolveVariable('ENV:upper', variables, {});
|
|
182
|
+
expect(result).toBe('PRODUCTION');
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test('should transform variable to lowercase with :lower', () => {
|
|
186
|
+
const variables = { RESOURCE: 'USERS' };
|
|
187
|
+
const result = YamlParser.resolveVariable('RESOURCE:lower', variables, {});
|
|
188
|
+
expect(result).toBe('users');
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test('should return null for transform on missing variable', () => {
|
|
192
|
+
const result = YamlParser.resolveVariable('MISSING:upper', {}, {});
|
|
193
|
+
expect(result).toBeNull();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test('should work with interpolateVariables for :upper transform', () => {
|
|
197
|
+
const obj = {
|
|
198
|
+
headers: {
|
|
199
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: Testing string transform
|
|
200
|
+
'X-Environment': '${ENV:upper}',
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
const variables = { ENV: 'production' };
|
|
204
|
+
const result = YamlParser.interpolateVariables(obj, variables);
|
|
205
|
+
expect(result).toEqual({
|
|
206
|
+
headers: {
|
|
207
|
+
'X-Environment': 'PRODUCTION',
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test('should work with interpolateVariables for :lower transform', () => {
|
|
213
|
+
const obj = {
|
|
214
|
+
headers: {
|
|
215
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: Testing string transform
|
|
216
|
+
'X-Resource': '${RESOURCE:lower}',
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
const variables = { RESOURCE: 'USERS' };
|
|
220
|
+
const result = YamlParser.interpolateVariables(obj, variables);
|
|
221
|
+
expect(result).toEqual({
|
|
222
|
+
headers: {
|
|
223
|
+
'X-Resource': 'users',
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test('should mix transforms with regular variables', () => {
|
|
229
|
+
const obj = {
|
|
230
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: Testing string transform
|
|
231
|
+
url: '${BASE_URL}/${RESOURCE:lower}',
|
|
232
|
+
headers: {
|
|
233
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: Testing string transform
|
|
234
|
+
'X-Environment': '${ENV:upper}',
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
const variables = { BASE_URL: 'https://api.example.com', RESOURCE: 'Users', ENV: 'staging' };
|
|
238
|
+
const result = YamlParser.interpolateVariables(obj, variables);
|
|
239
|
+
expect(result).toEqual({
|
|
240
|
+
url: 'https://api.example.com/users',
|
|
241
|
+
headers: {
|
|
242
|
+
'X-Environment': 'STAGING',
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test('should keep unresolved transforms as-is', () => {
|
|
248
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: Testing string transform
|
|
249
|
+
const obj = { value: '${MISSING:upper}' };
|
|
250
|
+
const result = YamlParser.interpolateVariables(obj, {});
|
|
251
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: Testing string transform
|
|
252
|
+
expect(result).toEqual({ value: '${MISSING:upper}' });
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
describe('YamlParser.resolveVariable with default values', () => {
|
|
257
|
+
test('should use default value when variable is not set', () => {
|
|
258
|
+
const result = YamlParser.resolveVariable('API_TIMEOUT:5000', {}, {});
|
|
259
|
+
expect(result).toBe('5000');
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test('should use variable value when set, ignoring default', () => {
|
|
263
|
+
const variables = { API_TIMEOUT: '10000' };
|
|
264
|
+
const result = YamlParser.resolveVariable('API_TIMEOUT:5000', variables, {});
|
|
265
|
+
expect(result).toBe('10000');
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test('should handle nested default with first variable set', () => {
|
|
269
|
+
const variables = { DATABASE_HOST: 'prod-db.example.com' };
|
|
270
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: Testing variable interpolation
|
|
271
|
+
const result = YamlParser.resolveVariable('DATABASE_HOST:${DB_HOST:localhost}', variables, {});
|
|
272
|
+
expect(result).toBe('prod-db.example.com');
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
test('should handle nested default with second variable set', () => {
|
|
276
|
+
const variables = { DB_HOST: 'staging-db.example.com' };
|
|
277
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: Testing variable interpolation
|
|
278
|
+
const result = YamlParser.resolveVariable('DATABASE_HOST:${DB_HOST:localhost}', variables, {});
|
|
279
|
+
expect(result).toBe('staging-db.example.com');
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
test('should use final fallback when no variables are set', () => {
|
|
283
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: Testing variable interpolation
|
|
284
|
+
const result = YamlParser.resolveVariable('DATABASE_HOST:${DB_HOST:localhost}', {}, {});
|
|
285
|
+
expect(result).toBe('localhost');
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
test('should not confuse DATE: with default syntax', () => {
|
|
289
|
+
const result = YamlParser.resolveVariable('DATE:YYYY-MM-DD', {}, {});
|
|
290
|
+
// Should return a formatted date, not treat 'YYYY-MM-DD' as a default
|
|
291
|
+
expect(result).toMatch(/^\d{4}-\d{2}-\d{2}$/);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test('should not confuse TIME: with default syntax', () => {
|
|
295
|
+
const result = YamlParser.resolveVariable('TIME:HH:mm:ss', {}, {});
|
|
296
|
+
// Should return a formatted time, not treat 'HH:mm:ss' as a default
|
|
297
|
+
expect(result).toMatch(/^\d{2}:\d{2}:\d{2}$/);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
test('should handle default value with special characters', () => {
|
|
301
|
+
const result = YamlParser.resolveVariable('API_URL:https://api.example.com/v1', {}, {});
|
|
302
|
+
expect(result).toBe('https://api.example.com/v1');
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
test('should handle empty default value', () => {
|
|
306
|
+
const result = YamlParser.resolveVariable('OPTIONAL:', {}, {});
|
|
307
|
+
expect(result).toBe('');
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
test('should use store variable when available before default', () => {
|
|
311
|
+
const storeContext = { token: 'stored-token' };
|
|
312
|
+
// Note: store variables have a different syntax (store.X), but we test
|
|
313
|
+
// that the default mechanism doesn't interfere
|
|
314
|
+
const result = YamlParser.resolveVariable('AUTH_TOKEN:default-token', {}, storeContext);
|
|
315
|
+
expect(result).toBe('default-token');
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
describe('YamlParser.interpolateVariables with default values', () => {
|
|
320
|
+
test('should interpolate variable with default in string', () => {
|
|
321
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: Testing variable interpolation
|
|
322
|
+
const obj = { timeout: '${API_TIMEOUT:5000}' };
|
|
323
|
+
const result = YamlParser.interpolateVariables(obj, {});
|
|
324
|
+
expect(result).toEqual({ timeout: '5000' });
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
test('should interpolate variable with value set over default', () => {
|
|
328
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: Testing variable interpolation
|
|
329
|
+
const obj = { timeout: '${API_TIMEOUT:5000}' };
|
|
330
|
+
const variables = { API_TIMEOUT: '10000' };
|
|
331
|
+
const result = YamlParser.interpolateVariables(obj, variables);
|
|
332
|
+
expect(result).toEqual({ timeout: '10000' });
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
test('should handle multiple variables with defaults in one string', () => {
|
|
336
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: Testing variable interpolation
|
|
337
|
+
const obj = { url: '${BASE_URL:https://api.example.com}/${VERSION:v1}/users' };
|
|
338
|
+
const result = YamlParser.interpolateVariables(obj, {});
|
|
339
|
+
expect(result).toEqual({ url: 'https://api.example.com/v1/users' });
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
test('should mix defaults with regular variables', () => {
|
|
343
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: Testing variable interpolation
|
|
344
|
+
const obj = { url: '${BASE_URL}/api/${VERSION:v1}/users' };
|
|
345
|
+
const variables = { BASE_URL: 'https://prod.example.com' };
|
|
346
|
+
const result = YamlParser.interpolateVariables(obj, variables);
|
|
347
|
+
expect(result).toEqual({ url: 'https://prod.example.com/api/v1/users' });
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
test('should handle nested defaults in interpolation', () => {
|
|
351
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: Testing variable interpolation
|
|
352
|
+
const obj = { host: '${DATABASE_HOST:${DB_HOST:localhost}}' };
|
|
353
|
+
const result = YamlParser.interpolateVariables(obj, {});
|
|
354
|
+
expect(result).toEqual({ host: 'localhost' });
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
test('should preserve DATE: formatting syntax', () => {
|
|
358
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: Testing variable interpolation
|
|
359
|
+
const obj = { date: '${DATE:YYYY-MM-DD}' };
|
|
360
|
+
const result = YamlParser.interpolateVariables(obj, {}) as { date: string };
|
|
361
|
+
expect(result.date).toMatch(/^\d{4}-\d{2}-\d{2}$/);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
test('should preserve TIME: formatting syntax', () => {
|
|
365
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: Testing variable interpolation
|
|
366
|
+
const obj = { time: '${TIME:HH:mm:ss}' };
|
|
367
|
+
const result = YamlParser.interpolateVariables(obj, {}) as { time: string };
|
|
368
|
+
expect(result.time).toMatch(/^\d{2}:\d{2}:\d{2}$/);
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
describe('YamlParser.resolveDynamicVariable', () => {
|
|
373
|
+
test('should resolve UUID to a valid full UUID', () => {
|
|
374
|
+
const result = YamlParser.resolveDynamicVariable('UUID');
|
|
375
|
+
expect(result).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i);
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
test('should resolve UUID:short to first 8 characters of a UUID', () => {
|
|
379
|
+
const result = YamlParser.resolveDynamicVariable('UUID:short');
|
|
380
|
+
expect(result).toMatch(/^[0-9a-f]{8}$/i);
|
|
381
|
+
expect(result).toHaveLength(8);
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
test('should resolve RANDOM:min-max to a number within range', () => {
|
|
385
|
+
const result = YamlParser.resolveDynamicVariable('RANDOM:1-100');
|
|
386
|
+
expect(result).not.toBeNull();
|
|
387
|
+
const num = Number(result);
|
|
388
|
+
expect(num).toBeGreaterThanOrEqual(1);
|
|
389
|
+
expect(num).toBeLessThanOrEqual(100);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
test('should resolve RANDOM:min-max with large range', () => {
|
|
393
|
+
const result = YamlParser.resolveDynamicVariable('RANDOM:1-1000');
|
|
394
|
+
expect(result).not.toBeNull();
|
|
395
|
+
const num = Number(result);
|
|
396
|
+
expect(num).toBeGreaterThanOrEqual(1);
|
|
397
|
+
expect(num).toBeLessThanOrEqual(1000);
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
test('should resolve RANDOM:min-max with same min and max', () => {
|
|
401
|
+
const result = YamlParser.resolveDynamicVariable('RANDOM:5-5');
|
|
402
|
+
expect(result).toBe('5');
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
test('should resolve RANDOM:string:length to alphanumeric string of correct length', () => {
|
|
406
|
+
const result = YamlParser.resolveDynamicVariable('RANDOM:string:10');
|
|
407
|
+
expect(result).not.toBeNull();
|
|
408
|
+
expect(result).toHaveLength(10);
|
|
409
|
+
expect(result).toMatch(/^[A-Za-z0-9]+$/);
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
test('should resolve RANDOM:string:length with different lengths', () => {
|
|
413
|
+
const result5 = YamlParser.resolveDynamicVariable('RANDOM:string:5');
|
|
414
|
+
const result20 = YamlParser.resolveDynamicVariable('RANDOM:string:20');
|
|
415
|
+
expect(result5).toHaveLength(5);
|
|
416
|
+
expect(result20).toHaveLength(20);
|
|
417
|
+
expect(result5).toMatch(/^[A-Za-z0-9]+$/);
|
|
418
|
+
expect(result20).toMatch(/^[A-Za-z0-9]+$/);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
test('should resolve TIMESTAMP to a numeric string', () => {
|
|
422
|
+
const result = YamlParser.resolveDynamicVariable('TIMESTAMP');
|
|
423
|
+
expect(result).toMatch(/^\d+$/);
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
test('should resolve CURRENT_TIME to a numeric string', () => {
|
|
427
|
+
const result = YamlParser.resolveDynamicVariable('CURRENT_TIME');
|
|
428
|
+
expect(result).toMatch(/^\d+$/);
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
test('should return null for unknown dynamic variable', () => {
|
|
432
|
+
const result = YamlParser.resolveDynamicVariable('UNKNOWN');
|
|
433
|
+
expect(result).toBeNull();
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
describe('YamlParser.interpolateVariables with new dynamic variables', () => {
|
|
438
|
+
test('should interpolate UUID:short in objects', () => {
|
|
439
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: Testing ${UUID:short} interpolation
|
|
440
|
+
const obj = { id: '${UUID:short}' };
|
|
441
|
+
const result = YamlParser.interpolateVariables(obj, {}) as typeof obj;
|
|
442
|
+
expect(result.id).toMatch(/^[0-9a-f]{8}$/i);
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
test('should interpolate RANDOM:min-max in objects', () => {
|
|
446
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: Testing ${RANDOM:x-y} interpolation
|
|
447
|
+
const obj = { value: '${RANDOM:1-100}' };
|
|
448
|
+
const result = YamlParser.interpolateVariables(obj, {}) as typeof obj;
|
|
449
|
+
const num = Number(result.value);
|
|
450
|
+
expect(num).toBeGreaterThanOrEqual(1);
|
|
451
|
+
expect(num).toBeLessThanOrEqual(100);
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
test('should interpolate RANDOM:string:length in objects', () => {
|
|
455
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: Testing ${RANDOM:string:n} interpolation
|
|
456
|
+
const obj = { token: '${RANDOM:string:16}' };
|
|
457
|
+
const result = YamlParser.interpolateVariables(obj, {}) as typeof obj;
|
|
458
|
+
expect(result.token).toHaveLength(16);
|
|
459
|
+
expect(result.token).toMatch(/^[A-Za-z0-9]+$/);
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
test('should interpolate multiple new dynamic variables in one object', () => {
|
|
463
|
+
const obj = {
|
|
464
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: Testing dynamic variable interpolation
|
|
465
|
+
sessionId: '${UUID:short}',
|
|
466
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: Testing dynamic variable interpolation
|
|
467
|
+
randomNum: '${RANDOM:1-1000}',
|
|
468
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: Testing dynamic variable interpolation
|
|
469
|
+
randomStr: '${RANDOM:string:10}',
|
|
470
|
+
};
|
|
471
|
+
const result = YamlParser.interpolateVariables(obj, {}) as typeof obj;
|
|
472
|
+
|
|
473
|
+
expect(result.sessionId).toMatch(/^[0-9a-f]{8}$/i);
|
|
474
|
+
const num = Number(result.randomNum);
|
|
475
|
+
expect(num).toBeGreaterThanOrEqual(1);
|
|
476
|
+
expect(num).toBeLessThanOrEqual(1000);
|
|
477
|
+
expect(result.randomStr).toHaveLength(10);
|
|
478
|
+
expect(result.randomStr).toMatch(/^[A-Za-z0-9]+$/);
|
|
479
|
+
});
|
|
480
|
+
});
|
package/src/parser/yaml.ts
CHANGED
|
@@ -18,6 +18,8 @@ export class YamlParser {
|
|
|
18
18
|
* - Static variables: ${VAR_NAME}
|
|
19
19
|
* - Dynamic variables: ${UUID}, ${TIMESTAMP}, ${DATE:format}, ${TIME:format}
|
|
20
20
|
* - Stored response values: ${store.variableName}
|
|
21
|
+
* - Default values: ${VAR_NAME:default} - uses 'default' if VAR_NAME is not found
|
|
22
|
+
* - Nested defaults: ${VAR1:${VAR2:fallback}} - tries VAR1, then VAR2, then 'fallback'
|
|
21
23
|
*
|
|
22
24
|
* @param obj - The object to interpolate
|
|
23
25
|
* @param variables - Static variables map
|
|
@@ -29,19 +31,35 @@ export class YamlParser {
|
|
|
29
31
|
storeContext?: ResponseStoreContext,
|
|
30
32
|
): unknown {
|
|
31
33
|
if (typeof obj === 'string') {
|
|
32
|
-
//
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
// Extract all variable references with proper brace matching
|
|
35
|
+
const extractedVars = YamlParser.extractVariables(obj);
|
|
36
|
+
|
|
37
|
+
if (extractedVars.length === 0) {
|
|
38
|
+
return obj;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Check if it's a single variable that spans the entire string
|
|
42
|
+
if (
|
|
43
|
+
extractedVars.length === 1 &&
|
|
44
|
+
extractedVars[0].start === 0 &&
|
|
45
|
+
extractedVars[0].end === obj.length
|
|
46
|
+
) {
|
|
47
|
+
const varName = extractedVars[0].name;
|
|
36
48
|
const resolvedValue = YamlParser.resolveVariable(varName, variables, storeContext);
|
|
37
49
|
return resolvedValue !== null ? resolvedValue : obj;
|
|
38
50
|
}
|
|
39
51
|
|
|
40
|
-
// Handle multiple variables in the string
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
52
|
+
// Handle multiple variables in the string
|
|
53
|
+
let result = '';
|
|
54
|
+
let lastEnd = 0;
|
|
55
|
+
for (const varRef of extractedVars) {
|
|
56
|
+
result += obj.slice(lastEnd, varRef.start);
|
|
57
|
+
const resolvedValue = YamlParser.resolveVariable(varRef.name, variables, storeContext);
|
|
58
|
+
result += resolvedValue !== null ? resolvedValue : obj.slice(varRef.start, varRef.end);
|
|
59
|
+
lastEnd = varRef.end;
|
|
60
|
+
}
|
|
61
|
+
result += obj.slice(lastEnd);
|
|
62
|
+
return result;
|
|
45
63
|
}
|
|
46
64
|
|
|
47
65
|
if (Array.isArray(obj)) {
|
|
@@ -61,7 +79,7 @@ export class YamlParser {
|
|
|
61
79
|
|
|
62
80
|
/**
|
|
63
81
|
* Resolves a single variable reference.
|
|
64
|
-
* Priority: store context > dynamic variables > static variables
|
|
82
|
+
* Priority: store context > string transforms > dynamic variables > static variables > default values
|
|
65
83
|
*/
|
|
66
84
|
static resolveVariable(
|
|
67
85
|
varName: string,
|
|
@@ -77,6 +95,52 @@ export class YamlParser {
|
|
|
77
95
|
return null; // Store variable not found, return null to keep original
|
|
78
96
|
}
|
|
79
97
|
|
|
98
|
+
// Check for string transforms: ${VAR:upper} or ${VAR:lower}
|
|
99
|
+
const transformMatch = varName.match(/^([^:]+):(upper|lower)$/);
|
|
100
|
+
if (transformMatch) {
|
|
101
|
+
const baseVarName = transformMatch[1];
|
|
102
|
+
const transform = transformMatch[2];
|
|
103
|
+
const baseValue = variables[baseVarName] || process.env[baseVarName];
|
|
104
|
+
|
|
105
|
+
if (baseValue) {
|
|
106
|
+
return transform === 'upper' ? baseValue.toUpperCase() : baseValue.toLowerCase();
|
|
107
|
+
}
|
|
108
|
+
return null; // Base variable not found
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Check for default value syntax: ${VAR:default}
|
|
112
|
+
// Must check before dynamic variables to properly handle defaults
|
|
113
|
+
const colonIndex = varName.indexOf(':');
|
|
114
|
+
if (colonIndex !== -1) {
|
|
115
|
+
const actualVarName = varName.slice(0, colonIndex);
|
|
116
|
+
const defaultValue = varName.slice(colonIndex + 1);
|
|
117
|
+
|
|
118
|
+
// Don't confuse with DATE:, TIME:, UUID:, RANDOM: patterns
|
|
119
|
+
// These are reserved prefixes for dynamic variable generation
|
|
120
|
+
const reservedPrefixes = ['DATE', 'TIME', 'UUID', 'RANDOM'];
|
|
121
|
+
if (!reservedPrefixes.includes(actualVarName)) {
|
|
122
|
+
// Try to resolve the actual variable name
|
|
123
|
+
const resolved = YamlParser.resolveVariable(actualVarName, variables, storeContext);
|
|
124
|
+
if (resolved !== null) {
|
|
125
|
+
return resolved;
|
|
126
|
+
}
|
|
127
|
+
// Variable not found, use the default value
|
|
128
|
+
// The default value might itself be a variable reference like ${OTHER_VAR:fallback}
|
|
129
|
+
// Note: Due to the regex in interpolateVariables using [^}]+, nested braces
|
|
130
|
+
// get truncated (e.g., "${VAR:${OTHER:default}}" captures "VAR:${OTHER:default")
|
|
131
|
+
// So we check for both complete ${...} and truncated ${... patterns
|
|
132
|
+
if (defaultValue.startsWith('${')) {
|
|
133
|
+
// Handle both complete ${VAR} and truncated ${VAR (from nested braces)
|
|
134
|
+
const nestedVarName = defaultValue.endsWith('}')
|
|
135
|
+
? defaultValue.slice(2, -1)
|
|
136
|
+
: defaultValue.slice(2);
|
|
137
|
+
const nestedResolved = YamlParser.resolveVariable(nestedVarName, variables, storeContext);
|
|
138
|
+
return nestedResolved !== null ? nestedResolved : defaultValue;
|
|
139
|
+
}
|
|
140
|
+
return defaultValue;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
80
144
|
// Check for dynamic variable
|
|
81
145
|
const dynamicValue = YamlParser.resolveDynamicVariable(varName);
|
|
82
146
|
if (dynamicValue !== null) {
|
|
@@ -97,6 +161,29 @@ export class YamlParser {
|
|
|
97
161
|
return crypto.randomUUID();
|
|
98
162
|
}
|
|
99
163
|
|
|
164
|
+
// UUID:short - first segment (8 chars) of a UUID
|
|
165
|
+
if (varName === 'UUID:short') {
|
|
166
|
+
return crypto.randomUUID().split('-')[0];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// RANDOM:min-max - random number in range
|
|
170
|
+
const randomRangeMatch = varName.match(/^RANDOM:(\d+)-(\d+)$/);
|
|
171
|
+
if (randomRangeMatch) {
|
|
172
|
+
const min = Number(randomRangeMatch[1]);
|
|
173
|
+
const max = Number(randomRangeMatch[2]);
|
|
174
|
+
return String(Math.floor(Math.random() * (max - min + 1)) + min);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// RANDOM:string:length - random alphanumeric string
|
|
178
|
+
const randomStringMatch = varName.match(/^RANDOM:string:(\d+)$/);
|
|
179
|
+
if (randomStringMatch) {
|
|
180
|
+
const length = Number(randomStringMatch[1]);
|
|
181
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
182
|
+
return Array.from({ length }, () =>
|
|
183
|
+
chars.charAt(Math.floor(Math.random() * chars.length)),
|
|
184
|
+
).join('');
|
|
185
|
+
}
|
|
186
|
+
|
|
100
187
|
// Current timestamp variations
|
|
101
188
|
if (varName === 'CURRENT_TIME' || varName === 'TIMESTAMP') {
|
|
102
189
|
return Date.now().toString();
|
|
@@ -133,6 +220,46 @@ export class YamlParser {
|
|
|
133
220
|
return format.replace('HH', hours).replace('mm', minutes).replace('ss', seconds);
|
|
134
221
|
}
|
|
135
222
|
|
|
223
|
+
/**
|
|
224
|
+
* Extracts variable references from a string, properly handling nested braces.
|
|
225
|
+
* For example, "${VAR:${OTHER:default}}" is extracted as a single variable reference.
|
|
226
|
+
*/
|
|
227
|
+
static extractVariables(str: string): Array<{ start: number; end: number; name: string }> {
|
|
228
|
+
const variables: Array<{ start: number; end: number; name: string }> = [];
|
|
229
|
+
let i = 0;
|
|
230
|
+
|
|
231
|
+
while (i < str.length) {
|
|
232
|
+
// Look for ${
|
|
233
|
+
if (str[i] === '$' && str[i + 1] === '{') {
|
|
234
|
+
const start = i;
|
|
235
|
+
i += 2; // Skip past ${
|
|
236
|
+
let braceCount = 1;
|
|
237
|
+
const nameStart = i;
|
|
238
|
+
|
|
239
|
+
// Find the matching closing brace
|
|
240
|
+
while (i < str.length && braceCount > 0) {
|
|
241
|
+
if (str[i] === '{') {
|
|
242
|
+
braceCount++;
|
|
243
|
+
} else if (str[i] === '}') {
|
|
244
|
+
braceCount--;
|
|
245
|
+
}
|
|
246
|
+
i++;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (braceCount === 0) {
|
|
250
|
+
// Found matching closing brace
|
|
251
|
+
const name = str.slice(nameStart, i - 1); // Exclude the closing }
|
|
252
|
+
variables.push({ start, end: i, name });
|
|
253
|
+
}
|
|
254
|
+
// If braceCount > 0, we have unmatched braces - skip this variable
|
|
255
|
+
} else {
|
|
256
|
+
i++;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return variables;
|
|
261
|
+
}
|
|
262
|
+
|
|
136
263
|
static mergeConfigs(base: Partial<RequestConfig>, override: RequestConfig): RequestConfig {
|
|
137
264
|
return {
|
|
138
265
|
...base,
|
package/dist/cli.js
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
// @bun
|
|
3
|
-
var x=Object.create;var{getPrototypeOf:y,defineProperty:q,getOwnPropertyNames:f}=Object;var p=Object.prototype.hasOwnProperty;var g=($,K,Q)=>{Q=$!=null?x(y($)):{};let Z=K||!$||!$.__esModule?q(Q,"default",{value:$,enumerable:!0}):Q;for(let X of f($))if(!p.call(Z,X))q(Z,X,{get:()=>$[X],enumerable:!0});return Z};var E=import.meta.require;var{Glob:u}=globalThis.Bun;var{YAML:N}=globalThis.Bun;class G{static async parseFile($){let Q=await Bun.file($).text();return N.parse(Q)}static parse($){return N.parse($)}static interpolateVariables($,K,Q){if(typeof $==="string"){let Z=$.match(/^\$\{([^}]+)\}$/);if(Z){let X=Z[1],z=G.resolveVariable(X,K,Q);return z!==null?z:$}return $.replace(/\$\{([^}]+)\}/g,(X,z)=>{let W=G.resolveVariable(z,K,Q);return W!==null?W:X})}if(Array.isArray($))return $.map((Z)=>G.interpolateVariables(Z,K,Q));if($&&typeof $==="object"){let Z={};for(let[X,z]of Object.entries($))Z[X]=G.interpolateVariables(z,K,Q);return Z}return $}static resolveVariable($,K,Q){if($.startsWith("store.")&&Q){let X=$.slice(6);if(X in Q)return Q[X];return null}let Z=G.resolveDynamicVariable($);if(Z!==null)return Z;if($ in K)return K[$];return null}static resolveDynamicVariable($){if($==="UUID")return crypto.randomUUID();if($==="CURRENT_TIME"||$==="TIMESTAMP")return Date.now().toString();if($.startsWith("DATE:")){let K=$.slice(5);return G.formatDate(new Date,K)}if($.startsWith("TIME:")){let K=$.slice(5);return G.formatTime(new Date,K)}return null}static formatDate($,K){let Q=$.getFullYear(),Z=String($.getMonth()+1).padStart(2,"0"),X=String($.getDate()).padStart(2,"0");return K.replace("YYYY",Q.toString()).replace("MM",Z).replace("DD",X)}static formatTime($,K){let Q=String($.getHours()).padStart(2,"0"),Z=String($.getMinutes()).padStart(2,"0"),X=String($.getSeconds()).padStart(2,"0");return K.replace("HH",Q).replace("mm",Z).replace("ss",X)}static mergeConfigs($,K){return{...$,...K,headers:{...$.headers,...K.headers},params:{...$.params,...K.params},variables:{...$.variables,...K.variables}}}}function d($){return typeof $==="object"&&$!==null&&"file"in $}function P($){return $.replace(/'/g,"'\\''")}class j{static buildCommand($){let K=["curl"];if(K.push("-X",$.method||"GET"),K.push("-w",'"\\n__CURL_METRICS_START__%{json}__CURL_METRICS_END__"'),$.headers)for(let[Z,X]of Object.entries($.headers))K.push("-H",`"${Z}: ${X}"`);if($.auth){if($.auth.type==="basic"&&$.auth.username&&$.auth.password)K.push("-u",`"${$.auth.username}:${$.auth.password}"`);else if($.auth.type==="bearer"&&$.auth.token)K.push("-H",`"Authorization: Bearer ${$.auth.token}"`)}if($.formData)for(let[Z,X]of Object.entries($.formData))if(d(X)){let z=`@${X.file}`;if(X.filename)z+=`;filename=${X.filename}`;if(X.contentType)z+=`;type=${X.contentType}`;K.push("-F",`'${Z}=${P(z)}'`)}else{let z=String(X);K.push("-F",`'${Z}=${P(z)}'`)}else if($.body){let Z=typeof $.body==="string"?$.body:JSON.stringify($.body);if(K.push("-d",`'${Z.replace(/'/g,"'\\''")}'`),!$.headers?.["Content-Type"])K.push("-H",'"Content-Type: application/json"')}if($.timeout)K.push("--max-time",$.timeout.toString());if($.followRedirects!==!1){if(K.push("-L"),$.maxRedirects)K.push("--max-redirs",$.maxRedirects.toString())}if($.proxy)K.push("-x",$.proxy);if($.insecure)K.push("-k");if($.output)K.push("-o",$.output);K.push("-s","-S");let Q=$.url;if($.params&&Object.keys($.params).length>0){let Z=new URLSearchParams($.params).toString();Q+=(Q.includes("?")?"&":"?")+Z}return K.push(`"${Q}"`),K.join(" ")}static async executeCurl($){try{let K=Bun.spawn(["sh","-c",$],{stdout:"pipe",stderr:"pipe"}),Q=await new Response(K.stdout).text(),Z=await new Response(K.stderr).text();if(await K.exited,K.exitCode!==0&&!Q)return{success:!1,error:Z||`Command failed with exit code ${K.exitCode}`};let X=Q,z={},W=Q.match(/__CURL_METRICS_START__(.+?)__CURL_METRICS_END__/);if(W){X=Q.replace(/__CURL_METRICS_START__.+?__CURL_METRICS_END__/,"").trim();try{z=JSON.parse(W[1])}catch(J){}}let U={};if(z.response_code){let J=Z.split(`
|
|
4
|
-
`).filter((w)=>w.includes(":"));for(let w of J){let[_,...H]=w.split(":");if(_&&H.length>0)U[_.trim()]=H.join(":").trim()}}return{success:!0,status:z.response_code||z.http_code,headers:U,body:X,metrics:{duration:(z.time_total||0)*1000,size:z.size_download,dnsLookup:(z.time_namelookup||0)*1000,tcpConnection:(z.time_connect||0)*1000,tlsHandshake:(z.time_appconnect||0)*1000,firstByte:(z.time_starttransfer||0)*1000,download:(z.time_total||0)*1000}}}catch(K){return{success:!1,error:K instanceof Error?K.message:String(K)}}}}class L{colors;constructor($){this.colors=$}color($,K){if(!K||!this.colors[K])return $;return`${this.colors[K]}${$}${this.colors.reset}`}render($,K=" "){$.forEach((Q,Z)=>{let X=Z===$.length-1,z=X?`${K}\u2514\u2500`:`${K}\u251C\u2500`;if(Q.label&&Q.value){let W=Q.color?this.color(Q.value,Q.color):Q.value,U=W.split(`
|
|
5
|
-
`);if(U.length===1)console.log(`${z} ${Q.label}: ${W}`);else{console.log(`${z} ${Q.label}:`);let J=X?`${K} `:`${K}\u2502 `;U.forEach((w)=>{console.log(`${J}${w}`)})}}else if(Q.label&&!Q.value)console.log(`${z} ${Q.label}:`);else if(!Q.label&&Q.value){let W=X?`${K} `:`${K}\u2502 `;console.log(`${W}${Q.value}`)}if(Q.children&&Q.children.length>0){let W=X?`${K} `:`${K}\u2502 `;this.render(Q.children,W)}})}}class M{config;colors={reset:"\x1B[0m",bright:"\x1B[1m",dim:"\x1B[2m",underscore:"\x1B[4m",black:"\x1B[30m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",magenta:"\x1B[35m",cyan:"\x1B[36m",white:"\x1B[37m",bgBlack:"\x1B[40m",bgRed:"\x1B[41m",bgGreen:"\x1B[42m",bgYellow:"\x1B[43m",bgBlue:"\x1B[44m",bgMagenta:"\x1B[45m",bgCyan:"\x1B[46m",bgWhite:"\x1B[47m"};constructor($={}){this.config={verbose:!1,showHeaders:!1,showBody:!0,showMetrics:!1,format:"pretty",prettyLevel:"minimal",...$}}color($,K){return`${this.colors[K]}${$}${this.colors.reset}`}getShortFilename($){return $.replace(/.*\//,"").replace(".yaml","")}shouldShowOutput(){if(this.config.format==="raw")return!1;if(this.config.format==="pretty")return!0;return this.config.verbose!==!1}shouldShowHeaders(){if(this.config.format!=="pretty")return this.config.showHeaders||!1;switch(this.config.prettyLevel||"standard"){case"minimal":return!1;case"standard":return this.config.showHeaders||!1;case"detailed":return!0;default:return this.config.showHeaders||!1}}shouldShowBody(){if(this.config.format!=="pretty")return this.config.showBody!==!1;switch(this.config.prettyLevel||"standard"){case"minimal":return!1;case"standard":return this.config.showBody!==!1;case"detailed":return!0;default:return this.config.showBody!==!1}}shouldShowMetrics(){if(this.config.format!=="pretty")return this.config.showMetrics||!1;switch(this.config.prettyLevel||"standard"){case"minimal":return!1;case"standard":return this.config.showMetrics||!1;case"detailed":return!0;default:return this.config.showMetrics||!1}}shouldShowRequestDetails(){if(this.config.format!=="pretty")return this.config.verbose||!1;switch(this.config.prettyLevel||"standard"){case"minimal":return!1;case"standard":return this.config.verbose||!1;case"detailed":return!0;default:return this.config.verbose||!1}}shouldShowSeparators(){if(this.config.format!=="pretty")return!0;switch(this.config.prettyLevel||"standard"){case"minimal":return!1;case"standard":return!0;case"detailed":return!0;default:return!0}}colorStatusCode($){return this.color($,"yellow")}logValidationErrors($){let K=$.split("; ");if(K.length===1){let Q=K[0].trim(),Z=Q.match(/^Expected status (.+?), got (.+)$/);if(Z){let[,X,z]=Z,W=this.colorStatusCode(X.replace(" or ","|")),U=this.color(z,"red");console.log(` ${this.color("\u2717","red")} ${this.color("Error:","red")} Expected ${this.color("status","yellow")} ${W}, got ${U}`)}else console.log(` ${this.color("\u2717","red")} ${this.color("Error:","red")} ${Q}`)}else{console.log(` ${this.color("\u2717","red")} ${this.color("Validation Errors:","red")}`);for(let Q of K){let Z=Q.trim();if(Z)if(Z.startsWith("Expected ")){let X=Z.match(/^Expected status (.+?), got (.+)$/);if(X){let[,z,W]=X,U=this.colorStatusCode(z.replace(" or ","|")),J=this.color(W,"red");console.log(` ${this.color("\u2022","red")} ${this.color("status","yellow")}: expected ${U}, got ${J}`)}else{let z=Z.match(/^Expected (.+?) to be (.+?), got (.+)$/);if(z){let[,W,U,J]=z;console.log(` ${this.color("\u2022","red")} ${this.color(W,"yellow")}: expected ${this.color(U,"green")}, got ${this.color(J,"red")}`)}else console.log(` ${this.color("\u2022","red")} ${Z}`)}}else console.log(` ${this.color("\u2022","red")} ${Z}`)}}}formatJson($){if(this.config.format==="raw")return typeof $==="string"?$:JSON.stringify($);if(this.config.format==="json")return JSON.stringify($);return JSON.stringify($,null,2)}formatDuration($){if($<1000)return`${$.toFixed(0)}ms`;return`${($/1000).toFixed(2)}s`}formatSize($){if(!$)return"0 B";let K=["B","KB","MB","GB"],Q=Math.floor(Math.log($)/Math.log(1024));return`${($/1024**Q).toFixed(2)} ${K[Q]}`}logExecutionStart($,K){if(!this.shouldShowOutput())return;if(this.shouldShowSeparators())console.log(),console.log(this.color(`Executing ${$} request(s) in ${K} mode`,"dim")),console.log();else console.log()}logRequestStart($,K){return}logCommand($){if(this.shouldShowRequestDetails())console.log(this.color(" Command:","dim")),console.log(this.color(` ${$}`,"dim"))}logRetry($,K){console.log(this.color(` \u21BB Retry ${$}/${K}...`,"yellow"))}logRequestComplete($){if(this.config.format==="raw"){if($.success&&this.config.showBody&&$.body){let U=this.formatJson($.body);console.log(U)}return}if(this.config.format==="json"){let U={request:{name:$.request.name,url:$.request.url,method:$.request.method||"GET"},success:$.success,status:$.status,...this.shouldShowHeaders()&&$.headers?{headers:$.headers}:{},...this.shouldShowBody()&&$.body?{body:$.body}:{},...$.error?{error:$.error}:{},...this.shouldShowMetrics()&&$.metrics?{metrics:$.metrics}:{}};console.log(JSON.stringify(U,null,2));return}if(!this.shouldShowOutput())return;let K=this.config.prettyLevel||"minimal",Q=$.success?"green":"red",Z=$.success?"\u2713":"x",X=$.request.name||"Request";if(K==="minimal"){let U=$.request.sourceFile?this.getShortFilename($.request.sourceFile):"inline";console.log(`${this.color(Z,Q)} ${this.color(X,"bright")} [${U}]`);let J=[],w=new L(this.colors);J.push({label:$.request.method||"GET",value:$.request.url,color:"blue"});let _=$.status?`${$.status}`:"ERROR";if(J.push({label:`${Z} Status`,value:_,color:Q}),$.metrics){let H=`${this.formatDuration($.metrics.duration)} | ${this.formatSize($.metrics.size)}`;J.push({label:"Duration",value:H,color:"cyan"})}if(w.render(J),$.error)console.log(),this.logValidationErrors($.error);console.log();return}console.log(`${this.color(Z,Q)} ${this.color(X,"bright")}`);let z=[],W=new L(this.colors);if(z.push({label:"URL",value:$.request.url,color:"blue"}),z.push({label:"Method",value:$.request.method||"GET",color:"yellow"}),z.push({label:"Status",value:String($.status||"ERROR"),color:Q}),$.metrics)z.push({label:"Duration",value:this.formatDuration($.metrics.duration),color:"cyan"});if(this.shouldShowHeaders()&&$.headers&&Object.keys($.headers).length>0){let U=Object.entries($.headers).map(([J,w])=>({label:this.color(J,"dim"),value:String(w)}));z.push({label:"Headers",children:U})}if(this.shouldShowBody()&&$.body){let J=this.formatJson($.body).split(`
|
|
6
|
-
`),w=this.shouldShowRequestDetails()?1/0:10,_=J.slice(0,w);if(J.length>w)_.push(this.color(`... (${J.length-w} more lines)`,"dim"));z.push({label:"Response Body",value:_.join(`
|
|
7
|
-
`)})}if(this.shouldShowMetrics()&&$.metrics&&K==="detailed"){let U=$.metrics,J=[];if(J.push({label:"Request Duration",value:this.formatDuration(U.duration),color:"cyan"}),U.size!==void 0)J.push({label:"Response Size",value:this.formatSize(U.size),color:"cyan"});if(U.dnsLookup)J.push({label:"DNS Lookup",value:this.formatDuration(U.dnsLookup),color:"cyan"});if(U.tcpConnection)J.push({label:"TCP Connection",value:this.formatDuration(U.tcpConnection),color:"cyan"});if(U.tlsHandshake)J.push({label:"TLS Handshake",value:this.formatDuration(U.tlsHandshake),color:"cyan"});if(U.firstByte)J.push({label:"Time to First Byte",value:this.formatDuration(U.firstByte),color:"cyan"});z.push({label:"Metrics",children:J})}if(W.render(z),$.error)console.log(),this.logValidationErrors($.error);console.log()}logSummary($,K=!1){if(this.config.format==="raw")return;if(this.config.format==="json"){let U={summary:{total:$.total,successful:$.successful,failed:$.failed,duration:$.duration},results:$.results.map((J)=>({request:{name:J.request.name,url:J.request.url,method:J.request.method||"GET"},success:J.success,status:J.status,...this.shouldShowHeaders()&&J.headers?{headers:J.headers}:{},...this.shouldShowBody()&&J.body?{body:J.body}:{},...J.error?{error:J.error}:{},...this.shouldShowMetrics()&&J.metrics?{metrics:J.metrics}:{}}))};console.log(JSON.stringify(U,null,2));return}if(!this.shouldShowOutput())return;let Q=this.config.prettyLevel||"minimal";if(K)console.log();if(Q==="minimal"){let U=$.failed===0?"green":"red",J=$.failed===0?`${$.total} request${$.total===1?"":"s"} completed successfully`:`${$.successful}/${$.total} request${$.total===1?"":"s"} completed, ${$.failed} failed`;console.log(`${K?"\u25C6 Global Summary":"Summary"}: ${this.color(J,U)}`);return}let Z=($.successful/$.total*100).toFixed(1),X=$.failed===0?"green":"red",z=$.failed===0?`${$.total} request${$.total===1?"":"s"} completed successfully`:`${$.successful}/${$.total} request${$.total===1?"":"s"} completed, ${$.failed} failed`,W=K?"\u25C6 Global Summary":"Summary";if(console.log(),console.log(`${W}: ${this.color(z,X)} (${this.color(this.formatDuration($.duration),"cyan")})`),$.failed>0&&this.shouldShowRequestDetails())$.results.filter((U)=>!U.success).forEach((U)=>{let J=U.request.name||U.request.url;console.log(` ${this.color("\u2022","red")} ${J}: ${U.error}`)})}logError($){console.error(this.color(`\u2717 ${$}`,"red"))}logWarning($){console.warn(this.color(`\u26A0 ${$}`,"yellow"))}logInfo($){console.log(this.color(`\u2139 ${$}`,"blue"))}logSuccess($){console.log(this.color(`\u2713 ${$}`,"green"))}logFileHeader($,K){if(!this.shouldShowOutput()||this.config.format!=="pretty")return;let Q=$.replace(/.*\//,"").replace(".yaml","");console.log(),console.log(this.color(`\u25B6 ${Q}.yaml`,"bright")+this.color(` (${K} request${K===1?"":"s"})`,"dim"))}}function m($,K){let Q=K.split("."),Z=$;for(let X of Q){if(Z===null||Z===void 0)return;if(typeof Z!=="object")return;let z=X.match(/^(\w+)\[(\d+)\]$/);if(z){let[,W,U]=z,J=Number.parseInt(U,10);if(Z=Z[W],Array.isArray(Z))Z=Z[J];else return}else if(/^\d+$/.test(X)&&Array.isArray(Z))Z=Z[Number.parseInt(X,10)];else Z=Z[X]}return Z}function c($){if($===void 0||$===null)return"";if(typeof $==="string")return $;if(typeof $==="number"||typeof $==="boolean")return String($);return JSON.stringify($)}function h($,K){let Q={},Z={status:$.status,headers:$.headers||{},body:$.body,metrics:$.metrics};for(let[X,z]of Object.entries(K)){let W=m(Z,z);Q[X]=c(W)}return Q}function b(){return{}}class S{logger;globalConfig;constructor($={}){this.globalConfig=$,this.logger=new M($.output)}mergeOutputConfig($){return{...this.globalConfig.output,...$.sourceOutputConfig}}isFileAttachment($){return typeof $==="object"&&$!==null&&"file"in $}async validateFileAttachments($){if(!$.formData)return;let K=[];for(let[Q,Z]of Object.entries($.formData))if(this.isFileAttachment(Z)){let X=Z.file;if(!await Bun.file(X).exists())K.push(`${Q}: ${X}`)}if(K.length>0)return`File(s) not found: ${K.join(", ")}`;return}async executeRequest($,K=0){let Q=performance.now(),Z=this.mergeOutputConfig($),X=new M(Z);X.logRequestStart($,K);let z=await this.validateFileAttachments($);if(z){let H={request:$,success:!1,error:z,metrics:{duration:performance.now()-Q}};return X.logRequestComplete(H),H}let W=j.buildCommand($);X.logCommand(W);let U=0,J,w=($.retry?.count||0)+1;while(U<w){if(U>0){if(X.logRetry(U,w-1),$.retry?.delay)await Bun.sleep($.retry.delay)}let H=await j.executeCurl(W);if(H.success){let D=H.body;try{if(H.headers?.["content-type"]?.includes("application/json")||D&&(D.trim().startsWith("{")||D.trim().startsWith("[")))D=JSON.parse(D)}catch(T){}let I={request:$,success:!0,status:H.status,headers:H.headers,body:D,metrics:{...H.metrics,duration:performance.now()-Q}};if($.expect){let T=this.validateResponse(I,$.expect);if(!T.success)I.success=!1,I.error=T.error}return X.logRequestComplete(I),I}J=H.error,U++}let _={request:$,success:!1,error:J,metrics:{duration:performance.now()-Q}};return X.logRequestComplete(_),_}validateResponse($,K){if(!K)return{success:!0};let Q=[];if(K.status!==void 0){let X=Array.isArray(K.status)?K.status:[K.status];if(!X.includes($.status||0))Q.push(`Expected status ${X.join(" or ")}, got ${$.status}`)}if(K.headers)for(let[X,z]of Object.entries(K.headers)){let W=$.headers?.[X]||$.headers?.[X.toLowerCase()];if(W!==z)Q.push(`Expected header ${X}="${z}", got "${W}"`)}if(K.body!==void 0){let X=this.validateBodyProperties($.body,K.body,"");if(X.length>0)Q.push(...X)}if(K.responseTime!==void 0&&$.metrics){let X=$.metrics.duration;if(!this.validateRangePattern(X,K.responseTime))Q.push(`Expected response time to match ${K.responseTime}ms, got ${X.toFixed(2)}ms`)}let Z=Q.length>0;if(K.failure===!0){if(Z)return{success:!1,error:Q.join("; ")};let X=$.status||0;if(X>=400)return{success:!0};else return{success:!1,error:`Expected request to fail (4xx/5xx) but got status ${X}`}}else if(Z)return{success:!1,error:Q.join("; ")};else return{success:!0}}validateBodyProperties($,K,Q){let Z=[];if(typeof K!=="object"||K===null){let X=this.validateValue($,K,Q||"body");if(!X.isValid)Z.push(X.error);return Z}if(Array.isArray(K)){let X=this.validateValue($,K,Q||"body");if(!X.isValid)Z.push(X.error);return Z}for(let[X,z]of Object.entries(K)){let W=Q?`${Q}.${X}`:X,U;if(Array.isArray($)&&this.isArraySelector(X))U=this.getArrayValue($,X);else U=$?.[X];if(typeof z==="object"&&z!==null&&!Array.isArray(z)){let J=this.validateBodyProperties(U,z,W);Z.push(...J)}else{let J=this.validateValue(U,z,W);if(!J.isValid)Z.push(J.error)}}return Z}validateValue($,K,Q){if(K==="*")return{isValid:!0};if(Array.isArray(K)){if(!K.some((X)=>{if(X==="*")return!0;if(typeof X==="string"&&this.isRegexPattern(X))return this.validateRegexPattern($,X);if(typeof X==="string"&&this.isRangePattern(X))return this.validateRangePattern($,X);return $===X}))return{isValid:!1,error:`Expected ${Q} to match one of ${JSON.stringify(K)}, got ${JSON.stringify($)}`};return{isValid:!0}}if(typeof K==="string"&&this.isRegexPattern(K)){if(!this.validateRegexPattern($,K))return{isValid:!1,error:`Expected ${Q} to match pattern ${K}, got ${JSON.stringify($)}`};return{isValid:!0}}if(typeof K==="string"&&this.isRangePattern(K)){if(!this.validateRangePattern($,K))return{isValid:!1,error:`Expected ${Q} to match range ${K}, got ${JSON.stringify($)}`};return{isValid:!0}}if(K==="null"||K===null){if($!==null)return{isValid:!1,error:`Expected ${Q} to be null, got ${JSON.stringify($)}`};return{isValid:!0}}if($!==K)return{isValid:!1,error:`Expected ${Q} to be ${JSON.stringify(K)}, got ${JSON.stringify($)}`};return{isValid:!0}}isRegexPattern($){return $.startsWith("^")||$.endsWith("$")||$.includes("\\d")||$.includes("\\w")||$.includes("\\s")||$.includes("[")||$.includes("*")||$.includes("+")||$.includes("?")}validateRegexPattern($,K){let Q=String($);try{return new RegExp(K).test(Q)}catch{return!1}}isRangePattern($){return/^(>=?|<=?|>|<)\s*[\d.-]+(\s*,\s*(>=?|<=?|>|<)\s*[\d.-]+)*$/.test($)}validateRangePattern($,K){let Q=Number($);if(Number.isNaN(Q))return!1;let Z=K.match(/^([\d.-]+)\s*-\s*([\d.-]+)$/);if(Z){let z=Number(Z[1]),W=Number(Z[2]);return Q>=z&&Q<=W}return K.split(",").map((z)=>z.trim()).every((z)=>{let W=z.match(/^(>=?|<=?|>|<)\s*([\d.-]+)$/);if(!W)return!1;let U=W[1],J=Number(W[2]);switch(U){case">":return Q>J;case">=":return Q>=J;case"<":return Q<J;case"<=":return Q<=J;default:return!1}})}isArraySelector($){return/^\[.*\]$/.test($)||$==="*"||$.startsWith("slice(")}getArrayValue($,K){if(K==="*")return $;if(K.startsWith("[")&&K.endsWith("]")){let Q=K.slice(1,-1);if(Q==="*")return $;let Z=Number(Q);if(!Number.isNaN(Z))return Z>=0?$[Z]:$[$.length+Z]}if(K.startsWith("slice(")){let Q=K.match(/slice\((\d+)(?:,(\d+))?\)/);if(Q){let Z=Number(Q[1]),X=Q[2]?Number(Q[2]):void 0;return $.slice(Z,X)}}return}async executeSequential($){let K=performance.now(),Q=[],Z=b();for(let X=0;X<$.length;X++){let z=this.interpolateStoreVariables($[X],Z),W=await this.executeRequest(z,X+1);if(Q.push(W),W.success&&z.store){let U=h(W,z.store);Object.assign(Z,U),this.logStoredValues(U)}if(!W.success&&!this.globalConfig.continueOnError){this.logger.logError("Stopping execution due to error");break}}return this.createSummary(Q,performance.now()-K)}interpolateStoreVariables($,K){if(Object.keys(K).length===0)return $;return G.interpolateVariables($,{},K)}logStoredValues($){if(Object.keys($).length===0)return;let K=Object.entries($);for(let[Q,Z]of K){let X=Z.length>50?`${Z.substring(0,50)}...`:Z;this.logger.logInfo(`Stored: ${Q} = "${X}"`)}}async executeParallel($){let K=performance.now(),Q=$.map((X,z)=>this.executeRequest(X,z+1)),Z=await Promise.all(Q);return this.createSummary(Z,performance.now()-K)}async execute($){this.logger.logExecutionStart($.length,this.globalConfig.execution||"sequential");let K=this.globalConfig.execution==="parallel"?await this.executeParallel($):await this.executeSequential($);if(this.logger.logSummary(K),this.globalConfig.output?.saveToFile)await this.saveSummaryToFile(K);return K}createSummary($,K){let Q=$.filter((X)=>X.success).length,Z=$.filter((X)=>!X.success).length;return{total:$.length,successful:Q,failed:Z,duration:K,results:$}}async saveSummaryToFile($){let K=this.globalConfig.output?.saveToFile;if(!K)return;let Q=JSON.stringify($,null,2);await Bun.write(K,Q),this.logger.logInfo(`Results saved to ${K}`)}}function B(){if(typeof BUILD_VERSION<"u")return BUILD_VERSION;if(process.env.CURL_RUNNER_VERSION)return process.env.CURL_RUNNER_VERSION;try{let $=["../package.json","./package.json","../../package.json"];for(let K of $)try{let Q=E(K);if(Q.name==="@curl-runner/cli"&&Q.version)return Q.version}catch{}return"0.0.0"}catch{return"0.0.0"}}var V={reset:"\x1B[0m",bright:"\x1B[1m",dim:"\x1B[2m",underscore:"\x1B[4m",black:"\x1B[30m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",magenta:"\x1B[35m",cyan:"\x1B[36m",white:"\x1B[37m",bgBlack:"\x1B[40m",bgRed:"\x1B[41m",bgGreen:"\x1B[42m",bgYellow:"\x1B[43m",bgBlue:"\x1B[44m",bgMagenta:"\x1B[45m",bgCyan:"\x1B[46m",bgWhite:"\x1B[47m"};function O($,K){return`${V[K]}${$}${V.reset}`}var C=`${process.env.HOME}/.curl-runner-version-cache.json`,i=86400000,n="https://registry.npmjs.org/@curl-runner/cli/latest";class R{async checkForUpdates($=!1){try{if(process.env.CI)return;let K=B();if(K==="0.0.0")return;if(!$){let Z=await this.getCachedVersion();if(Z&&Date.now()-Z.lastCheck<i){this.compareVersions(K,Z.latestVersion);return}}let Q=await this.fetchLatestVersion();if(Q)await this.setCachedVersion(Q),this.compareVersions(K,Q)}catch{}}async fetchLatestVersion(){try{let $=await fetch(n,{signal:AbortSignal.timeout(3000)});if(!$.ok)return null;return(await $.json()).version}catch{return null}}compareVersions($,K){if(this.isNewerVersion($,K))console.log(),console.log(O("\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E","yellow")),console.log(O("\u2502","yellow")+" "+O("\u2502","yellow")),console.log(O("\u2502","yellow")+" "+O("\uD83D\uDCE6 New version available!","bright")+` ${O($,"red")} \u2192 ${O(K,"green")} `+O("\u2502","yellow")),console.log(O("\u2502","yellow")+" "+O("\u2502","yellow")),console.log(O("\u2502","yellow")+" Update with: "+O("npm install -g @curl-runner/cli","cyan")+" "+O("\u2502","yellow")),console.log(O("\u2502","yellow")+" or: "+O("bun install -g @curl-runner/cli","cyan")+" "+O("\u2502","yellow")),console.log(O("\u2502","yellow")+" "+O("\u2502","yellow")),console.log(O("\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F","yellow")),console.log()}isNewerVersion($,K){try{let Q=$.replace(/^v/,""),Z=K.replace(/^v/,""),X=Q.split(".").map(Number),z=Z.split(".").map(Number);for(let W=0;W<Math.max(X.length,z.length);W++){let U=X[W]||0,J=z[W]||0;if(J>U)return!0;if(J<U)return!1}return!1}catch{return!1}}async getCachedVersion(){try{let $=Bun.file(C);if(await $.exists())return JSON.parse(await $.text())}catch{}return null}async setCachedVersion($){try{let K={lastCheck:Date.now(),latestVersion:$};await Bun.write(C,JSON.stringify(K))}catch{}}}class v{logger=new M;async loadConfigFile(){let $=["curl-runner.yaml","curl-runner.yml",".curl-runner.yaml",".curl-runner.yml"];for(let K of $)try{if(await Bun.file(K).exists()){let Z=await G.parseFile(K),X=Z.global||Z;return this.logger.logInfo(`Loaded configuration from ${K}`),X}}catch(Q){this.logger.logWarning(`Failed to load configuration from ${K}: ${Q}`)}return{}}loadEnvironmentVariables(){let $={};if(process.env.CURL_RUNNER_TIMEOUT)$.defaults={...$.defaults,timeout:Number.parseInt(process.env.CURL_RUNNER_TIMEOUT,10)};if(process.env.CURL_RUNNER_RETRIES)$.defaults={...$.defaults,retry:{...$.defaults?.retry,count:Number.parseInt(process.env.CURL_RUNNER_RETRIES,10)}};if(process.env.CURL_RUNNER_RETRY_DELAY)$.defaults={...$.defaults,retry:{...$.defaults?.retry,delay:Number.parseInt(process.env.CURL_RUNNER_RETRY_DELAY,10)}};if(process.env.CURL_RUNNER_VERBOSE)$.output={...$.output,verbose:process.env.CURL_RUNNER_VERBOSE.toLowerCase()==="true"};if(process.env.CURL_RUNNER_EXECUTION)$.execution=process.env.CURL_RUNNER_EXECUTION;if(process.env.CURL_RUNNER_CONTINUE_ON_ERROR)$.continueOnError=process.env.CURL_RUNNER_CONTINUE_ON_ERROR.toLowerCase()==="true";if(process.env.CURL_RUNNER_OUTPUT_FORMAT){let K=process.env.CURL_RUNNER_OUTPUT_FORMAT;if(["json","pretty","raw"].includes(K))$.output={...$.output,format:K}}if(process.env.CURL_RUNNER_PRETTY_LEVEL){let K=process.env.CURL_RUNNER_PRETTY_LEVEL;if(["minimal","standard","detailed"].includes(K))$.output={...$.output,prettyLevel:K}}if(process.env.CURL_RUNNER_OUTPUT_FILE)$.output={...$.output,saveToFile:process.env.CURL_RUNNER_OUTPUT_FILE};if(process.env.CURL_RUNNER_STRICT_EXIT)$.ci={...$.ci,strictExit:process.env.CURL_RUNNER_STRICT_EXIT.toLowerCase()==="true"};if(process.env.CURL_RUNNER_FAIL_ON)$.ci={...$.ci,failOn:Number.parseInt(process.env.CURL_RUNNER_FAIL_ON,10)};if(process.env.CURL_RUNNER_FAIL_ON_PERCENTAGE){let K=Number.parseFloat(process.env.CURL_RUNNER_FAIL_ON_PERCENTAGE);if(K>=0&&K<=100)$.ci={...$.ci,failOnPercentage:K}}return $}async run($){try{let{files:K,options:Q}=this.parseArguments($);if(!Q.version&&!Q.help)new R().checkForUpdates().catch(()=>{});if(Q.help){this.showHelp();return}if(Q.version){console.log(`curl-runner v${B()}`);return}let Z=this.loadEnvironmentVariables(),X=await this.loadConfigFile(),z=await this.findYamlFiles(K,Q);if(z.length===0)this.logger.logError("No YAML files found"),process.exit(1);this.logger.logInfo(`Found ${z.length} YAML file(s)`);let W=this.mergeGlobalConfigs(Z,X),U=[],J=[];for(let D of z){this.logger.logInfo(`Processing: ${D}`);let{requests:I,config:T}=await this.processYamlFile(D),A=T?.output||{},F=I.map((Y)=>({...Y,sourceOutputConfig:A,sourceFile:D}));if(T){let{...Y}=T;W=this.mergeGlobalConfigs(W,Y)}J.push({file:D,requests:F,config:T}),U.push(...F)}if(Q.execution)W.execution=Q.execution;if(Q.continueOnError!==void 0)W.continueOnError=Q.continueOnError;if(Q.verbose!==void 0)W.output={...W.output,verbose:Q.verbose};if(Q.quiet!==void 0)W.output={...W.output,verbose:!1};if(Q.output)W.output={...W.output,saveToFile:Q.output};if(Q.outputFormat)W.output={...W.output,format:Q.outputFormat};if(Q.prettyLevel)W.output={...W.output,prettyLevel:Q.prettyLevel};if(Q.showHeaders!==void 0)W.output={...W.output,showHeaders:Q.showHeaders};if(Q.showBody!==void 0)W.output={...W.output,showBody:Q.showBody};if(Q.showMetrics!==void 0)W.output={...W.output,showMetrics:Q.showMetrics};if(Q.timeout)W.defaults={...W.defaults,timeout:Q.timeout};if(Q.retries||Q.noRetry){let D=Q.noRetry?0:Q.retries||0;W.defaults={...W.defaults,retry:{...W.defaults?.retry,count:D}}}if(Q.retryDelay)W.defaults={...W.defaults,retry:{...W.defaults?.retry,delay:Q.retryDelay}};if(Q.strictExit!==void 0)W.ci={...W.ci,strictExit:Q.strictExit};if(Q.failOn!==void 0)W.ci={...W.ci,failOn:Q.failOn};if(Q.failOnPercentage!==void 0)W.ci={...W.ci,failOnPercentage:Q.failOnPercentage};if(U.length===0)this.logger.logError("No requests found in YAML files"),process.exit(1);let w=new S(W),_;if(J.length>1){let D=[],I=0;for(let F=0;F<J.length;F++){let Y=J[F];this.logger.logFileHeader(Y.file,Y.requests.length);let k=await w.execute(Y.requests);if(D.push(...k.results),I+=k.duration,F<J.length-1)console.log()}let T=D.filter((F)=>F.success).length,A=D.filter((F)=>!F.success).length;_={total:D.length,successful:T,failed:A,duration:I,results:D},w.logger.logSummary(_,!0)}else _=await w.execute(U);let H=this.determineExitCode(_,W);process.exit(H)}catch(K){this.logger.logError(K instanceof Error?K.message:String(K)),process.exit(1)}}parseArguments($){let K={},Q=[];for(let Z=0;Z<$.length;Z++){let X=$[Z];if(X.startsWith("--")){let z=X.slice(2),W=$[Z+1];if(z==="help"||z==="version")K[z]=!0;else if(z==="no-retry")K.noRetry=!0;else if(z==="quiet")K.quiet=!0;else if(z==="show-headers")K.showHeaders=!0;else if(z==="show-body")K.showBody=!0;else if(z==="show-metrics")K.showMetrics=!0;else if(z==="strict-exit")K.strictExit=!0;else if(W&&!W.startsWith("--")){if(z==="continue-on-error")K.continueOnError=W==="true";else if(z==="verbose")K.verbose=W==="true";else if(z==="timeout")K.timeout=Number.parseInt(W,10);else if(z==="retries")K.retries=Number.parseInt(W,10);else if(z==="retry-delay")K.retryDelay=Number.parseInt(W,10);else if(z==="fail-on")K.failOn=Number.parseInt(W,10);else if(z==="fail-on-percentage"){let U=Number.parseFloat(W);if(U>=0&&U<=100)K.failOnPercentage=U}else if(z==="output-format"){if(["json","pretty","raw"].includes(W))K.outputFormat=W}else if(z==="pretty-level"){if(["minimal","standard","detailed"].includes(W))K.prettyLevel=W}else K[z]=W;Z++}else K[z]=!0}else if(X.startsWith("-")){let z=X.slice(1);for(let W of z)switch(W){case"h":K.help=!0;break;case"v":K.verbose=!0;break;case"p":K.execution="parallel";break;case"c":K.continueOnError=!0;break;case"q":K.quiet=!0;break;case"o":{let U=$[Z+1];if(U&&!U.startsWith("-"))K.output=U,Z++;break}}}else Q.push(X)}return{files:Q,options:K}}async findYamlFiles($,K){let Q=new Set,Z=[];if($.length===0)Z=K.all?["**/*.yaml","**/*.yml"]:["*.yaml","*.yml"];else for(let X of $)try{let W=await(await import("fs/promises")).stat(X);if(W.isDirectory()){if(Z.push(`${X}/*.yaml`,`${X}/*.yml`),K.all)Z.push(`${X}/**/*.yaml`,`${X}/**/*.yml`)}else if(W.isFile())Z.push(X)}catch{Z.push(X)}for(let X of Z){let z=new u(X);for await(let W of z.scan("."))if(W.endsWith(".yaml")||W.endsWith(".yml"))Q.add(W)}return Array.from(Q).sort()}async processYamlFile($){let K=await G.parseFile($),Q=[],Z;if(K.global)Z=K.global;let X={...K.global?.variables,...K.collection?.variables},z={...K.global?.defaults,...K.collection?.defaults};if(K.request){let W=this.prepareRequest(K.request,X,z);Q.push(W)}if(K.requests)for(let W of K.requests){let U=this.prepareRequest(W,X,z);Q.push(U)}if(K.collection?.requests)for(let W of K.collection.requests){let U=this.prepareRequest(W,X,z);Q.push(U)}return{requests:Q,config:Z}}prepareRequest($,K,Q){let Z=G.interpolateVariables($,K);return G.mergeConfigs(Q,Z)}mergeGlobalConfigs($,K){return{...$,...K,variables:{...$.variables,...K.variables},output:{...$.output,...K.output},defaults:{...$.defaults,...K.defaults},ci:{...$.ci,...K.ci}}}determineExitCode($,K){let{failed:Q,total:Z}=$,X=K.ci;if(Q===0)return 0;if(X){if(X.strictExit)return 1;if(X.failOn!==void 0&&Q>X.failOn)return 1;if(X.failOnPercentage!==void 0&&Z>0){if(Q/Z*100>X.failOnPercentage)return 1}if(X.failOn!==void 0||X.failOnPercentage!==void 0)return 0}return!K.continueOnError?1:0}showHelp(){console.log(`
|
|
8
|
-
${this.logger.color("\uD83D\uDE80 CURL RUNNER","bright")}
|
|
9
|
-
|
|
10
|
-
${this.logger.color("USAGE:","yellow")}
|
|
11
|
-
curl-runner [files...] [options]
|
|
12
|
-
|
|
13
|
-
${this.logger.color("OPTIONS:","yellow")}
|
|
14
|
-
-h, --help Show this help message
|
|
15
|
-
-v, --verbose Enable verbose output
|
|
16
|
-
-q, --quiet Suppress non-error output
|
|
17
|
-
-p, --execution parallel Execute requests in parallel
|
|
18
|
-
-c, --continue-on-error Continue execution on errors
|
|
19
|
-
-o, --output <file> Save results to file
|
|
20
|
-
--all Find all YAML files recursively
|
|
21
|
-
--timeout <ms> Set request timeout in milliseconds
|
|
22
|
-
--retries <count> Set maximum retry attempts
|
|
23
|
-
--retry-delay <ms> Set delay between retries in milliseconds
|
|
24
|
-
--no-retry Disable retry mechanism
|
|
25
|
-
--output-format <format> Set output format (json|pretty|raw)
|
|
26
|
-
--pretty-level <level> Set pretty format level (minimal|standard|detailed)
|
|
27
|
-
--show-headers Include response headers in output
|
|
28
|
-
--show-body Include response body in output
|
|
29
|
-
--show-metrics Include performance metrics in output
|
|
30
|
-
--version Show version
|
|
31
|
-
|
|
32
|
-
${this.logger.color("CI/CD OPTIONS:","yellow")}
|
|
33
|
-
--strict-exit Exit with code 1 if any validation fails (for CI/CD)
|
|
34
|
-
--fail-on <count> Exit with code 1 if failures exceed this count
|
|
35
|
-
--fail-on-percentage <pct> Exit with code 1 if failure percentage exceeds this value
|
|
36
|
-
|
|
37
|
-
${this.logger.color("EXAMPLES:","yellow")}
|
|
38
|
-
# Run all YAML files in current directory
|
|
39
|
-
curl-runner
|
|
40
|
-
|
|
41
|
-
# Run specific file
|
|
42
|
-
curl-runner api-tests.yaml
|
|
43
|
-
|
|
44
|
-
# Run all files in a directory
|
|
45
|
-
curl-runner examples/
|
|
46
|
-
|
|
47
|
-
# Run all files in multiple directories
|
|
48
|
-
curl-runner tests/ examples/
|
|
49
|
-
|
|
50
|
-
# Run all files recursively in parallel
|
|
51
|
-
curl-runner --all -p
|
|
52
|
-
|
|
53
|
-
# Run directory recursively
|
|
54
|
-
curl-runner --all examples/
|
|
55
|
-
|
|
56
|
-
# Run with verbose output and continue on errors
|
|
57
|
-
curl-runner tests/*.yaml -vc
|
|
58
|
-
|
|
59
|
-
# Run with minimal pretty output (only status and errors)
|
|
60
|
-
curl-runner --output-format pretty --pretty-level minimal test.yaml
|
|
61
|
-
|
|
62
|
-
# Run with detailed pretty output (show all information)
|
|
63
|
-
curl-runner --output-format pretty --pretty-level detailed test.yaml
|
|
64
|
-
|
|
65
|
-
# CI/CD: Fail if any validation fails (strict mode)
|
|
66
|
-
curl-runner tests/ --strict-exit
|
|
67
|
-
|
|
68
|
-
# CI/CD: Run all tests but fail if any validation fails
|
|
69
|
-
curl-runner tests/ --continue-on-error --strict-exit
|
|
70
|
-
|
|
71
|
-
# CI/CD: Allow up to 2 failures
|
|
72
|
-
curl-runner tests/ --fail-on 2
|
|
73
|
-
|
|
74
|
-
# CI/CD: Allow up to 10% failures
|
|
75
|
-
curl-runner tests/ --fail-on-percentage 10
|
|
76
|
-
|
|
77
|
-
${this.logger.color("YAML STRUCTURE:","yellow")}
|
|
78
|
-
Single request:
|
|
79
|
-
request:
|
|
80
|
-
url: https://api.example.com
|
|
81
|
-
method: GET
|
|
82
|
-
|
|
83
|
-
Multiple requests:
|
|
84
|
-
requests:
|
|
85
|
-
- url: https://api.example.com/users
|
|
86
|
-
method: GET
|
|
87
|
-
- url: https://api.example.com/posts
|
|
88
|
-
method: POST
|
|
89
|
-
body: { title: "Test" }
|
|
90
|
-
|
|
91
|
-
With global config:
|
|
92
|
-
global:
|
|
93
|
-
execution: parallel
|
|
94
|
-
variables:
|
|
95
|
-
BASE_URL: https://api.example.com
|
|
96
|
-
requests:
|
|
97
|
-
- url: \${BASE_URL}/users
|
|
98
|
-
method: GET
|
|
99
|
-
`)}}var l=new v;l.run(process.argv.slice(2));
|
|
100
|
-
|
|
101
|
-
//# debugId=EA3EA83C7A39EBEE64756E2164756E21
|
|
102
|
-
//# sourceMappingURL=cli.js.map
|