qa360 1.0.1 → 1.0.3
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/bin/qa360.js +11 -0
- package/dist/commands/history.d.ts.map +1 -1
- package/dist/commands/history.js +1 -1
- package/dist/commands/report.d.ts +43 -28
- package/dist/commands/report.d.ts.map +1 -1
- package/dist/commands/report.js +275 -247
- package/dist/commands/run.d.ts +31 -50
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +122 -278
- package/dist/commands/secrets.js +1 -1
- package/dist/commands/verify.d.ts.map +1 -1
- package/dist/commands/verify.js +29 -6
- package/dist/index.js +17 -8
- package/package.json +14 -9
package/dist/commands/run.d.ts
CHANGED
|
@@ -1,55 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* QA360 Run Command -
|
|
2
|
+
* QA360 Run Command - Execute quality gates
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* qa360 run [pack.yaml]
|
|
6
|
+
* qa360 run --output ./results
|
|
7
|
+
* qa360 run --verbose
|
|
3
8
|
*/
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
timestamp: string;
|
|
10
|
-
duration: number;
|
|
11
|
-
}
|
|
12
|
-
export interface TestResult {
|
|
13
|
-
name: string;
|
|
14
|
-
adapter: string;
|
|
15
|
-
status: 'passed' | 'failed' | 'skipped';
|
|
16
|
-
duration: number;
|
|
9
|
+
import { type Phase3RunResult, type PackConfigV1 } from 'qa360-core';
|
|
10
|
+
/**
|
|
11
|
+
* CLI options for run command
|
|
12
|
+
*/
|
|
13
|
+
export interface RunOptions {
|
|
17
14
|
output?: string;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
export interface RunSummary {
|
|
22
|
-
total: number;
|
|
23
|
-
passed: number;
|
|
24
|
-
failed: number;
|
|
25
|
-
skipped: number;
|
|
26
|
-
successRate: number;
|
|
15
|
+
verbose?: boolean;
|
|
16
|
+
dryRun?: boolean;
|
|
17
|
+
strict?: boolean;
|
|
27
18
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
private executeLighthouseTest;
|
|
46
|
-
private executeSemgrepTest;
|
|
47
|
-
private executeZapTest;
|
|
48
|
-
private executeInDocker;
|
|
49
|
-
private isToolAvailable;
|
|
50
|
-
private generateSummary;
|
|
51
|
-
private saveRunResults;
|
|
52
|
-
private displayResults;
|
|
53
|
-
}
|
|
54
|
-
export declare function runCommand(packPath?: string, options?: any): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Load and validate pack configuration
|
|
21
|
+
*/
|
|
22
|
+
export declare function loadPack(packPath: string): Promise<PackConfigV1>;
|
|
23
|
+
/**
|
|
24
|
+
* Find pack file (search order: argument, .qa360/pack.yml, pack.yaml, pack.yml)
|
|
25
|
+
*/
|
|
26
|
+
export declare function findPackFile(packArg?: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* Display run results summary
|
|
29
|
+
*/
|
|
30
|
+
export declare function displayResults(result: Phase3RunResult): void;
|
|
31
|
+
/**
|
|
32
|
+
* Main run command
|
|
33
|
+
*/
|
|
34
|
+
export declare function runCommand(packArg?: string, options?: RunOptions): Promise<void>;
|
|
35
|
+
export default runCommand;
|
|
55
36
|
//# sourceMappingURL=run.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,OAAO,EAAgB,KAAK,eAAe,EAAkC,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAEnH;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,wBAAsB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAwBtE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CA0BrD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CA8B5D;AAED;;GAEG;AACH,wBAAsB,UAAU,CAC9B,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,GAAE,UAAe,GACvB,OAAO,CAAC,IAAI,CAAC,CAiDf;AAGD,eAAe,UAAU,CAAC"}
|
package/dist/commands/run.js
CHANGED
|
@@ -1,293 +1,137 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* QA360 Run Command -
|
|
2
|
+
* QA360 Run Command - Execute quality gates
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* qa360 run [pack.yaml]
|
|
6
|
+
* qa360 run --output ./results
|
|
7
|
+
* qa360 run --verbose
|
|
3
8
|
*/
|
|
4
|
-
import {
|
|
5
|
-
import { join } from 'path';
|
|
6
|
-
import { execSync } from 'child_process';
|
|
9
|
+
import { existsSync, readFileSync } from 'fs';
|
|
10
|
+
import { join, resolve } from 'path';
|
|
7
11
|
import chalk from 'chalk';
|
|
8
|
-
import ora from 'ora';
|
|
9
12
|
import { load } from 'js-yaml';
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
async loadPack(packPath) {
|
|
54
|
-
const defaultPath = join(this.qa360Dir, 'pack.yml');
|
|
55
|
-
const targetPath = packPath || defaultPath;
|
|
56
|
-
if (!existsSync(targetPath)) {
|
|
57
|
-
throw new Error(`Pack non trouvé: ${targetPath}\n💡 Utilisez 'qa360 ask' pour générer un pack`);
|
|
58
|
-
}
|
|
59
|
-
try {
|
|
60
|
-
const content = readFileSync(targetPath, 'utf8');
|
|
61
|
-
const pack = load(content);
|
|
62
|
-
// Validation basique
|
|
63
|
-
if (!pack.name || !pack.adapters || !pack.tests) {
|
|
64
|
-
throw new Error('Pack invalide: champs requis manquants (name, adapters, tests)');
|
|
65
|
-
}
|
|
66
|
-
return pack;
|
|
67
|
-
}
|
|
68
|
-
catch (error) {
|
|
69
|
-
throw new Error(`Erreur de parsing du pack: ${error}`);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
async executeHooks(hooks, phase) {
|
|
73
|
-
if (!hooks || hooks.length === 0)
|
|
74
|
-
return;
|
|
75
|
-
console.log(chalk.yellow(`🔗 Exécution hooks ${phase}:`));
|
|
76
|
-
for (const hook of hooks) {
|
|
77
|
-
const spinner = ora(`Hook: ${hook}`).start();
|
|
78
|
-
try {
|
|
79
|
-
execSync(hook, {
|
|
80
|
-
stdio: 'pipe',
|
|
81
|
-
cwd: process.cwd(),
|
|
82
|
-
timeout: 30000
|
|
83
|
-
});
|
|
84
|
-
spinner.succeed();
|
|
85
|
-
}
|
|
86
|
-
catch (error) {
|
|
87
|
-
spinner.fail(`Hook échoué: ${error}`);
|
|
88
|
-
throw error;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
async executeTests(tests, options) {
|
|
93
|
-
const results = [];
|
|
94
|
-
console.log(chalk.blue(`\n🧪 Exécution de ${tests.length} test(s):\n`));
|
|
95
|
-
for (let i = 0; i < tests.length; i++) {
|
|
96
|
-
const test = tests[i];
|
|
97
|
-
const spinner = ora(`[${i + 1}/${tests.length}] ${test.name}`).start();
|
|
98
|
-
const startTime = Date.now();
|
|
99
|
-
try {
|
|
100
|
-
const result = await this.executeTest(test, options);
|
|
101
|
-
result.duration = Date.now() - startTime;
|
|
102
|
-
if (result.status === 'passed') {
|
|
103
|
-
spinner.succeed(chalk.green(`${test.name} (${result.duration}ms)`));
|
|
104
|
-
}
|
|
105
|
-
else if (result.status === 'skipped') {
|
|
106
|
-
spinner.warn(chalk.yellow(`${test.name} - Ignoré`));
|
|
107
|
-
}
|
|
108
|
-
else {
|
|
109
|
-
spinner.fail(chalk.red(`${test.name} - ${result.error}`));
|
|
110
|
-
}
|
|
111
|
-
results.push(result);
|
|
112
|
-
}
|
|
113
|
-
catch (error) {
|
|
114
|
-
const result = {
|
|
115
|
-
name: test.name,
|
|
116
|
-
adapter: test.adapter,
|
|
117
|
-
status: 'failed',
|
|
118
|
-
duration: Date.now() - startTime,
|
|
119
|
-
error: error instanceof Error ? error.message : String(error)
|
|
120
|
-
};
|
|
121
|
-
spinner.fail(chalk.red(`${test.name} - Erreur: ${result.error}`));
|
|
122
|
-
results.push(result);
|
|
123
|
-
// En mode strict, arrêter à la première erreur
|
|
124
|
-
if (options.mode === 'strict') {
|
|
125
|
-
throw new Error(`Test échoué en mode strict: ${test.name}`);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
return results;
|
|
130
|
-
}
|
|
131
|
-
async executeTest(test, options) {
|
|
132
|
-
const baseResult = {
|
|
133
|
-
name: test.name,
|
|
134
|
-
adapter: test.adapter,
|
|
135
|
-
status: 'failed',
|
|
136
|
-
duration: 0,
|
|
137
|
-
artifacts: []
|
|
138
|
-
};
|
|
139
|
-
switch (test.adapter) {
|
|
140
|
-
case 'playwright':
|
|
141
|
-
return await this.executePlaywrightTest(test, baseResult, options);
|
|
142
|
-
case 'k6':
|
|
143
|
-
return await this.executeK6Test(test, baseResult, options);
|
|
144
|
-
case 'lighthouse':
|
|
145
|
-
return await this.executeLighthouseTest(test, baseResult, options);
|
|
146
|
-
case 'semgrep':
|
|
147
|
-
return await this.executeSemgrepTest(test, baseResult, options);
|
|
148
|
-
case 'zap':
|
|
149
|
-
return await this.executeZapTest(test, baseResult, options);
|
|
150
|
-
default:
|
|
151
|
-
throw new Error(`Adapter non supporté: ${test.adapter}`);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
async executePlaywrightTest(test, result, options) {
|
|
155
|
-
try {
|
|
156
|
-
// Check if Playwright is available
|
|
157
|
-
if (!this.isToolAvailable('playwright')) {
|
|
158
|
-
if (options.docker) {
|
|
159
|
-
return await this.executeInDocker(test, result);
|
|
160
|
-
}
|
|
161
|
-
throw new Error('Playwright non disponible. Utilisez --docker ou installez Playwright');
|
|
162
|
-
}
|
|
163
|
-
const config = test.config;
|
|
164
|
-
// Simple HTTP test for API endpoints
|
|
165
|
-
if (config.url && config.method) {
|
|
166
|
-
// Use native fetch (Node 18+)
|
|
167
|
-
const response = await fetch(config.url, {
|
|
168
|
-
method: config.method || 'GET',
|
|
169
|
-
signal: AbortSignal.timeout(test.timeout || 10000)
|
|
170
|
-
});
|
|
171
|
-
// Check expectations
|
|
172
|
-
if (config.expect?.status && response.status !== config.expect.status) {
|
|
173
|
-
throw new Error(`Status attendu: ${config.expect.status}, reçu: ${response.status}`);
|
|
174
|
-
}
|
|
175
|
-
if (config.expect?.responseTime) {
|
|
176
|
-
const maxTime = parseInt(config.expect.responseTime.replace(/[^\d]/g, ''));
|
|
177
|
-
if (result.duration > maxTime) {
|
|
178
|
-
throw new Error(`Temps de réponse trop élevé: ${result.duration}ms > ${maxTime}ms`);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
result.status = 'passed';
|
|
182
|
-
result.output = `HTTP ${response.status} - ${config.url}`;
|
|
183
|
-
}
|
|
184
|
-
else if (config.actions) {
|
|
185
|
-
// Browser automation test (simplified)
|
|
186
|
-
result.status = 'skipped';
|
|
187
|
-
result.output = 'Test browser ignoré (implémentation Phase 2)';
|
|
188
|
-
}
|
|
189
|
-
else {
|
|
190
|
-
throw new Error('Configuration Playwright invalide');
|
|
191
|
-
}
|
|
192
|
-
return result;
|
|
193
|
-
}
|
|
194
|
-
catch (error) {
|
|
195
|
-
result.error = error instanceof Error ? error.message : String(error);
|
|
196
|
-
return result;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
async executeK6Test(test, result, options) {
|
|
200
|
-
try {
|
|
201
|
-
if (!this.isToolAvailable('k6')) {
|
|
202
|
-
result.status = 'skipped';
|
|
203
|
-
result.output = 'k6 non disponible - test ignoré';
|
|
204
|
-
return result;
|
|
205
|
-
}
|
|
206
|
-
// Simplified k6 test execution
|
|
207
|
-
const config = test.config;
|
|
208
|
-
// Basic load test simulation (using native fetch for Node 18+)
|
|
209
|
-
const vus = config.scenarios?.load_test?.vus || 1;
|
|
210
|
-
const duration = config.scenarios?.load_test?.duration || '10s';
|
|
211
|
-
const promises = [];
|
|
212
|
-
for (let i = 0; i < vus; i++) {
|
|
213
|
-
promises.push(fetch(config.url));
|
|
214
|
-
}
|
|
215
|
-
await Promise.all(promises);
|
|
216
|
-
result.status = 'passed';
|
|
217
|
-
result.output = `Load test: ${vus} VUs pendant ${duration}`;
|
|
218
|
-
}
|
|
219
|
-
catch (error) {
|
|
220
|
-
result.error = error instanceof Error ? error.message : String(error);
|
|
221
|
-
}
|
|
222
|
-
return result;
|
|
223
|
-
}
|
|
224
|
-
async executeLighthouseTest(test, result, options) {
|
|
225
|
-
result.status = 'skipped';
|
|
226
|
-
result.output = 'Lighthouse test ignoré (implémentation Phase 2)';
|
|
227
|
-
return result;
|
|
228
|
-
}
|
|
229
|
-
async executeSemgrepTest(test, result, options) {
|
|
230
|
-
result.status = 'skipped';
|
|
231
|
-
result.output = 'Semgrep test ignoré (implémentation Phase 2)';
|
|
232
|
-
return result;
|
|
233
|
-
}
|
|
234
|
-
async executeZapTest(test, result, options) {
|
|
235
|
-
result.status = 'skipped';
|
|
236
|
-
result.output = 'ZAP test ignoré (implémentation Phase 2)';
|
|
237
|
-
return result;
|
|
238
|
-
}
|
|
239
|
-
async executeInDocker(test, result) {
|
|
240
|
-
result.status = 'skipped';
|
|
241
|
-
result.output = 'Exécution Docker ignorée (implémentation Phase 2)';
|
|
242
|
-
return result;
|
|
243
|
-
}
|
|
244
|
-
isToolAvailable(tool) {
|
|
245
|
-
try {
|
|
246
|
-
execSync(`which ${tool}`, { stdio: 'pipe' });
|
|
247
|
-
return true;
|
|
248
|
-
}
|
|
249
|
-
catch {
|
|
250
|
-
return false;
|
|
13
|
+
import { Phase3Runner, PackValidator } from 'qa360-core';
|
|
14
|
+
/**
|
|
15
|
+
* Load and validate pack configuration
|
|
16
|
+
*/
|
|
17
|
+
export async function loadPack(packPath) {
|
|
18
|
+
if (!existsSync(packPath)) {
|
|
19
|
+
throw new Error(`Pack file not found: ${packPath}`);
|
|
20
|
+
}
|
|
21
|
+
const content = readFileSync(packPath, 'utf-8');
|
|
22
|
+
const pack = load(content);
|
|
23
|
+
// Validate pack schema
|
|
24
|
+
const validator = new PackValidator();
|
|
25
|
+
const validation = await validator.validate(pack);
|
|
26
|
+
if (!validation.valid) {
|
|
27
|
+
const errors = validation.errors?.map(e => ` • ${e}`).join('\n');
|
|
28
|
+
throw new Error(`Invalid pack configuration:\n${errors}`);
|
|
29
|
+
}
|
|
30
|
+
// Show warnings if any
|
|
31
|
+
if (validation.warnings && validation.warnings.length > 0) {
|
|
32
|
+
console.log(chalk.yellow('\n⚠️ Pack warnings:'));
|
|
33
|
+
validation.warnings.forEach(w => console.log(chalk.yellow(` • ${w}`)));
|
|
34
|
+
}
|
|
35
|
+
return pack;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Find pack file (search order: argument, .qa360/pack.yml, pack.yaml, pack.yml)
|
|
39
|
+
*/
|
|
40
|
+
export function findPackFile(packArg) {
|
|
41
|
+
if (packArg) {
|
|
42
|
+
return resolve(packArg);
|
|
43
|
+
}
|
|
44
|
+
const searchPaths = [
|
|
45
|
+
join(process.cwd(), '.qa360', 'pack.yml'),
|
|
46
|
+
join(process.cwd(), '.qa360', 'pack.yaml'),
|
|
47
|
+
join(process.cwd(), 'pack.yml'),
|
|
48
|
+
join(process.cwd(), 'pack.yaml'),
|
|
49
|
+
join(process.cwd(), 'qa360.yml'),
|
|
50
|
+
join(process.cwd(), 'qa360.yaml'),
|
|
51
|
+
];
|
|
52
|
+
for (const path of searchPaths) {
|
|
53
|
+
if (existsSync(path)) {
|
|
54
|
+
return path;
|
|
251
55
|
}
|
|
252
56
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
console.log(
|
|
270
|
-
console.log(
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
console.log(chalk.
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
57
|
+
throw new Error('No pack file found. Create one with:\n' +
|
|
58
|
+
' qa360 ask "test my API at https://api.example.com"\n' +
|
|
59
|
+
'Or specify a path:\n' +
|
|
60
|
+
' qa360 run path/to/pack.yaml');
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Display run results summary
|
|
64
|
+
*/
|
|
65
|
+
export function displayResults(result) {
|
|
66
|
+
console.log(chalk.bold('\n' + '═'.repeat(50)));
|
|
67
|
+
console.log(chalk.bold('📊 EXECUTION SUMMARY'));
|
|
68
|
+
console.log('═'.repeat(50));
|
|
69
|
+
console.log(`\n⏱️ Duration: ${result.duration}ms`);
|
|
70
|
+
console.log(`🎯 Trust Score: ${result.summary.trustScore}%`);
|
|
71
|
+
console.log(`📋 Gates: ${result.summary.passed}/${result.summary.total} passed`);
|
|
72
|
+
if (result.proofPath) {
|
|
73
|
+
console.log(`\n📄 Proof: ${result.proofPath}`);
|
|
74
|
+
console.log(chalk.gray(' Verify with: qa360 verify ' + result.proofPath));
|
|
75
|
+
}
|
|
76
|
+
if (result.success) {
|
|
77
|
+
console.log(chalk.green.bold('\n✅ All quality gates passed!'));
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
console.log(chalk.red.bold(`\n❌ ${result.summary.failed} gate(s) failed`));
|
|
81
|
+
// Show failed gates
|
|
82
|
+
const failed = result.gates.filter((g) => !g.success);
|
|
83
|
+
if (failed.length > 0) {
|
|
84
|
+
console.log(chalk.red('\nFailed gates:'));
|
|
85
|
+
failed.forEach((g) => {
|
|
86
|
+
console.log(chalk.red(` • ${g.gate}: ${g.error || 'Unknown error'}`));
|
|
87
|
+
});
|
|
280
88
|
}
|
|
281
|
-
console.log(chalk.blue('\n💡 Prochaine étape: qa360 report --sign'));
|
|
282
89
|
}
|
|
90
|
+
console.log('═'.repeat(50) + '\n');
|
|
283
91
|
}
|
|
284
|
-
|
|
285
|
-
|
|
92
|
+
/**
|
|
93
|
+
* Main run command
|
|
94
|
+
*/
|
|
95
|
+
export async function runCommand(packArg, options = {}) {
|
|
286
96
|
try {
|
|
287
|
-
|
|
97
|
+
// Step 1: Find pack file
|
|
98
|
+
const packPath = findPackFile(packArg);
|
|
99
|
+
console.log(chalk.blue(`\n📦 Loading pack: ${packPath}`));
|
|
100
|
+
// Step 2: Load and validate pack
|
|
101
|
+
const pack = await loadPack(packPath);
|
|
102
|
+
console.log(chalk.green(` ✅ Pack loaded: ${pack.name} v${pack.version}`));
|
|
103
|
+
console.log(chalk.gray(` Gates: ${pack.gates.join(', ')}`));
|
|
104
|
+
// Step 3: Dry run check
|
|
105
|
+
if (options.dryRun) {
|
|
106
|
+
console.log(chalk.yellow('\n🔍 Dry run mode - no tests executed'));
|
|
107
|
+
console.log(chalk.gray(' Remove --dry-run to execute tests'));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
// Step 4: Create and run Phase3Runner
|
|
111
|
+
const workingDir = process.cwd();
|
|
112
|
+
const outputDir = options.output
|
|
113
|
+
? resolve(options.output)
|
|
114
|
+
: join(workingDir, '.qa360', 'runs');
|
|
115
|
+
const runner = new Phase3Runner({
|
|
116
|
+
workingDir,
|
|
117
|
+
pack,
|
|
118
|
+
outputDir,
|
|
119
|
+
});
|
|
120
|
+
const result = await runner.run();
|
|
121
|
+
// Step 5: Display results
|
|
122
|
+
displayResults(result);
|
|
123
|
+
// Step 6: Exit with appropriate code
|
|
124
|
+
process.exit(result.success ? 0 : 1);
|
|
288
125
|
}
|
|
289
126
|
catch (error) {
|
|
290
|
-
console.error(chalk.red('❌
|
|
127
|
+
console.error(chalk.red('\n❌ Execution failed:'));
|
|
128
|
+
console.error(chalk.red(` ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
129
|
+
if (options.verbose && error instanceof Error && error.stack) {
|
|
130
|
+
console.error(chalk.gray('\nStack trace:'));
|
|
131
|
+
console.error(chalk.gray(error.stack));
|
|
132
|
+
}
|
|
291
133
|
process.exit(1);
|
|
292
134
|
}
|
|
293
135
|
}
|
|
136
|
+
// Export for CLI integration
|
|
137
|
+
export default runCommand;
|
package/dist/commands/secrets.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../src/commands/verify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;
|
|
1
|
+
{"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../src/commands/verify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAeH;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAqGD;;GAEG;AACH,wBAAsB,YAAY,CAChC,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,MAAM,CAAC,CAgDjB;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,MAAM,CAAC,CA6EjB;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,IAAI,CAAC,CA2Bf"}
|
package/dist/commands/verify.js
CHANGED
|
@@ -14,7 +14,7 @@ import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
|
|
|
14
14
|
import { join, resolve, basename } from 'path';
|
|
15
15
|
import chalk from 'chalk';
|
|
16
16
|
// Import from core
|
|
17
|
-
import { verifyProofFile, VerificationCode, } from 'qa360-core';
|
|
17
|
+
import { verifyProofFile, verifyPhase3Proof, VerificationCode, } from 'qa360-core';
|
|
18
18
|
/**
|
|
19
19
|
* Format verification result for human-readable output
|
|
20
20
|
*/
|
|
@@ -83,12 +83,17 @@ function findProofFiles(dirPath) {
|
|
|
83
83
|
for (const entry of entries) {
|
|
84
84
|
const fullPath = join(dirPath, entry);
|
|
85
85
|
const stat = statSync(fullPath);
|
|
86
|
-
if (stat.isFile() && entry.endsWith('.json')) {
|
|
87
|
-
|
|
86
|
+
if (stat.isFile() && entry.endsWith('-proof.json')) {
|
|
87
|
+
files.push(fullPath);
|
|
88
|
+
}
|
|
89
|
+
else if (stat.isFile() && entry.endsWith('.json')) {
|
|
90
|
+
// Check if it looks like a proof bundle (either format)
|
|
88
91
|
try {
|
|
89
92
|
const content = readFileSync(fullPath, 'utf-8');
|
|
90
93
|
const json = JSON.parse(content);
|
|
91
|
-
|
|
94
|
+
// Support both RFC format (spec) and Phase3 format (version + signature)
|
|
95
|
+
if (json.spec === 'qa360.proof.v1' ||
|
|
96
|
+
(json.version && json.signature && json.runId)) {
|
|
92
97
|
files.push(fullPath);
|
|
93
98
|
}
|
|
94
99
|
}
|
|
@@ -113,14 +118,32 @@ export async function verifySingle(filePath, options = {}) {
|
|
|
113
118
|
return 1;
|
|
114
119
|
}
|
|
115
120
|
try {
|
|
116
|
-
|
|
121
|
+
// Detect proof format
|
|
122
|
+
const content = readFileSync(absolutePath, 'utf-8');
|
|
123
|
+
const json = JSON.parse(content);
|
|
124
|
+
let result;
|
|
125
|
+
if (json.spec === 'qa360.proof.v1') {
|
|
126
|
+
// RFC format
|
|
127
|
+
result = await verifyProofFile(absolutePath);
|
|
128
|
+
}
|
|
129
|
+
else if (json.version && json.signature && json.runId) {
|
|
130
|
+
// Phase3 format
|
|
131
|
+
result = await verifyPhase3Proof(absolutePath);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
result = {
|
|
135
|
+
valid: false,
|
|
136
|
+
code: VerificationCode.PROOF_MALFORMED,
|
|
137
|
+
message: 'Unknown proof format',
|
|
138
|
+
};
|
|
139
|
+
}
|
|
117
140
|
if (options.json) {
|
|
118
141
|
formatJSON(result);
|
|
119
142
|
}
|
|
120
143
|
else {
|
|
121
144
|
formatResult(result, filePath);
|
|
122
145
|
}
|
|
123
|
-
return result.code;
|
|
146
|
+
return result.valid ? 0 : result.code;
|
|
124
147
|
}
|
|
125
148
|
catch (error) {
|
|
126
149
|
if (options.json) {
|
package/dist/index.js
CHANGED
|
@@ -7,14 +7,21 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { Command } from 'commander';
|
|
9
9
|
import chalk from 'chalk';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
import {
|
|
10
|
+
import { readFileSync } from 'fs';
|
|
11
|
+
import { join, dirname } from 'path';
|
|
12
|
+
import { fileURLToPath } from 'url';
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = dirname(__filename);
|
|
15
|
+
const packageJson = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf8'));
|
|
16
|
+
const version = packageJson.version;
|
|
17
|
+
import { doctorCommand } from './commands/doctor.js';
|
|
18
|
+
import { askCommand } from './commands/ask.js';
|
|
19
|
+
import { runCommand } from './commands/run.js';
|
|
20
|
+
import { reportCommand } from './commands/report.js';
|
|
21
|
+
import { verifyCommand } from './commands/verify.js';
|
|
22
|
+
import { packValidateCommand, packLintCommand, packMigrateCommand } from './commands/pack.js';
|
|
23
|
+
import { secretsAddCommand, secretsListCommand, secretsRemoveCommand, secretsDoctorCommand, secretsExportCommand } from './commands/secrets.js';
|
|
24
|
+
import { createHistoryCommands } from './commands/history.js';
|
|
18
25
|
const program = new Command();
|
|
19
26
|
program
|
|
20
27
|
.name('qa360')
|
|
@@ -187,6 +194,8 @@ program
|
|
|
187
194
|
console.log(chalk.blue('📦 QA360 Proof Bundle'));
|
|
188
195
|
console.log(chalk.yellow('⚠️ Phase 3 implementation coming soon...'));
|
|
189
196
|
});
|
|
197
|
+
// History commands (Evidence Vault)
|
|
198
|
+
program.addCommand(createHistoryCommands());
|
|
190
199
|
// Show banner
|
|
191
200
|
console.log(chalk.bold.blue('QA360 Core v' + version));
|
|
192
201
|
console.log(chalk.gray('Transform software testing into verifiable, signed, and traceable proofs\n'));
|