qa360 2.0.12 → 2.0.13

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 (42) hide show
  1. package/dist/commands/ai.js +26 -14
  2. package/dist/commands/ask.d.ts +75 -23
  3. package/dist/commands/ask.js +413 -265
  4. package/dist/commands/crawl.d.ts +24 -0
  5. package/dist/commands/crawl.js +121 -0
  6. package/dist/commands/history.js +38 -3
  7. package/dist/commands/init.d.ts +89 -95
  8. package/dist/commands/init.js +282 -200
  9. package/dist/commands/run.d.ts +1 -0
  10. package/dist/core/adapters/playwright-ui.d.ts +45 -7
  11. package/dist/core/adapters/playwright-ui.js +365 -59
  12. package/dist/core/assertions/engine.d.ts +51 -0
  13. package/dist/core/assertions/engine.js +530 -0
  14. package/dist/core/assertions/index.d.ts +11 -0
  15. package/dist/core/assertions/index.js +11 -0
  16. package/dist/core/assertions/types.d.ts +121 -0
  17. package/dist/core/assertions/types.js +37 -0
  18. package/dist/core/crawler/index.d.ts +57 -0
  19. package/dist/core/crawler/index.js +281 -0
  20. package/dist/core/crawler/journey-generator.d.ts +49 -0
  21. package/dist/core/crawler/journey-generator.js +412 -0
  22. package/dist/core/crawler/page-analyzer.d.ts +88 -0
  23. package/dist/core/crawler/page-analyzer.js +709 -0
  24. package/dist/core/crawler/selector-generator.d.ts +34 -0
  25. package/dist/core/crawler/selector-generator.js +240 -0
  26. package/dist/core/crawler/types.d.ts +353 -0
  27. package/dist/core/crawler/types.js +6 -0
  28. package/dist/core/generation/crawler-pack-generator.d.ts +44 -0
  29. package/dist/core/generation/crawler-pack-generator.js +231 -0
  30. package/dist/core/generation/index.d.ts +2 -0
  31. package/dist/core/generation/index.js +2 -0
  32. package/dist/core/index.d.ts +3 -0
  33. package/dist/core/index.js +4 -0
  34. package/dist/core/types/pack-v1.d.ts +90 -0
  35. package/dist/index.js +6 -2
  36. package/examples/accessibility.yml +39 -16
  37. package/examples/api-basic.yml +19 -14
  38. package/examples/complete.yml +134 -42
  39. package/examples/fullstack.yml +66 -31
  40. package/examples/security.yml +47 -15
  41. package/examples/ui-basic.yml +16 -12
  42. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  /**
2
- * QA360 Ask Command - Natural language to pack.yml generation
2
+ * QA360 Ask Command - Natural language to pack.yml generation (v2)
3
3
  */
4
4
  import { writeFileSync } from 'fs';
5
5
  import { join } from 'path';
@@ -29,14 +29,14 @@ export class QA360Ask {
29
29
  type: 'input',
30
30
  name: 'description',
31
31
  message: 'Description du pack:',
32
- default: 'Pack de tests QA360 généré automatiquement'
32
+ default: 'Pack de tests QA360 genere automatiquement'
33
33
  },
34
34
  {
35
35
  type: 'list',
36
36
  name: 'type',
37
37
  message: 'Type de tests principal:',
38
38
  choices: [
39
- { name: '[WEB] API REST/GraphQL', value: 'api' },
39
+ { name: '[API] API REST/GraphQL', value: 'api' },
40
40
  { name: '[UI] Interface Web (UI)', value: 'web' },
41
41
  { name: '[PERF] Performance', value: 'performance' },
42
42
  { name: '[SEC] Securite', value: 'security' },
@@ -55,7 +55,7 @@ export class QA360Ask {
55
55
  }
56
56
  async parseNaturalLanguage(query) {
57
57
  console.log(chalk.gray(`Analyse: "${query}"`));
58
- // Détection de patterns simples (Phase 1 - déterministe)
58
+ // Detection de patterns simples (Phase 1 - deterministe)
59
59
  const patterns = {
60
60
  api: /\b(api|rest|graphql|endpoint|swagger|openapi)\b/i,
61
61
  web: /\b(web|ui|interface|page|site|browser)\b/i,
@@ -92,309 +92,443 @@ export class QA360Ask {
92
92
  .substring(0, 30) || 'generated-pack';
93
93
  return this.generatePackFromAnswers({
94
94
  name,
95
- description: `Pack généré depuis: "${query}"`,
95
+ description: `Pack genere depuis: "${query}"`,
96
96
  type,
97
97
  target
98
98
  });
99
99
  }
100
100
  generatePackFromAnswers(answers) {
101
- const baseConfig = {
102
- name: answers.name,
103
- version: '1.0.0',
104
- description: answers.description,
105
- adapters: [],
106
- tests: [],
107
- environment: {
108
- TARGET_URL: answers.target
109
- }
110
- };
101
+ const baseUrl = `\${BASE_URL:-${answers.target}}`;
111
102
  switch (answers.type) {
112
103
  case 'api':
113
- return this.generateApiPack(baseConfig, answers.target);
104
+ return this.generateApiPack(answers.name, answers.description, baseUrl);
114
105
  case 'web':
115
- return this.generateWebPack(baseConfig, answers.target);
106
+ return this.generateWebPack(answers.name, answers.description, baseUrl);
116
107
  case 'performance':
117
- return this.generatePerformancePack(baseConfig, answers.target);
108
+ return this.generatePerformancePack(answers.name, answers.description, baseUrl);
118
109
  case 'security':
119
- return this.generateSecurityPack(baseConfig, answers.target);
110
+ return this.generateSecurityPack(answers.name, answers.description, baseUrl);
120
111
  case 'accessibility':
121
- return this.generateAccessibilityPack(baseConfig, answers.target);
112
+ return this.generateAccessibilityPack(answers.name, answers.description, baseUrl);
122
113
  case 'complete':
123
- return this.generateCompletePack(baseConfig, answers.target);
114
+ return this.generateCompletePack(answers.name, answers.description, baseUrl);
124
115
  default:
125
- return this.generateApiPack(baseConfig, answers.target);
116
+ return this.generateApiPack(answers.name, answers.description, baseUrl);
126
117
  }
127
118
  }
128
- generateApiPack(config, target) {
129
- config.adapters = ['playwright'];
130
- config.tests = [
131
- {
132
- name: 'API Health Check',
133
- adapter: 'playwright',
134
- config: {
135
- url: target,
136
- method: 'GET',
137
- expect: {
138
- status: 200,
139
- responseTime: '<2000ms'
140
- }
141
- },
142
- timeout: 10000,
143
- retries: 2
144
- },
145
- {
146
- name: 'API Response Structure',
147
- adapter: 'playwright',
148
- config: {
149
- url: target,
150
- method: 'GET',
151
- expect: {
152
- headers: ['content-type'],
153
- body: 'json'
119
+ generateApiPack(name, description, baseUrl) {
120
+ return {
121
+ version: 2,
122
+ name,
123
+ description,
124
+ gates: {
125
+ 'api-health': {
126
+ adapter: 'playwright-api',
127
+ enabled: true,
128
+ config: {
129
+ baseUrl,
130
+ smoke: [
131
+ 'GET /health -> 200',
132
+ 'GET /status -> 200',
133
+ ]
134
+ },
135
+ options: {
136
+ timeout: 10000,
137
+ retries: 2
154
138
  }
155
139
  }
140
+ },
141
+ execution: {
142
+ default_timeout: 30000,
143
+ default_retries: 2,
144
+ on_failure: 'continue',
145
+ parallel: false
156
146
  }
157
- ];
158
- if (target.includes('httpbin')) {
159
- config.tests.push({
160
- name: 'HTTPBin GET Test',
161
- adapter: 'playwright',
162
- config: {
163
- url: `${target}/get`,
164
- method: 'GET',
165
- expect: {
166
- status: 200,
167
- body: { url: `${target}/get` }
168
- }
169
- }
170
- });
171
- }
172
- return config;
147
+ };
173
148
  }
174
- generateWebPack(config, target) {
175
- config.adapters = ['playwright', 'lighthouse'];
176
- config.tests = [
177
- {
178
- name: 'Page Load Test',
179
- adapter: 'playwright',
180
- config: {
181
- url: target,
182
- actions: [
183
- { type: 'goto', url: target },
184
- { type: 'waitForLoadState', state: 'networkidle' },
185
- { type: 'screenshot', path: 'page-loaded.png' }
186
- ]
187
- }
188
- },
189
- {
190
- name: 'Core Web Vitals',
191
- adapter: 'lighthouse',
192
- config: {
193
- url: target,
194
- categories: ['performance', 'accessibility', 'best-practices', 'seo'],
195
- thresholds: {
196
- performance: 80,
197
- accessibility: 90
149
+ generateWebPack(name, description, baseUrl) {
150
+ return {
151
+ version: 2,
152
+ name,
153
+ description,
154
+ gates: {
155
+ 'ui-smoke': {
156
+ adapter: 'playwright-ui',
157
+ enabled: true,
158
+ config: {
159
+ baseUrl,
160
+ pages: [
161
+ {
162
+ url: '/',
163
+ expectedElements: ['body', 'main']
164
+ }
165
+ ]
166
+ }
167
+ },
168
+ 'a11y': {
169
+ adapter: 'playwright-ui',
170
+ enabled: true,
171
+ config: {
172
+ baseUrl,
173
+ pages: [
174
+ {
175
+ url: '/',
176
+ a11yRules: ['wcag2a', 'wcag2aa']
177
+ }
178
+ ]
179
+ },
180
+ budgets: {
181
+ violations: 10
198
182
  }
199
183
  }
184
+ },
185
+ execution: {
186
+ default_timeout: 30000,
187
+ default_retries: 2,
188
+ on_failure: 'continue',
189
+ parallel: true
200
190
  }
201
- ];
202
- return config;
191
+ };
203
192
  }
204
- generatePerformancePack(config, target) {
205
- config.adapters = ['k6', 'lighthouse'];
206
- config.tests = [
207
- {
208
- name: 'Load Test',
209
- adapter: 'k6',
210
- config: {
211
- url: target,
212
- scenarios: {
213
- load_test: {
214
- executor: 'constant-vus',
215
- vus: 10,
216
- duration: '30s'
217
- }
193
+ generatePerformancePack(name, description, baseUrl) {
194
+ return {
195
+ version: 2,
196
+ name,
197
+ description,
198
+ gates: {
199
+ 'api-health': {
200
+ adapter: 'playwright-api',
201
+ enabled: true,
202
+ config: {
203
+ baseUrl,
204
+ smoke: ['GET /health -> 200']
205
+ }
206
+ },
207
+ 'perf-load': {
208
+ adapter: 'k6-perf',
209
+ enabled: true,
210
+ config: {
211
+ script: `import http from 'k6/http';
212
+ import { check } from 'k6';
213
+
214
+ export const options = {
215
+ stages: [
216
+ { duration: '10s', target: 10 },
217
+ { duration: '20s', target: 50 },
218
+ { duration: '10s', target: 0 },
219
+ ],
220
+ };
221
+
222
+ export default function () {
223
+ const res = http.get('${baseUrl}/api/health');
224
+ check(res, {
225
+ 'status is 200': (r) => r.status === 200,
226
+ 'response time < 500ms': (r) => r.timings.duration < 500,
227
+ });
228
+ }`
218
229
  },
219
- thresholds: {
220
- http_req_duration: ['p(95)<2000'],
221
- http_req_failed: ['rate<0.1']
230
+ budgets: {
231
+ p95_ms: 2000,
232
+ p99_ms: 5000,
233
+ error_rate: 0.05
222
234
  }
223
235
  }
224
236
  },
225
- {
226
- name: 'Performance Audit',
227
- adapter: 'lighthouse',
228
- config: {
229
- url: target,
230
- categories: ['performance'],
231
- thresholds: {
232
- performance: 85
233
- }
234
- }
237
+ execution: {
238
+ default_timeout: 60000,
239
+ default_retries: 1,
240
+ on_failure: 'continue',
241
+ parallel: true
235
242
  }
236
- ];
237
- return config;
243
+ };
238
244
  }
239
- generateSecurityPack(config, target) {
240
- config.adapters = ['zap', 'semgrep', 'playwright'];
241
- config.tests = [
242
- {
243
- name: 'DAST Security Scan',
244
- adapter: 'zap',
245
- config: {
246
- target: 'api',
247
- profile: 'baseline',
248
- url: target,
249
- scanType: 'baseline',
250
- rules: {
251
- 'sql-injection': 'enabled',
252
- 'xss': 'enabled',
253
- 'csrf': 'enabled'
245
+ generateSecurityPack(name, description, baseUrl) {
246
+ return {
247
+ version: 2,
248
+ name,
249
+ description,
250
+ gates: {
251
+ 'api-health': {
252
+ adapter: 'playwright-api',
253
+ enabled: true,
254
+ config: {
255
+ baseUrl,
256
+ smoke: ['GET / -> 200']
254
257
  }
255
- }
256
- },
257
- {
258
- name: 'SAST Code Analysis',
259
- adapter: 'semgrep',
260
- config: {
261
- rules: ['security', 'owasp-top-10'],
262
- paths: ['src/', 'lib/']
263
- }
264
- },
265
- {
266
- name: 'Security Headers Check',
267
- adapter: 'playwright',
268
- config: {
269
- url: target,
270
- method: 'GET',
271
- expect: {
272
- headers: ['x-frame-options', 'x-content-type-options', 'strict-transport-security']
258
+ },
259
+ 'dast-scan': {
260
+ adapter: 'zap-dast',
261
+ enabled: true,
262
+ config: {
263
+ target: 'api',
264
+ profile: 'baseline',
265
+ url: baseUrl,
266
+ scanType: 'baseline'
267
+ },
268
+ budgets: {
269
+ high_findings: 5,
270
+ medium_findings: 20
271
+ }
272
+ },
273
+ 'sast-scan': {
274
+ adapter: 'semgrep-sast',
275
+ enabled: true,
276
+ config: {
277
+ rules: ['security', 'owasp-top-10'],
278
+ paths: ['src/', 'lib/']
279
+ },
280
+ budgets: {
281
+ high_findings: 0,
282
+ medium_findings: 10
283
+ }
284
+ },
285
+ 'secrets-scan': {
286
+ adapter: 'gitleaks-secrets',
287
+ enabled: true,
288
+ config: {
289
+ paths: ['.', 'src/', 'config/']
290
+ },
291
+ budgets: {
292
+ critical_findings: 0
273
293
  }
274
294
  }
295
+ },
296
+ execution: {
297
+ default_timeout: 120000,
298
+ default_retries: 1,
299
+ on_failure: 'continue',
300
+ parallel: false
275
301
  }
276
- ];
277
- return config;
302
+ };
278
303
  }
279
- generateAccessibilityPack(config, target) {
280
- config.adapters = ['playwright', 'lighthouse'];
281
- config.tests = [
282
- {
283
- name: 'WCAG Compliance',
284
- adapter: 'playwright',
285
- config: {
286
- url: target,
287
- accessibility: {
288
- rules: ['wcag2a', 'wcag2aa'],
289
- include: ['main', 'nav', 'form']
304
+ generateAccessibilityPack(name, description, baseUrl) {
305
+ return {
306
+ version: 2,
307
+ name,
308
+ description,
309
+ gates: {
310
+ 'ui-smoke': {
311
+ adapter: 'playwright-ui',
312
+ enabled: true,
313
+ config: {
314
+ baseUrl,
315
+ pages: [
316
+ { url: '/', expectedElements: ['body'] }
317
+ ]
290
318
  }
291
- }
292
- },
293
- {
294
- name: 'Lighthouse A11y Audit',
295
- adapter: 'lighthouse',
296
- config: {
297
- url: target,
298
- categories: ['accessibility'],
299
- thresholds: {
300
- accessibility: 95
319
+ },
320
+ 'a11y-compliance': {
321
+ adapter: 'playwright-ui',
322
+ enabled: true,
323
+ config: {
324
+ baseUrl,
325
+ pages: [
326
+ {
327
+ url: '/',
328
+ a11yRules: ['wcag2a', 'wcag2aa', 'wcag21aa']
329
+ }
330
+ ]
331
+ },
332
+ budgets: {
333
+ violations: 5,
334
+ a11y_score: 90
301
335
  }
302
336
  }
337
+ },
338
+ execution: {
339
+ default_timeout: 30000,
340
+ default_retries: 2,
341
+ on_failure: 'continue',
342
+ parallel: true
303
343
  }
304
- ];
305
- return config;
344
+ };
306
345
  }
307
- generateCompletePack(config, target) {
308
- config.adapters = ['playwright', 'k6', 'semgrep', 'gitleaks', 'lighthouse', 'zap'];
309
- config.tests = [
310
- // API Smoke Tests
311
- {
312
- name: 'API Health Check',
313
- adapter: 'playwright',
314
- config: { url: target, method: 'GET', expect: { status: 200 } }
315
- },
316
- // UI Tests
317
- {
318
- name: 'UI Login Flow',
319
- adapter: 'playwright',
320
- config: {
321
- url: target,
322
- actions: [
323
- { type: 'goto', url: target },
324
- { type: 'click', selector: '[data-testid="login"], .login, #login' },
325
- { type: 'screenshot', path: 'ui-login.png' }
326
- ]
327
- }
328
- },
329
- // Performance P95
330
- {
331
- name: 'Performance P95 Check',
332
- adapter: 'lighthouse',
333
- config: {
334
- url: target,
335
- categories: ['performance'],
336
- thresholds: { performance: 80 }
346
+ generateCompletePack(name, description, baseUrl) {
347
+ return {
348
+ version: 2,
349
+ name,
350
+ description,
351
+ auth: {
352
+ api: 'default-bearer',
353
+ profiles: {
354
+ 'default-bearer': {
355
+ type: 'bearer',
356
+ config: {
357
+ token: '${API_TOKEN:-your-token-here}'
358
+ }
359
+ }
337
360
  }
338
361
  },
339
- // Load Test
340
- {
341
- name: 'Load Test P95',
342
- adapter: 'k6',
343
- config: {
344
- url: target,
345
- scenarios: {
346
- load_test: { executor: 'constant-vus', vus: 10, duration: '30s' }
362
+ gates: {
363
+ // API Health Check
364
+ 'api-health': {
365
+ adapter: 'playwright-api',
366
+ enabled: true,
367
+ auth: 'default-bearer',
368
+ config: {
369
+ baseUrl,
370
+ smoke: [
371
+ 'GET /health -> 200',
372
+ 'GET /api/v1/status -> 200'
373
+ ]
347
374
  },
348
- thresholds: {
349
- 'http_req_duration': ['p(95)<2000']
375
+ options: {
376
+ timeout: 5000,
377
+ retries: 2
378
+ }
379
+ },
380
+ // API CRUD Tests
381
+ 'api-crud': {
382
+ adapter: 'playwright-api',
383
+ enabled: true,
384
+ auth: 'default-bearer',
385
+ config: {
386
+ baseUrl,
387
+ smoke: [
388
+ 'GET /api/v1/users -> 200',
389
+ 'POST /api/v1/users -> 201',
390
+ 'PUT /api/v1/users/1 -> 200',
391
+ 'DELETE /api/v1/users/1 -> 204'
392
+ ]
393
+ },
394
+ budgets: {
395
+ p95_ms: 1000,
396
+ error_rate: 0.01
397
+ }
398
+ },
399
+ // UI E2E Tests
400
+ 'ui-e2e': {
401
+ adapter: 'playwright-ui',
402
+ enabled: true,
403
+ config: {
404
+ baseUrl,
405
+ pages: [
406
+ {
407
+ name: 'Homepage',
408
+ url: '/',
409
+ expectedElements: ['header', 'main']
410
+ },
411
+ {
412
+ name: 'Login',
413
+ url: '/login',
414
+ expectedElements: ['input[type="text"]', 'input[type="password"]']
415
+ }
416
+ ]
417
+ },
418
+ budgets: {
419
+ a11y_score: 85
420
+ }
421
+ },
422
+ // Accessibility Tests
423
+ 'a11y': {
424
+ adapter: 'playwright-ui',
425
+ enabled: true,
426
+ config: {
427
+ baseUrl,
428
+ pages: [
429
+ { url: '/', a11yRules: ['wcag2a', 'wcag2aa'] }
430
+ ]
431
+ },
432
+ budgets: {
433
+ violations: 10
434
+ }
435
+ },
436
+ // Performance Tests
437
+ 'perf': {
438
+ adapter: 'k6-perf',
439
+ enabled: true,
440
+ config: {
441
+ script: `import http from 'k6/http';
442
+ import { check } from 'k6';
443
+
444
+ export const options = {
445
+ stages: [
446
+ { duration: '10s', target: 10 },
447
+ { duration: '20s', target: 50 },
448
+ { duration: '10s', target: 0 },
449
+ ],
450
+ };
451
+
452
+ export default function () {
453
+ const res = http.get('${baseUrl}/api/health');
454
+ check(res, {
455
+ 'status is 200': (r) => r.status === 200,
456
+ 'response time < 500ms': (r) => r.timings.duration < 500,
457
+ });
458
+ }`
459
+ },
460
+ budgets: {
461
+ p95_ms: 2000,
462
+ p99_ms: 5000,
463
+ error_rate: 0.05
464
+ }
465
+ },
466
+ // SAST Security Tests
467
+ 'sast': {
468
+ adapter: 'semgrep-sast',
469
+ enabled: true,
470
+ config: {
471
+ rules: ['security', 'owasp-top-10'],
472
+ paths: ['src/', 'lib/']
473
+ },
474
+ budgets: {
475
+ high_findings: 0,
476
+ medium_findings: 10
477
+ }
478
+ },
479
+ // Secrets Detection
480
+ 'secrets': {
481
+ adapter: 'gitleaks-secrets',
482
+ enabled: true,
483
+ config: {
484
+ paths: ['.', 'src/', 'config/']
485
+ },
486
+ budgets: {
487
+ critical_findings: 0
488
+ }
489
+ },
490
+ // DAST Security Tests
491
+ 'dast': {
492
+ adapter: 'zap-dast',
493
+ enabled: true,
494
+ config: {
495
+ target: 'api',
496
+ profile: 'baseline',
497
+ url: baseUrl,
498
+ scanType: 'baseline'
499
+ },
500
+ budgets: {
501
+ high_findings: 5,
502
+ medium_findings: 20
350
503
  }
351
504
  }
352
505
  },
353
- // SAST Security
354
- {
355
- name: 'SAST Security Scan',
356
- adapter: 'semgrep',
357
- config: {
358
- rules: ['security', 'owasp-top-10'],
359
- paths: ['src/', 'lib/']
360
- }
361
- },
362
- // Secrets Detection
363
- {
364
- name: 'Secrets Detection',
365
- adapter: 'gitleaks',
366
- config: {
367
- paths: ['.', 'src/', 'config/'],
368
- rules: ['api-keys', 'passwords', 'tokens']
369
- }
370
- },
371
- // DAST Security
372
- {
373
- name: 'DAST Baseline Scan',
374
- adapter: 'zap',
375
- config: {
376
- target: 'api',
377
- profile: 'baseline',
378
- url: target,
379
- scanType: 'baseline'
380
- }
506
+ hooks: {
507
+ beforeAll: [
508
+ {
509
+ type: 'wait_on',
510
+ wait_for: {
511
+ resource: `${baseUrl}/health`,
512
+ timeout: 30000
513
+ }
514
+ }
515
+ ],
516
+ afterAll: [
517
+ {
518
+ type: 'run',
519
+ command: 'echo "Test suite finished"',
520
+ timeout: 5000
521
+ }
522
+ ]
381
523
  },
382
- // Accessibility
383
- {
384
- name: 'A11y Compliance',
385
- adapter: 'lighthouse',
386
- config: {
387
- url: target,
388
- categories: ['accessibility'],
389
- thresholds: { accessibility: 90 }
390
- }
524
+ execution: {
525
+ default_timeout: 60000,
526
+ default_retries: 2,
527
+ on_failure: 'continue',
528
+ parallel: true,
529
+ max_concurrency: 3
391
530
  }
392
- ];
393
- config.hooks = {
394
- beforeAll: ['echo "Starting comprehensive test suite"'],
395
- afterAll: ['echo "Complete test suite finished"']
396
531
  };
397
- return config;
398
532
  }
399
533
  async savePack(pack) {
400
534
  // Save to qa360.yml in current directory (not .qa360/pack.yml)
@@ -402,7 +536,9 @@ export class QA360Ask {
402
536
  const yamlContent = dump(pack, {
403
537
  indent: 2,
404
538
  lineWidth: 120,
405
- noRefs: true
539
+ noRefs: true,
540
+ quotingType: '"',
541
+ forceQuotes: false
406
542
  });
407
543
  writeFileSync(packPath, yamlContent, 'utf8');
408
544
  return packPath;
@@ -410,11 +546,23 @@ export class QA360Ask {
410
546
  displayPack(pack) {
411
547
  console.log(chalk.bold('\n[PACK] Pack genere:\n'));
412
548
  console.log(chalk.blue(`Nom: ${pack.name}`));
413
- console.log(chalk.gray(`Description: ${pack.description}`));
414
- console.log(chalk.green(`Adapters: ${pack.adapters.join(', ')}`));
415
- console.log(chalk.yellow(`Tests: ${pack.tests.length} test(s) configure(s)`));
416
- if (pack.environment?.TARGET_URL) {
417
- console.log(chalk.cyan(`Cible: ${pack.environment.TARGET_URL}`));
549
+ console.log(chalk.gray(`Description: ${pack.description || 'N/A'}`));
550
+ console.log(chalk.green(`Version: v2`));
551
+ console.log(chalk.yellow(`Gates: ${Object.keys(pack.gates).length} gate(s) configure(s)`));
552
+ const gateNames = Object.keys(pack.gates);
553
+ if (gateNames.length > 0) {
554
+ console.log(chalk.cyan(` - ${gateNames.join('\n - ')}`));
555
+ }
556
+ if (pack.auth?.api) {
557
+ console.log(chalk.magenta(`Auth API: ${pack.auth.api}`));
558
+ }
559
+ // Find BASE_URL in gate configs
560
+ for (const gate of Object.values(pack.gates)) {
561
+ if (gate.config?.baseUrl) {
562
+ const url = String(gate.config.baseUrl).replace(/\$\{BASE_URL:-([^}]+)\}/, '$1');
563
+ console.log(chalk.cyan(`Cible: ${url}`));
564
+ break;
565
+ }
418
566
  }
419
567
  }
420
568
  }